Programming Language/go

Go 언어 프로그래밍 - 슬라이스(Slice)

김크리 2021. 7. 5. 23:04

『Tucker의 Go 언어 프로그래밍』 스터디 요약 노트입니다.


슬라이스

Go 에서 제공하는 동적 배열 타입이다.

  • 정적 타입 (static type) : Compile time, Build time 시전에 진행, 실행도중 절대 변하지 않는 값
  • 동적 타입 (dynamic type) : Runtime, 프로그램 실행 중 진행, 실행도중 변할 수 있는 값

즉, 슬라이스는 프로그램 실행 중 사이즈가 변경될 수 있는 동적 배열이다.

var slice []int //슬라이스 선언 slice := []int{}

ex18.1

package main import "fmt" func main() { var slice []int //처음 초기화시, 슬라이스의 길이는 0이다. if len(slice) == 0 { fmt.Println("slice is empty", slice) } //slice[1] = 10 // 실행한다면 runtime 에러가 날 것이다.(길이 초과) fmt.Println(slice) slice = []int{1, 2, 3} //slice에 1,2,3을 넣는다. fmt.Println(slice) }

package main func main() { var slice = []int{1, 2, 3} //슬라이스 선언 var slice2 = []int{1, 5: 2, 10: 3} //[1 0 0 0 0 2 0 0 0 0 3] var array = [...]int{1, 2, 3} //배열선언 //배열과 슬라이스 선언방법에 주의 var slice3 = make([]int, 3) var slice4 = make[]int{0,0,0} //slice3과 slice4는 동일하게 [0 0 0]의 슬라이스가 생성된다. }

슬라이스 순회

배열과 유사

슬라이스 요소 추가 - Append()

배열과의 차이점을 슬라이스 요소 추가를 통해 알 수 있다.
append는 슬라이스에 값을 추가하는 내장함수이다.

package main import "fmt" func main() { var slice = []int{1, 2, 3} //요소가 3개인 슬라이스 slice2 := append(slice, 4) //요소 추가 - 새로운 슬라이스를 만들어서 append fmt.Println(slice) fmt.Println(slice2) }

슬라이스 동작원리

파이썬의 슬라이스와는 상이하게 동작한다.
슬라이스는 구조체로 되어있으며, 아래와 같은 구조로 되어있다.
실제 배열이 별도로 존재하고, 실제 배열을 가리키는 형태로 되어있다.

type SliceHeader struct { Data uintptr //실제 배열을 가리키는 포인터 Len int //요소 개수 Cap int //실제 배열의 길이, capacity의 약자(실제 사용자가 만들수 있는 배열의 최대 길이) }

make 함수 활용1

var slice2 = make([]int, 3) // make(타입, len) //length 만큼 배열을 사용하는 슬라이스를 만든다.

make 함수 활용2

var slice2 = make([]int, 3, 5) // make(타입, len, cap) //capacity에 해당되는 만큼 배열을 만들고, length 만큼 배열을 사용하는 슬라이스를 만든다.

배열과 슬라이스의 차이

package main import "fmt" //모든 배열이 복사된다. func changeArray(array2 [5]int) { array2[2] = 200 } //배열이 아닌 slice structure(24bytes)가 복사된다. func changeSlice(slice2 []int) { slice2[2] = 200 } func main() { array := [5]int{1, 2, 3, 4, 5} slice := []int{1, 2, 3, 4, 5} changeArray(array) //array를 대입받은 array2가 생성된다.(값을 복사) changeSlice(slice) fmt.Println(array) fmt.Println(slice) }

array는 현재 값을 전달하고 있으므로, 가리키는 값을 변하는 것이 아니다.
Go 언어는 대입연산자를 사용 할 경우, 그대로 값을 복사한다.
slice의 경우, slice structure를 복사한다.

슬라이스 - append() 동작 원리

append()는 슬라이스에 요소를 추가한 새로운 슬라이스를 반환한다.
기존의 슬라이스가 바뀔 수도 있고 아닐 수도 있다.

남은 빈 공간 = cap - len

1. 빈공간이 충분할 경우, 빈공간에 요소를 추가한다.
2. 빈공간이 충분하지 못 할 경우,

새로운 배열 할당 -> 기존 슬라이스 복사 -> 요소 추가

하여 새로운 슬라이스를 반환한다.

2번의 경우, slice structure를 복사하여 capacity를 늘린 값이 반환된다.
이에 따라서 기존의 slice를 변환하면 생성된 슬라이스가 같이 변환될 수 있다. (구조체가 동일)
slice 의 구조에 대해 이해하고, 필요에 따른 충분한 capacity 할당이 필요하다.

새로운 배열을 만들지 않고 같은 슬라이스를 사용하여 값을 만들 때, 기존의 값을 참조한다는 것은 인지해야한다.
해당 부분으로 생각지 못한 오류를 범할 수 있다.

흔히 하는 실수

package main import ( "fmt" ) //이름은 동일하지만 호출할때 들어가는 슬라이스 값과 가리키는 주소값이 다르다. //새로운 주소값을 참조하는 슬라이스를 만든다. func addNum(slice []int) { slice = append(slice, 4) } //메모리 주소 값을 가져가서 주소값의 데이터를 변경한다. func addNum1(slice *[]int) { *slice = append(*slice, 4) } //생성한 새로운 슬라이스를 반환한다. func addNum2(slice []int) []int { slice = append(slice, 4) return slice } func main() { slice := []int{1, 2, 3} addNum(slice) fmt.Println(slice) addNum1(&slice) fmt.Println(slice) slice = addNum2(slice) fmt.Println(slice) }

슬라이싱(Slicing)

슬라이싱은 배열의 일부를 집어내는 기능이다.
슬라이싱의 결과가 슬라이스이다.
어떤 배열이 있을 때, 시작인덱스와 끝인덱스를 설정하여 배열을 추출하여(잘라내어) 슬라이스를 만들어낸다.

array[startIdx:endindex]
1(index : 0) 2(index : 1) 3(index : 2) 4(index : 3) 5(index : 4)

위에서 array[1:4] 일 경우, {2,3,4}값을 반환한다. 시작값은 포함하고 끝값은 포함하지 않는다.

2 3 4


예제를 실행하기전 어떻게 출력되는지 생각하고 진행해보자.

package main import ( "fmt" ) func main() { array := [5]int{1, 2, 3, 4, 5} slice := array[1:2] fmt.Println("array : ", array) fmt.Println("slice : ", slice, len(slice), cap(slice)) //슬라이싱은 새로운 배열을 만드는 것이 아닌 기존배열을 활용하는 것임을 인지해야한다. array[1] = 100 fmt.Println("After change second element") fmt.Println("array : ", array) fmt.Println("slice : ", slice, len(slice), cap(slice)) slice = append(slice, 500) fmt.Println("After append 500") fmt.Println("array : ", array) fmt.Println("slice : ", slice, len(slice), cap(slice)) }

슬라이스를 슬라이싱
슬라이싱은 슬라이스 또한 슬라이싱 할 수 있다.

파이썬의 슬라이스와는 상이하다

파이썬에서는 배열의 복사 개념으로 슬라이싱으로 사용한다.
Go에서는 슬라이싱을 하면 새로운 배열을 생성하는 것이 아닌 기존의 값을 슬라이싱 하는 것이다.(가리키는 주소값이 같다.)
전체 슬라이싱(array[:])을 할 경우, 파이썬은 새로운 슬라이스가 생성되지만, Go 는 기존의 슬라이스가 슬라이싱 되는 것이다.

유용한 슬라이싱 기능 - 복사

슬라이스를 그대로 복사하여 새로운 슬라이스를 만들면, 두 슬라이스가 서로 영향을 주지 않습니다.
서로 가리키는 주소값이 다른 슬라이스를 만드는 것이기 때문에 간섭이 없다.
복사 방법에 대해 3가지를 예시로 작성해주셨다.
가독성과 사용자에 따라 활용하면 된다.

복사 예제

package main import ( "fmt" ) func copySlice1() { slice1 := []int{1, 2, 3, 4, 5} slice2 := make([]int, len(slice1)) for i, v := range slice1 { slice2[i] = v } slice2[1] = 100 fmt.Println("slice1", slice1) fmt.Println("slice2", slice2) } func copySlice2() { slice1 := []int{1, 2, 3, 4, 5} slice2 := append([]int{}, slice1...) slice2[1] = 100 fmt.Println("slice1", slice1) fmt.Println("slice2", slice2) } func copySlice3() { slice1 := []int{1, 2, 3, 4, 5} slice2 := make([]int, len(slice1)) copy(slice2, slice1) //copy(목적지, 출발지) //copy 의 리턴값은 복사한 값의 개수이다.(int) slice2[1] = 100 fmt.Println("slice1", slice1) fmt.Println("slice2", slice2) } func main() { copySlice1() copySlice2() copySlice3() }

요소 삭제 예제

package main import "fmt" func main() { delete1() delete2() delete3() } func delete1() { slice := []int{1, 2, 3, 4, 5, 6} idx := 2 for i := idx + 1; i < len(slice); i++ { slice[i-1] = slice[i] } slice = slice[:len(slice)-1] fmt.Println("slice : ", slice) } func delete2() { slice := []int{1, 2, 3, 4, 5, 6} idx := 2 slice = append(slice[:idx], slice[idx+1:]...) fmt.Println("slice : ", slice) } func delete3() { slice := []int{1, 2, 3, 4, 5, 6} idx := 2 copy(slice[idx:], slice[:idx+1]) slice = slice[:len(slice)-1] fmt.Println("slice : ", slice) }

요소 삽입 예제

package main import ( "fmt" ) func main() { add() add2() add3() } func add() { slice := []int{1, 2, 3, 4, 5, 6} slice = append(slice, 0) idx := 2 for i := len(slice) - 2; i >= idx; i-- { slice[i+1] = slice[i] } slice[idx] = 100 fmt.Println(slice) } func add2() { slice := []int{1, 2, 3, 4, 5, 6} idx := 2 slice = append(slice[:idx], append([]int{100}, slice[:idx]...)...) fmt.Println(slice) } func add3() { slice := []int{1, 2, 3, 4, 5, 6} idx := 2 slice = append(slice, 0) copy(slice[idx+1:], slice[idx:]) fmt.Println(slice) }

슬라이스 정렬

Go에서 기본적으로 제공해주는 패키지 "sort"가 있다.
예제를 실행하면 오름차순으로 정렬되는 것을 확인할 수 있다.

package main import ( "fmt" "sort" ) func main() { slice := []int{5, 2, 6, 3, 1, 4} sort.Ints(slice) fmt.Println(slice) }


참조

Tucker의 Go언어 프로그래밍 - Go가 온당 18장(온라인 서당)*
https://www.youtube.com/c/TuckerProgramming/videosTuckerProgramming/videos