배열 선언하기
배열 타입은 [N]T로 씁니다. 길이 다음에 요소 타입이 옵니다. 길이는 컴파일 타임에 고정되며 타입의 일부입니다.
출력:
[90 0 0 0 75] 5
Tue
[5]int [2 3 5 7 11]
["" "b" "" "d" ""]
배열은 항상 전부 초기화됩니다. 값을 지정하지 않은 요소에는 요소 타입의 제로 값(0, "", false, nil)이 들어갑니다.
인덱싱과 범위
인덱스는 0부터 len(a)-1까지입니다. 범위를 벗어난 상수 인덱스는 컴파일 오류이고, 범위를 벗어난 변수 인덱스는 런타임에 패닉을 일으킵니다.
var a [3]int
a[5] = 1 // compile error: invalid argument: index 5 out of bounds [0:3]
i := 5
a[i] = 1 // panic: runtime error: index out of range [5] with length 3
순회하기
for i, v := range a는 요소를 순서대로 방문합니다. v는 요소의 복사본이므로 v에 대입해도 배열은 바뀌지 않습니다. 인덱스를 통해 쓰세요.
배열에만 있는 미묘한 점이 하나 더 있습니다. 배열 값에 대한 range는 시작할 때 배열의 복사본을 평가합니다. 본문에서 뒤쪽 요소를 수정해도 v에는 여전히 이전 값이 보입니다. &a나 a[:]를 순회하면 복사를 피할 수 있습니다.
배열은 값이다
C, Java, JavaScript의 배열과 가장 크게 다른 점입니다. 배열을 대입하면 모든 요소가 복사됩니다. 함수에 넘길 때도 복사됩니다.
출력:
[1 2 3] [99 2 3]
[1 2 3]
[0 2 3]
작은 배열은 복사 비용이 적지만 큰 배열은 비쌉니다. [1_000_000]int를 값으로 넘기면 8메가바이트가 복사됩니다. 배열이 크거나 함수가 배열을 바꿔야 한다면 포인터(*[N]T)나 슬라이스(a[:])를 넘기세요. 배열 포인터로 인덱싱할 때는 *를 명시할 필요가 없습니다. *[3]int에서도 a[0]이 동작합니다.
배열 비교와 맵 키로 쓰기
비교 가능한 요소로 된 배열은 ==와 !=를 지원합니다. 모든 요소가 같으면 두 배열은 같습니다. 그래서 배열은 슬라이스와 달리 맵 키로 쓸 수 있습니다.
길이가 다른 배열은 다른 타입이라서 아예 비교할 수 없습니다. [3]int{} == [4]int{}는 컴파일되지 않습니다(mismatched types [3]int and [4]int).
다차원 배열
배열의 배열은 고정된 격자를 만듭니다. 격자 전체가 연속된 하나의 메모리 블록입니다.
row[:]는 [3]rune 행을 string()이 받을 수 있는 []rune 슬라이스로 바꿉니다.
배열과 슬라이스
배열 [N]T | 슬라이스 []T | |
|---|---|---|
| 길이 | 고정, 타입의 일부 | append로 바뀜 |
| 대입과 전달 | 모든 요소를 복사 | 작은 헤더만 복사, 요소는 공유 |
| 제로 값 | 제로 값 요소 N개 | nil, 길이 0 |
== | 가능, 요소별 비교 | nil과만 비교 가능 |
| 맵 키 | 가능 | 불가능 |
| 주 용도 | 크기가 고정된 데이터: 해시, 좌표, 버퍼 | 거의 나머지 전부 |
실제로 배열을 만나는 곳은 몇 군데뿐입니다. [32]byte를 반환하는 sha256.Sum256, 크기가 변하지 않는 조회 테이블, 그리고 슬라이스의 기반 저장소입니다. 배열을 a[:]나 a[1:3]로 자르면 메모리를 공유하는 슬라이스가 생깁니다. 그 공유가 무엇을 뜻하는지는 슬라이스 페이지에서 다룹니다.
배열과 슬라이스 사이의 변환
슬라이스를 배열(Go 1.20)이나 배열 포인터(Go 1.17)로 변환할 때 슬라이스가 배열 길이보다 짧으면 패닉이 납니다: panic: runtime error: cannot convert slice with length 2 to array or pointer to array with length 3.
흔한 실수
- 함수가 배열 인자를 수정해 주길 기대함. 함수는 복사본을 받습니다.
*[N]T나 슬라이스를 넘기세요. - 슬라이스가 필요한 곳에 배열을 씀.
[]int{1, 2}는 슬라이스이고[2]int{1, 2}는 배열입니다.[]int를 받는 함수는[2]int를 받지 않으므로a[:]를 넘기세요. - range 반복문에서 큰 배열 사용.
for _, v := range bigArray는 먼저 배열 전체를 복사합니다.&bigArray나 슬라이스를 순회하세요.
자주 묻는 질문
Go에서 배열은 어떻게 선언하나요?
요소 타입 앞의 대괄호에 길이를 씁니다: var a [5]int는 0 다섯 개를 만듭니다. 값과 함께 쓰려면 a := [3]string{"x", "y", "z"}. 개수를 컴파일러에 맡기려면 [...]를 씁니다: a := [...]int{1, 2, 3}의 타입은 [3]int입니다.
Go에서 배열과 슬라이스의 차이는 무엇인가요?
배열은 길이가 고정되어 있고 그 길이가 타입의 일부이며([3]int와 [4]int는 서로 다른 타입), 대입하거나 넘길 때 복사됩니다. 슬라이스([]int)는 기반 배열을 바라보는 뷰로, append로 길이가 바뀔 수 있고, 슬라이스를 복사하면 요소가 아니라 뷰만 복사됩니다. 대부분의 Go 코드는 슬라이스를 씁니다.
Go에서 배열의 길이는 어떻게 구하나요?
내장 함수 len(a)를 씁니다. 배열에서는 컴파일 타임 상수이므로 상수 표현식에 쓸 수 있습니다. cap(a)도 같은 값을 반환합니다.
Go에서 배열을 비교할 수 있나요?
요소 타입이 비교 가능하다면 ==와 !=로 비교할 수 있습니다. 모든 요소가 같으면 두 배열은 같습니다. 배열은 맵 키로도 쓸 수 있습니다. 슬라이스는 ==로 비교할 수 없으므로(nil과의 비교는 예외) slices.Equal을 쓰세요.