array
는 고정된 크기를 갖는다. 반면 slice
는 내부적으로 len
, cap
, 그리고 array
에 대한 포인터로 구성된 구조체로, 편리한 사용을 위한 array
래퍼 구조체로 보면 좋다. Java의 Vector
와 매우 유사하다.slice
는 append
함수로 아이템을 추가하는데, len
과 cap
이 같아지면, cap
을 2배로 키우고, 새로운 array
를 만든다.package main
import (
"fmt"
)
func main() {
slice := make([]int, 0)
for i := 0; i < 10; i++ {
slice = append(slice, i)
fmt.Printf("pointer: %p, cap: %d\n", slice, cap(slice))
}
}
예를 들어, 위의 코드는 아래와 같은 결과를 준다.
pointer: 0xc0000b4020, cap: 1
pointer: 0xc0000b4040, cap: 2 // New array object
pointer: 0xc0000be020, cap: 4 // New array object
pointer: 0xc0000be020, cap: 4
pointer: 0xc0000c0000, cap: 8 // New array object
pointer: 0xc0000c0000, cap: 8
pointer: 0xc0000c0000, cap: 8
pointer: 0xc0000c0000, cap: 8
pointer: 0xc0000b2080, cap: 16 // New array object
pointer: 0xc0000b2080, cap: 16
그러므로, 미리 크기를 알고 있는 경우, array
로 선언하는게 slice
로 해서 append
함수를 쓰는 것 보다 성능 상 이점이 있다고 할 수 있다.
아니면, cap
값을 미리 지정해서 slice
를 생성하면, 당연하겠지만, 새로운 array
를 만들지 않는다. 그러니 전체 길이를 아는 배열을 만든다면, cap
값을 미리 지정해주는 것이 좋다.
package main
import (
"fmt"
)
func main() {
slice := make([]int, 0, 10)
for i := 0; i < 10; i++ {
slice = append(slice, i)
fmt.Printf("pointer: %p, cap: %d\n", slice, cap(slice))
}
}
pointer: 0xc00009e000, cap: 10
pointer: 0xc00009e000, cap: 10
pointer: 0xc00009e000, cap: 10
pointer: 0xc00009e000, cap: 10
pointer: 0xc00009e000, cap: 10
pointer: 0xc00009e000, cap: 10
pointer: 0xc00009e000, cap: 10
pointer: 0xc00009e000, cap: 10
pointer: 0xc00009e000, cap: 10
pointer: 0xc00009e000, cap: 10 // Same array object
slice
가 cap
을 키우며 새로운 array
객체를 만들때는 shallow-copy를 수행한다. 즉, 객체를 통째로 복사하지 않고 포인터 값만 가져온다. immutability를 고려한다면 주의하길.
slice
를 만들 때, len
값을 0보다 큰 값을 넣으면, 일단 Zero value로 넣으며, 그 상태에서 append
를 쓰면 당연히 그 이후의 index
값부터 채워넣는다.
package main
import (
"fmt"
)
func main() {
slice := make([]int, 5)
for i := 0; i < 5; i++ {
slice = append(slice, i)
fmt.Printf("%v\n", slice)
}
}
[0 0 0 0 0 0]
[0 0 0 0 0 0 1]
[0 0 0 0 0 0 1 2]
[0 0 0 0 0 0 1 2 3]
[0 0 0 0 0 0 1 2 3 4]