이 글은 Must Have Tucker의 Go 언어 프로그래밍 책을 참고하여 작성하였으며, 개인적인 학습 내용을 정리한 글입니다.
슬라이스
슬라이스는 Go 언어에서 제공하는 동적 배열 입니다.
동적 배열 이란? 자동으로 배열 크기를 증가시키는 자료 구조 입니다.
일반적인 배열은 처음 배열을 선언할 때 정한 길이에서 더 늘어나지 않습니다.
하지만 슬라이스를 사용하면 이런 불편함에서 벗어날 수 있습니다.
바로 선언 방법에 대해 알아보겠습니다.
슬라이스 선언
var slice []int기존 배열 선언에서 배열의 길이만 정의하지 않으면 됩니다.
그러다보니 슬라이스 길이를 초기화 해주지 않으면 길이가 0인 슬라이스가 됩니다.
예시 코드를 보면,
package main
import "fmt"
func main() {
var slice []int
if len(slice) == 0 { // ❶ slice 길이가 0인지 확인
fmt.Println("slice is empty", slice)
}
slice[1] = 10 // ❷ 에러 발생
fmt.Println(slice)
}
---
slice is empty []
panic: runtime error: index out of range [1] with length 0
goroutine 1 [running]:❶ slice 길이가 0 인지 검사를 합니다. 저희는 slice를 초기화 시켜주지 않았기 때문에 초깃값 0 그대로여서 조건이 true 나오고 "slice is empty" 문구가 출력이 됩니다.
❷ 길이가 0인 slice에서 두번째 요솟값에 접근을 하다보니 패닉이 발생을 합니다. 여기서 할당되지 않은 메모리 공간에 접근해서 프로그램이 비정상적으로 종료된 거라고 생각하시면 됩니다.
그러면 이제 슬라이스 초기화 방법에 대해 알아보겠습니다.
슬라이스 초기화
- 첫번째 방법
var slice1 = []int{1, 2, 3}
var slice2 = []int{1, 5:2, 10:3} // [1 0 0 0 0 2 0 0 0 0 3] 이렇게 배열됨, 5:2는 인덱스 5인 요소는 2 라는 의미 입니다.- 두번째 방법
var slice = make([]int, 3)이 방식은 make() 내장 함수를 사용하는 방법 입니다.
위와 같이 slice 선언을 하면, 길이 3개짜리 int 슬라이스 값을 갖습니다.
ex) [0 0 0] <= 이렇게 길이는 3개고, 요소 값을 지정해주지 않았으니 초깃값 0으로 생성됨
슬라이스 요소 접근
배열과 동일한 방법으로 접근 합니다.
var slice = make([]int, 3)
slice[1] = 5이렇게 하면 길이 3짜리 int 슬라이스 만들고, 인덱스 1에 5 값으로 변경하게 됩니다.
슬라이스 순회
이것 역시 배열과 같습니다.
var slice = []int{1, 2, 3}
for i := 0; i < len(slice); i++ { // 1번
slice[i] += 10
}
for i, v := range slice { // 2번
slice[i] = v * 2
}1번, len() 내장 함수를 사용해 slice 길이를 알아내어 순회하면서 각 요소의 값에 10씩 더해줌
2번, range 키워드를 사용해 각 요소들을 순회, for 문에 있는 v는 요솟값을 뜻하므로 각 요솟값에 2씩 곱해줍니다.
슬라이스 요소 추가 - append()
기존 배열은 한번 길이가 정해지면 늘릴 수 없지만, 슬라이스는 요소를 추가해 길이를 늘릴 수 있습니다.
요소 추가를 할 때, append 내장 함수를 사용 합니다.
package main
import "fmt"
func main() {
var slice = []int{1, 2, 3} // ❶ 요소가 3개인 슬라이스
slice2 := append(slice, 4) // ❷ 요소 추가
fmt.Println(slice)
fmt.Println(slice2)
}
❷ 에서 보면, append 함수를 사용해서, ❶ 에서 정의한 slice에 새로운 요솟값 4를 추가해주면
[1 2 3 4] 로 4개의 요소를 같은 슬라이스가 생기는 걸 알 수 있습니다.
슬라이스 동작 원리
슬라이스는 내장 타입으로 내부 구현이 감춰져 있지만, SliceHeader 구조체를 사용해 내부 구현을 살펴볼 수 있습니다.
type SliceHeader struct {
Data uintptr // 실제 배열을 가리키는 포인터
Len int // 요소 개수
Cap int // 실제 배열의 길이
}- 슬라이스 구현은 배열을 가리키는 포인터와 요소 개수를 나타내는 len
- 전체 배열 길이를 나타내는 cap
즉, 슬라이스는 배열 자체가 아니라 배열을 가리키는 뷰 입니다.
앞서 make() 함수를 사용해 slice를 생성을 했었는데, 좀 더 자세히 알아보면
var slice = make([]int, 3)이렇게 정의를 하면 SliceHeader 구조체에서는 아래 그림 처럼 들어 갑니다.

