본문으로 건너뛰기

슬라이스(Slice)

슬라이스란?

슬라이스는 Go에서 가장 많이 사용하는 자료구조입니다. 고정 길이인 배열과 달리 길이가 동적으로 늘어나는 가변 길이 시퀀스 입니다.

슬라이스의 내부 구조는 세 필드로 이루어진 헤더입니다.

슬라이스 헤더 (24 bytes on 64-bit)
┌──────────┬────────┬─────────┐
│ ptr │ len │ cap │
│ (8 byte) │(8 byte)│ (8 byte)│
└──────────┴────────┴─────────┘


┌────┬────┬────┬────┬────┐
│ 0 │ 1 │ 2 │ 3 │ 4 │ ← 내부 배열 (힙에 할당)
└────┴────┴────┴────┴────┘
  • ptr: 내부 배열의 첫 번째 요소를 가리키는 포인터
  • len: 현재 사용 중인 요소 수
  • cap: 내부 배열의 총 용량 (재할당 없이 담을 수 있는 최대 요소 수)

슬라이스 선언과 초기화

package main

import "fmt"

func main() {
// 1. nil 슬라이스 (len=0, cap=0, ptr=nil)
var s1 []int
fmt.Println(s1, len(s1), cap(s1)) // [] 0 0
fmt.Println(s1 == nil) // true

// 2. 리터럴 초기화
s2 := []int{1, 2, 3, 4, 5}
fmt.Println(s2, len(s2), cap(s2)) // [1 2 3 4 5] 5 5

// 3. make(타입, len, cap) — cap 생략 시 len과 동일
s3 := make([]int, 3, 10)
fmt.Println(s3, len(s3), cap(s3)) // [0 0 0] 3 10

// 4. 배열로부터 슬라이싱
arr := [5]int{10, 20, 30, 40, 50}
s4 := arr[1:4] // 인덱스 1~3 (4 미포함)
fmt.Println(s4, len(s4), cap(s4)) // [20 30 40] 3 4
}

슬라이싱 표현식

s[low:high:max] 형식으로 슬라이스를 생성합니다.

package main

import "fmt"

func main() {
s := []int{0, 1, 2, 3, 4, 5, 6, 7, 8, 9}

// s[low:high] — len=high-low, cap=len(s)-low
a := s[2:5]
fmt.Printf("a=%v len=%d cap=%d\n", a, len(a), cap(a))
// a=[2 3 4] len=3 cap=8

// s[low:high:max] — cap=max-low (3인자 슬라이싱)
b := s[2:5:6]
fmt.Printf("b=%v len=%d cap=%d\n", b, len(b), cap(b))
// b=[2 3 4] len=3 cap=4

// 생략 표현
fmt.Println(s[:3]) // [0 1 2] — s[0:3]과 동일
fmt.Println(s[7:]) // [7 8 9] — s[7:len(s)]와 동일
fmt.Println(s[:]) // [0 1 2 ... 9] — s[0:len(s)]와 동일
}

append와 자동 재할당

append는 슬라이스에 요소를 추가합니다. cap을 초과하면 새 배열을 할당하고 기존 데이터를 복사 합니다.

package main

import "fmt"

func main() {
s := make([]int, 0, 4)
fmt.Printf("초기: %v len=%d cap=%d ptr=%p\n", s, len(s), cap(s), s)

for i := 1; i <= 8; i++ {
prevCap := cap(s)
prevPtr := fmt.Sprintf("%p", s)
s = append(s, i)
if cap(s) != prevCap {
fmt.Printf("재할당! cap %d→%d, ptr %s→%p\n", prevCap, cap(s), prevPtr, s)
}
}
fmt.Println(s)
// 재할당! cap 4→8, ptr ...→...
// [1 2 3 4 5 6 7 8]

// 여러 요소 한 번에 추가
a := []int{1, 2, 3}
b := []int{4, 5, 6}
a = append(a, b...) // 스프레드 연산자
fmt.Println(a) // [1 2 3 4 5 6]
}

copy — 독립적인 복사본 만들기

copy(dst, src)는 슬라이스를 독립적으로 복사합니다. min(len(dst), len(src)) 개수만큼 복사됩니다.

package main

import "fmt"

