배열과 슬라이스, 뭐가 다를까?
1. 배열 (Array): 고정된 약속
Go 언어에서 배열은 한 번 선언되면 크기가 절대 변하지 않는 데이터 묶음입니다. 즉, 배열을 만들 때 몇 개의 요소를 담을지 미리 정하고, 그 개수는 프로그램이 끝날 때까지 바뀌지 않아요.
예를 들어, var scores [5]int라고 선언하면, scores라는 배열은 무조건 int 타입의 숫자 5개만 담을 수 있습니다. 6번째 숫자를 넣고 싶어도 공간이 없어서 불가능하죠.
package main
import "fmt"
func main() {
var numbers [3]int // int 타입 3개를 담을 수 있는 배열 선언
numbers[0] = 10
numbers[1] = 20
numbers[2] = 30
// numbers[3] = 40 // Error! 배열의 크기를 벗어남
fmt.Println("배열:", numbers) // 배열: [10 20 30]
fmt.Printf("배열의 타입: %T\n", numbers) // 배열의 타입: [3]int
}
위 코드에서 보듯이, [3]int는 [4]int와는 아예 다른 타입으로 간주됩니다. 크기가 곧 타입의 일부가 되는 거죠. 이런 특성 때문에 배열은 특정 크기가 고정되어야 하는 상황(예: 이미지의 RGB 값 묶음, 암호화 블록 등)이 아니면 일반적인 프로그래밍에서는 자주 사용되지 않습니다.
2. 슬라이스 (Slice): 유연한 참조
반면에 슬라이스는 동적으로 크기가 변할 수 있는 데이터 묶음입니다. 이름만 들으면 "동적 배열" 같지만, 사실 슬라이스는 배열의 일부를 참조하는(가리키는) 개념입니다. 내부적으로는 배열을 사용하지만, 우리는 슬라이스를 통해 더 유연하게 데이터를 다룰 수 있죠.
package main
import "fmt"
func main() {
// 슬라이스 리터럴로 선언 및 초기화
mySlice := []string{"apple", "banana", "cherry"}
fmt.Println("슬라이스:", mySlice) // 슬라이스: [apple banana cherry]
fmt.Printf("슬라이스의 타입: %T\n", mySlice) // 슬라이스의 타입: []string
// 요소 추가 (append 함수 사용)
mySlice = append(mySlice, "date")
fmt.Println("추가 후 슬라이스:", mySlice) // 추가 후 슬라이스: [apple banana cherry date]
}
보시다시피, 슬라이스는 선언할 때 길이를 명시하지 않고 []타입 형태로 선언합니다. 그리고 append() 함수를 사용해 얼마든지 요소를 추가할 수 있어요. 이것이 배열과의 결정적인 차이점이죠!
슬라이스를 더 깊이 파헤치기: 길이, 용량, 그리고 make()
슬라이스를 제대로 이해하려면 **길이(Length)**와 **용량(Capacity)**이라는 두 가지 중요한 개념을 알아야 합니다.
- 길이 (Length): 현재 슬라이스가 가지고 있는 요소의 개수입니다.
len()함수로 확인할 수 있습니다. - 용량 (Capacity): 슬라이스가 참조하고 있는 **기저 배열(underlying array)**이 최대로 수용할 수 있는 요소의 개수입니다.
cap()함수로 확인할 수 있습니다. 즉, 슬라이스가 현재 가지고 있는 요소 외에 더 추가할 수 있는 공간의 총량이죠.
make() 함수로 슬라이스 생성하기
슬라이스는 주로 make() 함수를 사용해서 생성합니다. make()는 슬라이스의 초기 길이와 용량을 지정할 수 있게 해줍니다.
make([]Type, length, capacity) 형태로 사용하며, capacity는 생략 가능합니다. 생략하면 length와 동일하게 설정됩니다.
package main
import "fmt"
func main() {
// 길이 5, 용량 5인 슬라이스 생성 (초기값은 int의 제로 값인 0)
s1 := make([]int, 5)
fmt.Printf("s1: %v, 길이: %d, 용량: %d\n", s1, len(s1), cap(s1))
// s1: [0 0 0 0 0], 길이: 5, 용량: 5
// 길이 0, 용량 10인 슬라이스 생성
s2 := make([]string, 0, 10)
fmt.Printf("s2: %v, 길이: %d, 용량: %d\n", s2, len(s2), cap(s2))
// s2: [], 길이: 0, 용량: 10
// s2에 요소 추가
s2 = append(s2, "Go", "Lang", "Programming")
fmt.Printf("s2 (append 후): %v, 길이: %d, 용량: %d\n", s2, len(s2), cap(s2))
// s2 (append 후): [Go Lang Programming], 길이: 3, 용량: 10
}
s2의 예시를 보면, 처음에는 길이가 0이었지만 용량이 10이었기 때문에 append를 통해 3개의 요소를 추가해도 용량 범위 내에서 길이만 늘어났음을 알 수 있습니다.
append()와 용량 확장
append() 함수로 슬라이스에 요소를 추가할 때, 현재 용량이 부족하면 Go 런타임은 자동으로 더 큰 새 기저 배열을 만들고 기존 요소를 새 배열로 복사한 다음, 새 요소를 추가합니다. 그리고 슬라이스는 이 새로운 기저 배열을 참조하게 됩니다.
이 과정에서 일반적으로 기존 용량의 2배로 용량을 확장하는 전략을 사용하므로, 잦은 append()가 발생하더라도 성능 저하를 최소화할 수 있습니다. 하지만 매우 많은 요소를 한 번에 추가해야 할 때는 미리 충분한 용량을 확보해두는 것이 좋습니다.
슬라이싱(Slicing): 슬라이스에서 슬라이스 만들기
이미 존재하는 배열이나 슬라이스의 일부분을 잘라내어 새로운 슬라이스를 만들 수 있는데, 이를 슬라이싱이라고 합니다.
[low:high] 또는 [low:high:capacity] 형태로 사용합니다.
low: 시작 인덱스 (포함)high: 끝 인덱스 (포함하지 않음)capacity(선택 사항): 생성될 슬라이스의 용량을 지정
package main
import "fmt"
func main() {
// 기존 배열에서 슬라이스 만들기
array := [6]int{10, 20, 30, 40, 50, 60}
s := array[1:4] // 인덱스 1부터 4 전까지 (20, 30, 40)
fmt.Printf("s: %v, 길이: %d, 용량: %d\n", s, len(s), cap(s))
// s: [20 30 40], 길이: 3, 용량: 5 (array[1]부터 array의 끝까지의 공간)
// 기존 슬라이스에서 슬라이스 만들기
original := []string{"A", "B", "C", "D", "E"}
sub := original[2:5] // 인덱스 2부터 5 전까지 (C, D, E)
fmt.Printf("sub: %v, 길이: %d, 용량: %d\n", sub, len(sub), cap(sub))
// sub: [C D E], 길이: 3, 용량: 3 (original[2]부터 original의 끝까지의 공간)
// 원본 변경 시 슬라이스 영향
original[2] = "X"
fmt.Println("original 변경 후 sub:", sub) // original 변경 후 sub: [X D E]
}
마지막 예시를 보면 original 슬라이스의 요소를 변경하니 sub 슬라이스도 함께 변경되는 것을 볼 수 있습니다. 이는 슬라이스가 참조 타입이기 때문에 원본 배열/슬라이스의 데이터를 공유하기 때문입니다. 이 점을 꼭 기억해야 예상치 못한 버그를 줄일 수 있습니다!
그래서 언제 무엇을 써야 할까?
Go 언어에서 데이터를 묶어 다룰 때는 대부분의 경우 슬라이스를 사용하세요.
- 슬라이스: 크기가 가변적이어서 요소를 추가하거나 삭제하는 등 유연한 작업이 필요할 때. 네트워크 통신으로 받은 데이터, 사용자 입력 목록 등 크기를 예측하기 어려운 경우에 최적입니다.
- 배열: 크기가 절대 변하지 않고 고정되어야 하는 매우 드문 경우에만 고려하세요. (예: 행렬 연산, 특정 하드웨어 인터페이스 등)
'Go언어' 카테고리의 다른 글
| Go의 고루틴, Kotlin의 코루틴, 그리고 Java 21의 가상 스레드 (2) | 2025.08.04 |
|---|---|
| Go언어, 동시성 철학: “공유 메모리가 아닌, 통신으로 메모리를 공유하라” (3) | 2025.08.03 |
| Go언어, 코루틴, 채널, Context 알아보기 (5) | 2025.08.03 |
| Go언어, 인터페이스 (3) | 2025.08.02 |
| Go언어, 메서드와 함수 (2) | 2025.08.02 |