slice 는 len(요소 개수)이 3이고, cap(배열 길이)이 3인 슬라이스가 생깁니다.
또 다른 예시를 보면,
var slice2 = make([]int, 3, 5)
len(요소 개수)은 3개, cap(배열 길이)은 5개 말하자면 총 5개 중 3개만 사용하고 나머지 2개는 나중에 추가될 요소를 위해 비워뒀다고 생각하면 됩니다.
그리고 요솟값을 초기화 해주지 않았으니 초기값인 0으로 3개의 요소에 들어가는 것을 알 수 있습니다.
지금까지의 설명을 들어보면 슬라이스는 SliceHeader 구조체를 사용하는 배열이라고 생각되어 배열을 사용하는 방법과 비슷하게 사용할 수도 있을 것 같습니다.
그러나 배열과 사용법이 비슷하다고 해서 똑같이 사용하면 버그를 만날 수 있습니다.
예시를 통해 알아보겠습니다.
package main
import "fmt"
func changeArray(array2 [5]int) { // ❶ 배열을 받아서 세 번째 값 변경
array2[2] = 200
}
func changeSlice(slice2 []int) { // ❷ 슬라이스를 받아서 세 번째 값 변경
slice2[2] = 200
}
func main() {
array := [5]int{1, 2, 3, 4, 5}
slice := []int{1, 2, 3, 4, 5}
changeArray(array)
changeSlice(slice)
fmt.Println("array:", array)
fmt.Println("slice:", slice)
}
---
array: [1 2 3 4 5]
slice: [1 2 200 4 5]결과 값을 보면, 배열은 인덱스 2번의 값이 200으로 변경 되지 않았지만, 슬라이스는 변경 된 것을 확인할 수 있습니다.
왜 이런지 알아보겠습니다.
동작 차이의 원인
Go 언어에서는 모든 값의 대입은 복사로 일어납니다. 배열도 마찬가지 입니다.
그렇다 보니 changeArray() 함수가 main() 함수에서 호출이 될 때, changeArray() 함수의 인수로 array를 입력해서 호출을 하면 array 값이 array2로 복사가 됩니다.
그런데, array 배열과 array2 배열은 메모릭 공간이 다른 완전히 다른 배열 이기 때문에, array2의 인덱스 2번 요소 값을 200으로 변경해도 array 배열은 변경되지 않습니다.
그래서 마지막에 array 배열의 값을 찍어보면 [1 2 3 4 5] 그대로 나옵니다.
그러나 changeSlice() 함수가 호출될 때를 보면,

앞서 설명한 것 처럼 slice는 "배열 자체가 아니라 배열을 가리키는 뷰" 라고 했었습니다.
그래서 changeSlice() 함수의 인수로 slice가 입력되어 호출되면 slice, slice2 모두 같은 배열을 보게 됩니다.
그러다 보니, slice2에서 인덱스 2번 요솟값을 200으로 변경하면 slice의 인덱스 2번 요솟값도 200으로 변경되는 것과 동일해서 결과값에서 slice는 [1 2 200 4 5]로 바뀐 것을 알 수 있습니다.
그러나 만약 append() 함수를 사용해서 slice2의 배열 길이를 늘려주면 slice와 다른 배열로 취급 되기도 합니다.
append() 함수가 호출되면 먼저 슬라이스에서 값을 추가할 수 있는 빈 공간이 있는지 확인 합니다.
남은 빈 공간 = cap - len이렇게 계산을 해서 남은 빈 공간의 개수가 추가하는 값의 개수보다 크거나 같은 경우 배열의 뒷부분에 값을 추가한 뒤 len 값을 증가 시킵니다.
그러나, 빈공간이 없으면 새로운 더 큰 배열을 마련합니다.
일반적으로 기존 배열의 2배 크기로 마련합니다. 그런 뒤 기존 배열의 요소를 모두 새로운 배열에 복사를 합니다.
그러다 보니, 기존 배열을 사용을 하더라도 앞선 내용 처럼 같은 배열을 바라보는 것이 아닌 새로운 배열이 생기게 됩니다.
예시를 들어보면,
package main
import "fmt"
func main() {
slice1 := []int{1, 2, 3} // ❶ len:3 cap:3 슬라이스 생성
slice2 := append(slice1, 4, 5) // ❷ append() 함수로 요소 추가
fmt.Println("slice1:", slice1, len(slice1), cap(slice1))
fmt.Println("slice2:", slice2, len(slice2), cap(slice2))
slice1[1] = 100 // ❸ slice1 요솟값 변경
fmt.Println("After change second element")
fmt.Println("slice:", slice1, len(slice1), cap(slice1))
fmt.Println("slice2:", slice2, len(slice2), cap(slice2))
slice1 = append(slice1, 500) // ➍ slice1 요솟값 변경
fmt.Println("After append 500")
fmt.Println("slice1:", slice1, len(slice1), cap(slice1))
fmt.Println("slice2:", slice2, len(slice2), cap(slice2))
}
---
slice1: [1 2 3] 3 3
slice2: [1 2 3 4 5] 5 6
After change second element
slice: [1 100 3] 3 3
slice2: [1 2 3 4 5] 5 6
After append 500
slice1: [1 100 3 500] 4 6
slice2: [1 2 3 4 5] 5 6slice1은 cap - len을 했을 때, 남은 빈 공간이 0 이여서 append() 함수를 사용해서
slice2에 slice1 배열에 4, 5 값을 추가를 해주면
slice1과 slice2 모두 같은 배열을 바라보는 것이 아닌, 각각 서로 다른 배열을 바라보게 됩니다.
슬라이싱
슬라이싱은 배열의 일부를 집어내는 기능을 말합니다.
슬라이싱 기능을 이용하면 그 결과로 슬라이스를 반환 합니다.
즉, 배열의 일부를 떼어내 슬라이스로 만드는 기능 입니다.
바로 사용법에 대해 알아보겠습니다.
array[startIdx:endindex]이렇게 배열에서 시작인덱스:끝인덱스를 적어주면,
배열의 시작인덱스 부터 끝인덱스 - 1까지 잘라내어 슬라이스로 반환 합니다.