func main() {
src := []int{1, 2, 3, 4, 5}

// 단순 대입은 같은 내부 배열을 공유
shared := src
shared[0] = 999
fmt.Println(src) // [999 2 3 4 5] — 원본도 변경됨!

// copy로 독립적인 복사본 생성
src2 := []int{1, 2, 3, 4, 5}
dst := make([]int, len(src2))
n := copy(dst, src2)
dst[0] = 999
fmt.Println(src2) // [1 2 3 4 5] — 원본 보존
fmt.Println(dst) // [999 2 3 4 5]
fmt.Println(n) // 5 (복사된 요소 수)

// 부분 복사
dst2 := make([]int, 3)
copy(dst2, src2[1:]) // src2[1:4] → dst2
fmt.Println(dst2) // [2 3 4]
}

슬라이스 내부 공유와 함정

슬라이스를 단순 대입하거나 슬라이싱하면 내부 배열을 공유 합니다. 이로 인한 의도치 않은 수정을 주의해야 합니다.

package main

import "fmt"

func main() {
base := []int{1, 2, 3, 4, 5}

// 슬라이싱 — 같은 내부 배열 공유
sub := base[1:4] // [2 3 4]
sub[0] = 200
fmt.Println(base) // [1 200 3 4 5] — base도 변경됨!

// append가 cap을 초과하면 새 배열로 분리됨
base2 := []int{1, 2, 3, 4, 5}
sub2 := base2[1:3:3] // len=2, cap=2 (3인자로 cap 제한)
sub2 = append(sub2, 99) // cap 초과 → 새 배열 할당
sub2[0] = 200
fmt.Println(base2) // [1 2 3 4 5] — 분리됐으므로 영향 없음
fmt.Println(sub2) // [200 3 99]
}

슬라이스 유용한 패턴들

package main

import "fmt"

func main() {
// 스택(Stack) 구현
var stack []int
stack = append(stack, 1, 2, 3) // push
top := stack[len(stack)-1] // peek
stack = stack[:len(stack)-1] // pop
fmt.Println(top, stack) // 3 [1 2]

// 큐(Queue) 구현
var queue []int
queue = append(queue, 1, 2, 3) // enqueue
front := queue[0] // peek front
queue = queue[1:] // dequeue
fmt.Println(front, queue) // 1 [2 3]

// 필터(Filter) — 짝수만 추출
nums := []int{1, 2, 3, 4, 5, 6, 7, 8}
var evens []int
for _, n := range nums {
if n%2 == 0 {
evens = append(evens, n)
}
}
fmt.Println(evens) // [2 4 6 8]

// 중간 요소 삭제 (순서 유지)
s := []int{1, 2, 3, 4, 5}
i := 2 // 인덱스 2 삭제
s = append(s[:i], s[i+1:]...)
fmt.Println(s) // [1 2 4 5]

// 중간 요소 삭제 (순서 무시, 더 빠름)
s2 := []int{1, 2, 3, 4, 5}
s2[i] = s2[len(s2)-1]
s2 = s2[:len(s2)-1]
fmt.Println(s2) // [1 2 5 4]
}

2차원 슬라이스

package main

import "fmt"

func main() {
// 2차원 슬라이스 (행마다 길이가 다를 수 있음)
rows := 3
matrix := make([][]int, rows)
for i := range matrix {
matrix[i] = make([]int, i+1) // 삼각형 모양
for j := range matrix[i] {
matrix[i][j] = (i+1) * (j+1)
}
}
for _, row := range matrix {
fmt.Println(row)
}
// [1]
// [2 4]
// [3 6 9]
}

실전 예제 — 슬라이딩 윈도우 최댓값

package main

import "fmt"

// 슬라이딩 윈도우 크기 k에서 각 위치의 최댓값 반환
func maxSlidingWindow(nums []int, k int) []int {
if len(nums) == 0 || k == 0 {
return nil
}
result := make([]int, 0, len(nums)-k+1)
for i := 0; i <= len(nums)-k; i++ {
window := nums[i : i+k]
maxVal := window[0]
for _, v := range window[1:] {
if v > maxVal {
maxVal = v
}
}
result = append(result, maxVal)
}
return result
}

func main() {
nums := []int{1, 3, -1, -3, 5, 3, 6, 7}
fmt.Println(maxSlidingWindow(nums, 3))
// [3 3 5 5 6 7]
}