**배열(array)**은 크기가 타입의 일부로 고정되어 있고, **슬라이스(slice)**는 기반 배열에 대한 유연하고 동적인 크기의 뷰(view)입니다. 실무에서는 거의 항상 slice를 사용합니다 — 배열은 일상적인 Go에서 드뭅니다.
배열 — 고정 크기
var arr [5]int
arr[] =
fmt.Println((arr))
배열 크기는 컴파일 시점에 고정되며 타입의 일부입니다([5]int ≠ [3]int). 배열은 또한 값 타입입니다 — 할당하거나 전달하면 배열 전체가 복사됩니다. 이 경직성 때문에 직접 사용되는 일이 드뭅니다.
s := []int{1, 2, 3} // slice(대괄호 안에 크기 없음)
s = append(s, 4) // 확장 — append는 새 slice를 반환
fmt.Println(len(s), cap(s)) // 길이와 용량
nums := make([]int, 0, 10) // slice 생성: 길이 0, 용량 10(미리 할당)
slice는 고정 크기가 없습니다 — append로 확장합니다. 동적 시퀀스의 기본 도구입니다.
slice는 작은 구조체: { 기반 배열 포인터, 길이, 용량 }
- len = 현재 slice에 있는 요소 수
- cap = slice 시작점부터의 기반 배열 크기
append: len < cap이면 배열 재사용; len == cap이면 더 큰 배열을 할당하고 복사
original := []int{1, 2, 3, 4, 5}
sub := original[1:3] // [2, 3] — 같은 기반 배열에 대한 뷰(VIEW)
sub[0] = 99 // ⚠️ original[1]도 변경됨 → [1, 99, 3, 4, 5]!
슬라이싱(s[low:high])은 복사하지 않습니다 — 같은 기반 배열을 가리키는 새 slice 헤더를 생성합니다. 하나를 수정하면 다른 것에 영향을 줄 수 있어, 미묘한 버그의 흔한 원인입니다. 독립적인 복사본은 copy()를 사용하세요.
a := []int{1, 2, 3}
b := append(a, 4) // 용량에 따라 a의 배열을 공유할 수도, 안 할 수도 있음
// 안전한 패턴: 항상 append가 반환한 slice를 사용
a = append(a, 4)
slice는 Go에서 가장 많이 쓰이면서도 가장 오해받는 기능 중 하나입니다.
배열은 고정 크기의 값 타입(직접 사용은 드묾)인 반면 slice는 기반 배열에 대한 유연하고 참조 같은 뷰(일상의 도구)라는 점을 이해하는 것이 기본입니다.
결정적으로, 기반 배열 공유 동작을 아는 것 — 슬라이싱이 복사가 아닌 뷰를 만들어 수정이 다른 slice에 영향을 줄 수 있고, append가 재할당을 할 수도, 안 할 수도 있다는 점 — 은 숙련된 개발자도 걸려드는 미묘하고 디버깅하기 어려운 앨리어싱 버그를 방지합니다.
slice를 숙달하는 것(len vs cap, append, copy, 공유 함정)은 거의 모든 컬렉션 작업이 이를 사용하므로 필수적인 일상 Go 지식이자 자주 나오는 면접 주제입니다.