Go - 배열, 슬라이스

Go는 배열과 슬라이스 2개의 자료형을 제공한다. 


슬라이스

배열은 한 번 초기화된 길이를 수정할 수 없다. 

슬라이스{포인터, len, cap} 3가지 값을 가진다. 

  • 포인터: 실제 배열이 저장되어 있는 주소 값을 가리키는 포인터
  • len: (length) 실제 데이터가 저장된 길이
  • cap: (capacity) 데이터를 저장할 수 있는 최대 길이

만약 cap이 5이고 len이 3인 슬라이스가 있다면 아래와 같은 형태이다. 

x x x    

슬라이스에 내용을 추가할 때, cap에 빈자리가 있으면 빈자리에 내용을 채우고, 그렇지 않으면 새로운 배열을 생성한 후 가리킨다. 자세한 내용은 아래에 기록하였다. 


초기화

// 배열
arr := [...]int{1, 2, 3}
arr := [3]int{1, 2, 3}

// 슬라이스
slice := []int{1, 2, 3}  // len: 3, cap: 3
slice := make([]int, 3)  // len: 3, cap: 3
slice := make([]int, 3, 5)  // len: 3, cap: 5

배열은 [길이]를 명시해주거나 [...]을 이용해 자동으로 지정해 준다. 반면 슬라이스는 []과 같이 작성해 생성한다. 이때 cap을 지정하지 않으면 len과 동일하게 초기화된다. 

slice := make([]int, 3)
fmt.Println(len(slice), cap(slice))  // 3 3

len()함수와 cap() 함수를 통해 확인할 수 있다.


인덱싱 및 슬라이싱

arr := [...]int{1, 2, 3}

arr[1]  // 2
arr[:1]  // {1, 2}
arr[:]  // {1, 2, 3}

배열과 슬라이스 모두 [ ]을 이용해 요소를 가져올 수 있다.


배열 & 슬라이스 관계

func main() {
    // 슬라이스는 포인터를 전달
    array := [2]int{0, 1}
    slice := []int{0, 1}
    ModifyArr(array)
    ModifySlice(slice)
    
    fmt.Println(array)  // [0 1]
    fmt.Println(slice)  // [999 1]
}

func ModifyArr(arr [2]int) {
    arr[0] = 999
}

func ModifySlice(slice []int) {
    slice[0] = 999
}

파라미터로 전달할 때, 배열은 복사하고 슬라이스는 참조한다. 앞에서 봤듯 슬라이스는 배열의 포인터 객체이다. 따라서 arr은 main과 ModifyArr에서 다른 배열을 사용하고 있고, slice는 main과 ModifySlice에서 같은 슬라이스를 사용하고 있다. 

 

array := [...]int{0, 3, 5, 5, 1}
slice := array1[:3]

array1[0] = 1000

fmt.Println(array1)  // [1000 3 5 5 1]
fmt.Println(slice1)  // [1000 3 5]
fmt.Println(len(slice1), cap(slice1))  // 3 5

배열을 수정하였는데 슬라이스까지 변경된 것을 볼 수 있다. 슬라이스를 배열로부터 가져올 경우, 복사가 아닌 참조를 수행한다. 


append

append(추가할 배열, 추가할 값...)

append는 슬라이스에 값을 추가한다. 

slice := []int{1, 2, 3}
slice = append(slice, 99, 77)
fmt.Println(slice) // [1 2 3 99 77]

복사

앞에서 봤듯 [:]의 형태로 슬라이싱 한다고 실제 복사가 이루어지지는 않는다. 

// 1
newSlice := append([]int{}, arr...)

// 2
newSlice := make([]int{})
copy(newSlice, arr)

따라서 새로운 슬라이스를 생성하고 값을 모두 수정하는 형식으로 복사해야 한다. 


삭제

func main() {
    slice := []int{1, 2, 3, 4, 5}
    slice = delete(slice, 3)
    fmt.Println(slice) // [1 2 3 5]
}

func delete(slice []int, index int) []int {
    slice = append(slice[:index], slice[index+1:]...)
    return slice
}

삭제할 값을 제외한 두 슬라이스를 append하는 형식으로 처리할 수 있다. 


삽입

func main() {
    slice := []int{1, 2, 3, 4, 5}
    slice = insert(slice, 3, 99)
    fmt.Println(slice) // [1 2 3 99 4 5]
}

func insert(slice []int, index int, value int) []int {
    slice = append(slice, 0)
    copy(slice[index+1:], slice[index:])
    slice[index] = value
    return slice
}

중간에 내용을 삽입하기 위해 슬라이스에 공간을 생성한 후 해당 인덱스의 내용을 수정하는 방식으로 구현하였다.