Table of Contents
Golang - 배열과 슬라이스 사용하기
배열
Go에서 배열은 거의 사용되지 않는다. 왜냐하면 Go에서는 타입을 컴파일 과정에서 반드시 해석되어야 하기 때문에 배열의 크기를 지정하기 위해 변수를 사용할 수 없고, 동일한 타입을 가진 다른 크기의 배열 간에 타입 변환을 시도할 수 없기 때문이다.
이런 제약들 때문에 정확히 배열의 크기를 아는 경우가 아니라면 배열을 사용하지 않는다.
배열 선언
배열의 모든 요소는 지정된 타입이어야 한다. 배열을 지정할 때 값을 초기화하지 않으면 정수 0으로 초기화된다.
1var x [3]int
2
3// 초기값 주기
4var x = [3]int{10, 20, 30}
5
6// 배열 리터럴로 배열 초기화
7var x = [...]int{10, 20, 30}
희소 배열 (대부분의 요소 값이 0으로 설정된 배열)을 만드려면 다음과 같이 사용한다.
1// [1, 0, 0, 4, 6, 0, 100, 15]
2var x = [12]int{1, 3:4, 6, 6:100, 15}
다차원 배열 (배열의 요소로 다른 배열을 포함하는 배열)은 다음과 같이 선언한다.
1var x [2][3]int
배열 응용
==
과 !=
를 사용하여 배열 간의 비교가 가능하다.
1var x = [...]int{10, 20, 30}
2var y = [3]int{10, 20, 30}
3fmt.Println(x == y) // true
대부분의 언어와 마찬가지로 괄호를 이용해 값을 읽거나 쓸 수 있다.
1x[0] = 10
2fmt.Println(x[2])
3
4// 배열의 길이
5fmt.Println(len(x))
배열의 끝을 넘어서거나 음수의 인덱스를 사용하여 값을 읽거나 쓸 수 없다.
슬라이스
일련의 값을 가지고 있는 자료구조를 구성할 때는 배열의 제약을 제거한 슬라이스를 주로 사용한다. 슬라이스의 크기는 해당 타입의 일부가 아니기 때문이다. → 슬라이스 크기를 지정할 필요가 없다는 것을 뜻한다.
보통 슬라이스가 커질 일이 없다면 var 선언을 사용하고, 슬라이스가 커져야 하지만 어떤 값인지 정확히 알 수 없으면 make를 사용하여 선언한다.
슬라이스 선언
배열 선언과 비슷하지만 가장 큰 차이점은 슬라이스의 크기를 지정할 필요가 없다.
1var x = []int{10, 20, 30}
2
3// [1, 0, 0, 4, 6, 0, 100, 15]
4var x = []int{1, 3:4, 6, 6:100, 15}
5
6// 다차원 슬라이스
7var x [][]int
배열과의 차이점
1var x []int // nil
2
3fmt.Println(x == nil) // true
정수 슬라이스에서 값을 할당하지 않으면 x에는 nil이라는 제로 값으로 할당된다. Go에서 nil은 타입의 부재를 표현한 식별자이다. 슬라이스가 nil 이라는 것은 어떤 요소도 가지고 있지 않다는 뜻이다.
슬라이스는 비교가 불가능한 타입으로, 두 슬라이스를 비교하기 위해 ==
나 =!
를 사용하면 컴파일 오류가 발생한다. 슬라이스는 오로지 nil로만 비교가 가능하다.
슬라이스 응용
배열과 같이 크기를 넘어서거나 음수의 인덱스로는 읽기와 쓰기가 불가능하다.
1x[0] = 10
2fmt.Println(x[2])
슬라이스에서 len()
, append()
같은 내장 함수들을 사용할 수 있다.
1var x []int
2
3fmt.Println(len(x)) // 0
4
5x = append(x, 10)
6fmt.Println(x) // [10]
7
8x = append(x, 20, 30, 40)
9fmt.Println(x) // [10, 20, 30, 40]
10
11y := []int{20, 30, 40}
12x = append(x, y...)
슬라이스는 수용력(예약된 메모리 공간의 크기)을 가지는데, 슬라이스의 길이와 수용력이 같아진 시점에 값을 추가하면 append 는 더 큰 수용력을 가지는 슬라이스를 할당한다.
1var x []int
2fmt.Println(x, cap(x)) // 수용력 크기 출력
선택적으로 수용력을 지정하여 슬라이스를 만들 수 있다.
1x := make([]int, 5) // 길이 5, 수용력 5를 가지는 정수 슬라이스 생성
2x := make([]int, 5, 10) // 길이 5, 수용력 10을 가지는 정수 슬라이스 생성
3
4x = append(x, 10) // [0, 0, 0, 0, 0, 10]
x[0] 부터 x[4]까지 접근 가능한 요소이며, 모두 0으로 초기화된다.
슬라이스 연산자를 사용하여 슬라이스에서 슬라이스를 만들 수 있다.
1a := []int{1, 2, 3, 4} // [1, 2, 3, 4]
2b := x[:2] // [1, 2]
3c := x[1:] // [2, 3, 4]
4d := x[1:3] // [2, 3]
5e := x[:] // [1, 2, 3, 4]
슬라이스에서 슬라이스를 가져오면 데이터를 복사하지 않고 메모리를 공유하는 두 개의 변수를 가지게 된다. 이는 슬라이스의 요소를 변경하면 모든 슬라이스에 영향이 생긴다는 것을 의미한다.
슬라이스끼리 영향이 생기는 것을 막기 위해서는 하위 슬라이스에 append 를 사용하지 않거나 full slice expression을 사용해야 한다.
1x := make([]int, 0, 5)
2x = append(x, 1, 2, 3, 4)
3y := x[:2:2]
4z := x[2:4:4]
5
6// append 사용
7y = append(y, 30, 40, 50) // x = [1, 2, 3, 4, 60]
8x = append(x, 60) // y = [1, 2, 30, 40, 50]
9z = append(z, 70) // z = [3, 4, 70]
완전한 슬라이스 연산(full slice expression)은 부모 슬라이스에서 파생된 하위 슬라이스에 얼마나 많은 메모리를 공유할 것인지 명확하게 해준다. 세번째 인자에는 부모 슬라이스의 수용력의 마지막 위치를 지정한다.
하위 슬라이스의 수용력은 세번째 인자 - 시작 오프셋
이 된다.
배열을 슬라이스로 만들수도 있다.
1x := [4]int{5, 6, 7, 8} // [10, 6, 7, 8]
2y := x[:2] // [10, 6]
3z := x[2:] // [7, 8]
4x[0] = 10
배열을 슬라이스로 만들 때도 슬라이스에서 슬라이스를 만들 때와 마찬가지로 메모리를 서로 공유한다.
원본 슬라이스로부터 독립적인 슬라이스를 만드려면 copy를 사용할 수도 있다.
1x := []int{1, 2, 3, 4}
2y := make([]int, 4}
3num := copy(y, x)
4fmt.Println(y, num) // [1, 2, 3, 4] 4
5
6a := []int{1, 2, 3, 4}
7b := make([]int, 2}
8num2 := copy(b, a)
9fmt.Println(b, num2) // [1, 2] 2
copy의 첫 번째 파라미터는 대상 슬라이스이고, 두 번째 파라미터는 원본 슬라이스이다. 길이를 기준으로 원본 슬라이스에서 대상 슬라이스로 값을 복사하고 실제 복사된 요소의 개수를 반환한다.