Menu

Golang 배열: 고정 길이, 값 의미론, 슬라이스와의 차이

Go 배열은 길이가 고정되어 있고 그 길이가 타입의 일부이며, 대입할 때 복사됩니다. 배열을 선언하고, 순회하고, 비교하고, 넘기는 방법과 대부분의 Go 코드가 대신 슬라이스를 쓰는 이유를 알아봅니다.

이 페이지에는 실행 가능한 에디터가 있습니다 - 편집하고 실행하면 결과를 바로 볼 수 있습니다.

배열 선언하기

배열 타입은 [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에는 여전히 이전 값이 보입니다. &aa[:]를 순회하면 복사를 피할 수 있습니다.

배열은 값이다

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을 쓰세요.

Coddy programming languages illustration

Coddy로 코딩 배우기

시작하기