배열(Array)
배열이란?
Go의 배열은 동일한 타입의 값을 고정된 개수만큼 연속된 메모리에 저장 하는 자료구조입니다. 선언 시 길이가 타입의 일부로 포함되기 때문에 [3]int와 [5]int는 서로 다른 타입입니다.
대부분의 언어에서 배열은 참조 타입이지만, Go에서 배열은 값 타입(value type) 입니다. 배열을 함수에 전달하거나 다른 변수에 대입하면 전체가 복사됩니다.
package main
import "fmt"
func main() {
// 배열 선언 — 선언 즉시 제로값으로 초기화
var a [3]int // [0 0 0]
a[0] = 10
a[1] = 20
a[2] = 30
fmt.Println(a) // [10 20 30]
// 배열 리터럴
b := [3]int{10, 20, 30}
fmt.Println(b) // [10 20 30]
// 길이를 컴파일러가 자동으로 계산 (... 사용)
c := [...]string{"Go", "Python", "Rust"}
fmt.Println(len(c)) // 3
// 인덱스를 지정한 부분 초기화
d := [5]int{1: 100, 3: 300} // [0 100 0 300 0]
fmt.Println(d)
}
배열의 값 타입 특성
배열이 값 타입이라는 점은 슬라이스와 가장 큰 차이입니다.
package main
import "fmt"
func modifyArray(arr [3]int) {
arr[0] = 999 // 복사본을 수정 — 원본에 영향 없음
}
func modifyArrayPtr(arr *[3]int) {
arr[0] = 999 // 포인터로 전달 — 원본 수정
}
func main() {
original := [3]int{1, 2, 3}
// 값 복사
copied := original
copied[0] = 100
fmt.Println(original) // [1 2 3] — 영향 없음
fmt.Println(copied) // [100 2 3]
// 함수에 값으로 전달
modifyArray(original)
fmt.Println(original) // [1 2 3] — 변경 없음
// 포인터로 전달
modifyArrayPtr(&original)
fmt.Println(original) // [999 2 3] — 변경됨
}
배열 비교
Go 배열은 ==와 != 연산자로 직접 비교할 수 있습니다. 길이와 타입이 같아야 하고, 모든 요소가 동일하면 같은 배열입니다.
package main
import "fmt"
func main() {
a := [3]int{1, 2, 3}
b := [3]int{1, 2, 3}
c := [3]int{1, 2, 4}
fmt.Println(a == b) // true
fmt.Println(a == c) // false
fmt.Println(a != c) // true
// 컴파일 에러: 길이가 다른 배열은 비교 불가
// d := [4]int{1, 2, 3, 4}
// fmt.Println(a == d) // invalid operation
}
배열 순회
for 루프와 range를 사용해 배열을 순회합니다.
package main
import "fmt"
func main() {
scores := [5]int{90, 85, 78, 92, 88}
// 인덱스와 값 모두 사용
for i, v := range scores {
fmt.Printf("scores[%d] = %d\n", i, v)
}
// 인덱스만 필요할 때
for i := range scores {
fmt.Println(i)
}
// 값만 필요할 때 (인덱스 무시)
sum := 0
for _, v := range scores {
sum += v
}
fmt.Printf("합계: %d, 평균: %.1f\n", sum, float64(sum)/float64(len(scores)))
}
다차원 배열
2차원 이상의 배열을 선언할 수 있습니다. 행렬 연산, 게임 보드 등에 활용됩니다.
package main
import "fmt"
func main() {
// 2차원 배열 선언
var matrix [3][3]int
for i := 0; i < 3; i++ {
for j := 0; j < 3; j++ {
matrix[i][j] = i*3 + j + 1
}
}
// [[1 2 3] [4 5 6] [7 8 9]]
fmt.Println(matrix)
// 2차원 배열 리터럴
grid := [3][3]string{
{"X", "O", "X"},
{"O", "X", "O"},
{"X", "O", "X"},
}
for _, row := range grid {
fmt.Println(row)
}
// 3차원 배열 (RGB 이미지 픽셀 표현 예시)
var rgb [2][2][3]uint8
rgb[0][0] = [3]uint8{255, 0, 0} // 빨강
rgb[0][1] = [3]uint8{0, 255, 0} // 초록
rgb[1][0] = [3]uint8{0, 0, 255} // 파랑
rgb[1][1] = [3]uint8{255, 255, 0} // 노랑
fmt.Println(rgb)
}
실전 예제 — 행렬 곱셈
package main
import "fmt"
// 3x3 행렬 곱셈
func matMul(a, b [3][3]int) [3][3]int {
var result [3][3]int
for i := 0; i < 3; i++ {
for j := 0; j < 3; j++ {
for k := 0; k < 3; k++ {
result[i][j] += a[i][k] * b[k][j]
}
}
}
return result
}
func printMatrix(m [3][3]int) {
for _, row := range m {
fmt.Println(row)
}
}
func main() {
a := [3][3]int{
{1, 2, 3},
{4, 5, 6},
{7, 8, 9},
}
b := [3][3]int{
{9, 8, 7},
{6, 5, 4},
{3, 2, 1},
}
fmt.Println("A × B =")
printMatrix(matMul(a, b))
// [30 24 18]
// [84 69 54]
// [138 114 90]
}
배열 vs 슬라이스 — 언제 배열을 쓸까?
| 항목 | 배열 | 슬라이스 |
|---|---|---|
| 크기 | 고정 (컴파일 타임) | 가변 (런타임) |
| 타입 | 크기 포함 ([3]int) | 크기 미포함 ([]int) |
| 복사 | 전체 복사 | 헤더만 복사 (내부 배열 공유) |
| 제로값 | 요소 제로값 | nil |
| 비교 | == 가능 | 불가 (reflect.DeepEqual 사용) |
| 주 용도 | 크기가 확정된 데이터 | 일반적인 동적 컬렉션 |
배열이 적합한 경우:
- SHA-256 해시처럼 크기가 고정된 값 (
[32]byte) - 행렬·벡터 연산
- 배열 자체를 맵의 키로 사용할 때 (슬라이스는 맵 키 불가)
// 배열을 맵 키로 사용 — 슬라이스는 불가
cache := map[[2]int]int{}
cache[[2]int{1, 2}] = 100
cache[[2]int{3, 4}] = 200
fmt.Println(cache) // map[[1 2]:100 [3 4]:200]