위 그림에서 array[1:3] 슬라이싱 하면
[2 3] 슬라이스가 생기게 됩니다.
다만 여기서 중요한 점이 있습니다.
원본 배열에서 슬라이싱 하여 슬라이스를 생성을 하면 새로운 슬라이스가 생기는 것이 아닌, 원본 배열을 바라보는 슬라이스가 생성이 되는 것 입니다.
예시로 보면,
package main
import "fmt"
func main() {
array := [5]int{1, 2, 3, 4, 5}
slice := array[1:2] // ❶ 슬라이싱
fmt.Println("array:", array)
fmt.Println("slice:", slice, len(slice), cap(slice))
array[1] = 100 // ❷ array의 두 번째 값 변경
fmt.Println("After change second element")
fmt.Println("array:", array)
fmt.Println("slice:", slice, len(slice), cap(slice))
slice = append(slice, 500) // ❸ slice에 값 추가
fmt.Println("After append 500")
fmt.Println("array:", array)
fmt.Println("slice:", slice, len(slice), cap(slice))
}
---
array: [1 2 3 4 5]
slice: [2] 1 4
After change second element
array: [1 100 3 4 5]
slice: [100] 1 4
After append 500
array: [1 100 500 4 5]
slice: [100 500] 2 4이렇게 원본 배열에서 잘라낸 슬라이스의 요솟값을 변경해도 원본 배열의 요솟값도 같이 변경 되는 것을 알 수 있습니다.
또한, 슬라이스의 cap의 길이가 4인 이유도 array에서 슬라이싱할 때, 원본 배열의 길이 - 슬라이싱의 startIdx 길이를 가지게 되어 cap(slice)의 값이 4가 나오게 됩니다.
슬라이스를 슬라이싱
슬라이싱은 배열 뿐 아니라 슬라이스의 일부를 집어낼 때도 사용 가능 합니다.
slice1 := []int{1, 2, 3, 4, 5}
slice2 := slice1[1:2] // slice2는 [2]
fmt.Println(len(slice1), cap(slice1)) // len:5, cap: 5
fmt.Println(len(slice2), cap(slice2)) // len:1, cap: 4- 처음부터 슬라이싱
slice1 := []int{1, 2, 3, 4, 5}
slice2 := slice1[0:3] // slice2는 [1, 2, 3]
slice2 := slice1[:3] // slice2는 [1, 2, 3] 위 방식과 동일한 결과- 끝까지 슬라이싱
slice1 := []int{1, 2, 3, 4, 5}
slice2 := slice1[2:len(slice1)] // slice2는 [3, 4, 5]
slice2 := slice1[2:] // slice2는 [3, 4, 5] 위 방식과 동일한 결과- 전체 슬라이싱
array := []int{1, 2, 3, 4, 5}
slice := array[:] // 전체 슬라이스이렇게 슬라이스에 대해 알아보았습니다.
감사합니다.
'개발 > Go' 카테고리의 다른 글
| [Go 언어 학습기 #9] Go 언어(Golang) 의 포인터 (0) | 2025.12.24 |
|---|---|
| [Go 언어 학습기 #7] Go 언어(Golang) 의 패키지 (0) | 2025.12.22 |
| [Go 언어 학습기 #6] Go 언어(Golang) 의 구조체 (0) | 2025.12.18 |
| [Go 언어 학습기 #5] Go 언어(Golang) 의 배열 (0) | 2025.12.17 |
| [Go 언어 학습기 #4] Go 언어(Golang) 의 함수 (0) | 2025.12.02 |
