var slice []int // 길이가 0인 슬라이스 생성슬라이스를 초기화 하는 방법
var slice1 = []int{1, 2, 3} //[1 2 3]
var slice1 = []int{1, 5:2, 10:3} //[1 0 0 0 2 0 0 0 0 3] - make()를 사용한 초기화
var slice = make([]int, 3) //길이가 3인 int 슬라이스를 갖는다. [0 0 0]
### 18.1.2 슬라이스 요소 접근
- 배열과 같음
### 18.1.3 슬라이스 순회
- 배열과 같음
### 18.1.4 슬라이스 요소 추가 - append() (가장 많이 사용하는듯?)
- 배열은 길이를 늘릴 수 없지만 슬라이스는 요소를 추가해 길이를 늘릴 수 있다.
```go
func Test_append(t *testing.T) {
arr := []int{1, 2, 3, 4, 5}
arr2 := append(arr, 6)
t.Log(arr, arr2) //[1 2 3 4 5] [1 2 3 4 5 6]
}
append()를 이용해 여러 값을 추가할 수 있다.
func Test_appendMoreThanTwo(t *testing.T) {
arr := []int{1, 2, 3, 4, 5}
arr2 := []int{6, 7, 8, 9, 10}
arrAppend := append(arr, arr2...)
t.Log(arrAppend) //[1 2 3 4 5 6 7 8 9 10]
}
슬라이스는 내장 타입으로 내부 구현이 감춰져 있지만 reflect 패키지의 SliceHeader 구조체를 사용해 내부 구현을 살펴볼 수 있다.
type SliceHeader struct {
Data uintptr
Len int
Cap int
}
func Test_SliceHeader(t testing.T) {
arr := []int{1, 2, 3, 4, 5}
header := (reflect.SliceHeader)(unsafe.Pointer(&arr))
log.Println(header.Data, header.Len, header.Cap)
}
슬라이스 구현은 배열을 가리키는 포인터와 요소 개수를 나타내는 len, 전체 배열 길이를 나타내는 cap 필드로 구성된 구조체다.
슬라이스 변수 대입 시 배열에 비해 사용되는 메모리나 속도에 이점이 있음.
make() 함수를 사용해 슬라이스를 만들 때 인수를 2개 혹은 3개를 넣는다.
func Test_makeSlice(t testing.T) {
arr := make([]int, 3)
header := (reflect.SliceHeader)(unsafe.Pointer(&arr))
log.Println(header.Data, header.Len, header.Cap) //1374393540056 3 3
}
func Test_makeSlice2(t testing.T) {
arr := make([]int, 3, 5)
header := (reflect.SliceHeader)(unsafe.Pointer(&arr))
log.Println(header.Data, header.Len, header.Cap) //1374396415432 3 5
}
슬라이스 내부 구현이 배열과 다르기 때문에 동작도 매우 다르다.
func Test_betweenSliceAndArray(t *testing.T) {
arr := [5]int{1, 2, 3, 4, 5}
slice := []int{1, 2, 3, 4, 5}
func(arr [5]int) {
arr[2] = 10
}(arr)
func(slice []int) {
slice[2] = 10
}(slice)
log.Println(arr) //[1 2 3 4 5]
log.Println(slice) //[1 2 10 4 5]
}
Go 언어에서는 모든 값의 대입은 복사로 일어난다.
Arr는 모든 배열값이 복사된다.
Slice는 실제 배열은 복사되지 않고, SliceHeader가 복사된다.
남은 빈 공간 = cap - len
배열의 일부를 집어내는 기능
array[startIdx:endIdx]
startIdx, endIdx 생략 가능
startIdx <= slice < endIdx
슬라이싱을 하면 배열 일부를 가리키는 슬라이스를 반환한다.
새로운 배열이 만들어지는게 아니라 배열의 일부를 포인터로 가리키는 슬라이스를 만들어 냄.
func Test_SlicingPointer(t *testing.T) {
arr := []int{1, 2, 3, 4, 5}
slice := arr[1:3]
arr[1] = 10
log.Println(slice) //[10 3]
slice[1] = 100
log.Println(arr) //[1 10 100 4 5]
}
같은 포인터를 공유하고 있기 때문에 위와 같은 결과가 나온다. 사용시 주의가 필요!
func Test_SlicingCapLen(t testing.T) {
arr := []int{1, 2, 3, 4, 5}
slice := arr[1:2]
log.Println(len(arr), cap(arr)) //5 5
log.Println(len(slice), cap(slice))//1 4
slice = arr[2:3]
log.Println(len(arr), cap(arr)) //5 5
log.Println(len(slice), cap(slice)) //1 3
}
인덱스 1이상 2미만의 슬라이스를 반환
len 1의 요소가 하나인 슬라이스
cap은 배열의 총길이에서 시작 인덱스를 뺀 만큼 가지게 된다.
cap 5 - 1 == 4
slicing한 슬라이스에 append()를 하면?
func Test_SlicingAppend(t testing.T) {
arr := []int{1, 2, 3, 4, 5}
slice := arr[1:2]
slice = append(slice, 10)
t.Log(arr) //[1 2 10 4 5]
t.Log(slice) //[2 10]
}
func Test_SlicedSliceSlicing(t *testing.T) {
arr := []int{1, 2, 3, 4, 5}
slice := arr[1:3] //[2 3]
slice1 := slice[1:2] //[3]
t.Log(slice1) //[3]
}
처음부터 슬라이싱
slice2 := slice1[0:3]
slice2 := slice1[:3]
끝까지 슬라이싱
slice2 := slice1[3:len(slice1)]
slice2 := slice1[3:]
전체 슬라이싱
slice2 := slice1[0:len(slice1)]
slice2 := slice1[:]
인덱스 3개로 슬라이싱해 cap 크기 조절
slice[시작인덱스:끝인덱스:최대인덱스]
func Test_SlicingThreeIndex(t *testing.T) {
arr := []int{1, 2, 3, 4, 5}
slice := arr[1:3:4]
t.Log(slice) //[2 3]
t.Log(len(slice), cap(slice)) //2 3
}
18.4 유용한 슬라이싱 기능 활용
18.4.1 슬라이스 복제
슬라이스 복제는 몇가지 방법이 있다.
For문 사용
func Test_SliceCopyByFor(t *testing.T) {
arr := []int{1, 2, 3, 4, 5}
slice := make([]int, 5, 5)
for i := 0; i < len(arr); i++ {
slice[i] = arr[i]
}
arr = nil
t.Log(arr) //[]
t.Log(slice) //[1 2 3 4 5]
}
append() 내장함수 사용
func Test_SliceCopyByAppend(t *testing.T) {
arr := []int{1, 2, 3, 4, 5}
var slice []int
slice = append(slice, arr...)
arr = nil
t.Log(arr) //[]
t.Log(slice) //[1 2 3 4 5]
}
copy() 내장함수 사용
func Test_SliceCopy(t *testing.T) {
arr := []int{1, 2, 3, 4, 5}
slice := make([]int, 5, 5)
copy(slice, arr)
arr = nil
t.Log(arr) //[]
t.Log(slice) //[1 2 3 4 5]
}
요소 삭제도 여러 방법이 있으나 제일 간단한 방법 몇가지만 적겠다.
//[]int{1,2,3,4,5}의 2번 인덱스에 10추가하는 경우
func Test_SliceAddElementByAppend(t *testing.T) {
arr := []int{1, 2, 3, 4, 5}
targetIndex := 2
var slice []int
slice = append(arr[:targetIndex], append([]int{10}, arr[targetIndex:]...)...)
t.Log(slice) //[1 2 10 3 4 5]
}
func Test_SliceAddElementByCopy(t *testing.T) {
arr := []int{1, 2, 3, 4, 5}
targetIndex := 2
slice := make([]int, len(arr)+1, cap(arr)+1)
copy(slice[:targetIndex], arr[:targetIndex])
copy(slice[targetIndex+1:], arr[targetIndex:])
slice[targetIndex] = 10
t.Log(slice) //[1 2 10 3 4 5]
}
간단하게 sort package 사용으로 정렬이 가능하다.
func Test_sortSlice(t *testing.T) {
ints := []int{5, 1, 2, 3, 4}
strings := []string{"나", "라", "다", "가"}
sort.Ints(ints)
sort.Strings(strings)
t.Log(ints)
t.Log(strings)
}
sort 패키지의 Sort()함수를 사용하기 위해서는 Len(), Less(), Swap() 세 메서드가 필요하다.
//나이 오름차순, 나이 같을경우 이름 내림차순
type User struct {
Name string
Age int
}
type Users []User
func (u Users) Len() int { return len(u) }
func (u Users) Less(i, j int) bool {
if u[i].Age < u[j].Age {
return true
}
return u[i].Name > u[j].Name
}
func (u Users) Swap(i, j int) { u[i], u[j] = u[j], u[i] }
func Test_sortStructBySort(t testing.T) {
users := []User{
{"Coen", 30},
{"Alice", 20},
{"Bob", 30},
}
sort.Sort(Users(users))
t.Log(users) //[{Alice 20} {Coen 30} {Bob 30}]
}
golang 1.8 이후부터는 더 간단하게 정렬이 가능하다.
//나이 오름차순, 나이 같을경우 이름 내림차순
func Test_sortStructBySlice(t testing.T) {
type User struct {
Name string
Age int
}
users := []User{
{"Coen", 34},
{"Alice", 20},
{"Bob", 30},
}
sort.Slice(users, func(i, j int) bool {
return users[i].Age < users[j].Age
})
t.Log(users) //[{Alice 20} {Coen 30} {Bob 30}]
}