개요
이번 블로그 포스트에서는 Golang의 Slice
(슬라이스)에 대해 자세히 알아보고 사용하는 방법에 대해서 살펴보려고 합니다. 이 블로그 포스트에서 소개하는 코드는 다음 링크를 통해 확인하실 수 있습니다.
Slice
슬라이스는 Golang에서 제공하는 동적 배열 타입(배열을 가리키는 포인터 타입)입니다.
- 정적(Static): 컴파일 시점(Compile)에 결정
- 동적(Dynamic): 실행 시점(Runtime)에 결정
다음은 Golang에서 배열을 선언하는 방법입니다.
var v [10]int
슬라이스는 다음과 같이 배열의 사이즈를 설정하지 않고 선언합니다.
var v []int
이를 확인하기 위해 main.go
파일을 생성하고 다음과 같이 수정합니다.
package main
import "fmt"
func main() {
var a [2]string
var b []string
fmt.Println(a)
fmt.Println(b)
}
이를 실행하면 다음과 같은 결과를 얻을 수 있습니다.
# go run main.go
[ ]
[]
배열과 슬라이스
슬라이스는 배열을 가리키는 포인터 타입입니다. 이를 확인하기 위해 main.go
파일을 다음과 같이 수정합니다.
package main
import "fmt"
func changeArr(arr2 [5]int) {
arr2[0] = 100
}
func changeSlice(slice2 []int) {
slice2[0] = 100
}
func main() {
arr := [5]int{1, 2, 3, 4, 5}
slice := []int{1, 2, 3, 4, 5}
changeArr(arr)
changeSlice(slice)
fmt.Println(arr)
fmt.Println(slice)
}
이를 실행하면 다음과 같은 결과를 얻을 수 있습니다.
# go run main.go
[1 2 3 4 5]
[100 2 3 4 5]
배열은 포인터 타입이 아니므로, changeArr
함수를 호출할 때, arr2
라는 새로운 인스턴스를 생성한 후, arr
의 내용을 복사하게 됩니다. 따라서 changeArr
함수안에서 arr2
값을 변경하여도 arr
값은 변경되지 않습니다.
하지만, 슬라이스는 포인터 타입입니다. 따라서 changeSlice
함수를 호출하면, slice2
라는 새로운 인스턴스를 생성하는게 아니라 slice
의 메모리 주소값을 복사하게 됩니다. 따라서 changeSlice
함수 안에서 slice2
값을 변경하면 slice
값도 변경됩니다.
len과 cap
슬라이스는 배열과는 다르게 len
이외에도 cap
이라는 데이터를 가지고 있습니다. 슬라이스의 len
은 현재 슬라이스에서 사용중인 길이를 의미하며, cap
은 현재 슬라이스의 총 크기(사용중인 길이 + 비어있는 길이)를 의미합니다.
이를 확인하기 위해 main.go
파일을 다음과 같이 수정합니다.
package main
import "fmt"
func main() {
slice1 := []int{1, 2, 3, 4, 5}
slice2 := make([]int, 2, 10)
fmt.Printf("slice1(%p): len=%d cap=%d %v\n", slice1, len(slice1), cap(slice1), slice1)
fmt.Printf("slice2(%p): len=%d cap=%d %v\n", slice2, len(slice2), cap(slice2), slice2)
}
이를 실행하면 다음과 같은 결과를 얻을 수 있습니다.
# go run main.go
slice1(0xc0000b2000): len=5 cap=5 [1 2 3 4 5]
slice2(0xc0000b4000): len=2 cap=10 [0 0]
make 함수
Golang에서 제공하는 make
함수를 사용하여 슬라이스를 생성할 수 있습니다.
slice1 := make([]int, 10)
fmt.Println(slice1)
위와 같이 make함수를 사용하여 10의 크기를 갖는 슬라이스를 생성할 수 있습니다.
slice2 := make([]int, 2, 10)
fmt.Println(slice2)
또는, 위와 같이 make함수를 사용하여 10의 크기를 갖는 슬라이스를 생성하고 두 개의 요소만 사용하도록 할 수 있습니다.
이를 확인하기 위해 main.go
파일을 다음과 같이 수정합니다.
package main
import "fmt"
func main() {
slice1 := make([]int, 10)
fmt.Println(slice1)
slice2 := make([]int, 2, 10)
fmt.Println(slice2)
}
이를 실행하면 다음과 같은 결과를 얻을 수 있습니다.
# go run main.go
[0 0 0 0 0 0 0 0 0 0]
[0 0]
슬라이싱
배열의 일부를 잘라내어 슬라이스를 만드는 것을 슬라이싱(Slicing)이라고 합니다.
- Array => Slicing => Slice
Golang에서 슬라이싱은 다음과 같이 사용할 수 있습니다.
Array[startIndex:endIndex]
startIndex에서부터 endIndex 직전
(endIndex -1)까지 값을 반환합니다. 이때, 반환된 Slice의 cap
은 startIndex부터 Array의 마지막 길이까지 입니다.
이를 확인하기 위해 main.go
파일을 다음과 같이 수정합니다.
package main
import "fmt"
func main() {
array := [5]int{1, 2, 3, 4, 5}
slice := array[1:2]
fmt.Printf("array: len=%d %v\n", len(array), array)
fmt.Printf("slice: len=%d cap=%d %v\n", len(slice), cap(slice), slice)
}
이를 실행하면 다음과 같은 결과를 얻을 수 있습니다.
# go run main.go
array: len=5 [1 2 3 4 5]
slice: len=1 cap=4 [2]
슬라이스를 슬라이싱하여 슬라이스를 생성할 수 있습니다. 또한, 다음과 같이 startIndex
에 0을 지정하면 처음부터 슬라이싱을 할 수 있으며, 0
은 생략이 가능합니다.
slice = []int{1, 2, 3, 4, 5}
slice1 := slice[0:3]
slice2 := slice[:3]
다음과 같이 사용하는 경우, 마지막까지 슬라이싱할 수 있으며, 마지막 인덱스를 생략할 수 있습니다.
slice = []int{1, 2, 3, 4, 5}
slice1 = slice[2:len(slice)]
slice2 = slice[2:]
마지막으로, 다음과 같이 전체를 슬라이싱할 수 있으며, 배열을 슬라이스로 바꿀때, 자주 사용됩니다.
arr := [5]int{1, 2, 3, 4, 5}
slice := arr[:]
슬라이싱은 새로운 변수를 만드는 것이 아니라, 단순히 메모리 주소를 가리키기만 함으로 다음과 같이 사용할 수 있습니다.
array1 := [100]int{1: 1, 2: 2, 99: 100}
slice1 = array1[1:10]
slice2 = slice1[2:99]
fmt.Println(slice1)
fmt.Println(slice2)
위의 예제에서 slice1
은 array1
의 메모리 주소를 가리키므로, slice1
에서 99번째까지의 값을 가져와서 slice2
를 만드는 것이 가능합니다.
cap 사이즈 조절 슬라이싱
Array를 슬라이싱하게 되면, 반환된 Slice의 cap
은 startIndex부터 Array의 마지막 길이까지가 됩니다. 하지만, 다음과 같이 슬라이싱을 할 때 maxIndex
를 추가하여 cap 사이즈를 조절할 수 있습니다.
slice[startIndex:endIndex:maxIndex]
slice1 = []int{1, 2, 3, 4, 5}
slice2 = slice1[1:3:4]
fmt.Printf("slice1: len=%d cap=%d %v\n", len(slice1), cap(slice1), slice1)
fmt.Printf("slice2: len=%d cap=%d %v\n", len(slice2), cap(slice2), sl
append 함수
슬라이스의 끝에 요소를 추가하기 위해서는 append
함수를 사용할 필요가 있습니다. append
함수를 사용하여 슬라이스에 요소를 추가한 경우, 새로운 요소가 추가된 새로운 슬라이스가 반환됩니다.
var slice1 = []int{1, 2, 3}
slice2 := append(slice1, 4)
이때, 새로운 슬라이스는 기존 슬라이스와 동일한 메모리 주소를 사용할 때도 있고, 동일한 메모리 주소를 사용하지 않을 때도 있습니다.
append
를 사용하여 새로운 요소를 슬라이스에 추가할 때, 슬라이스에 새로운 요소를 추가할 충분한 공간이 있는 경우, 새로운 슬라이스는 기존 슬라이스의 메모리 주소를 사용하게 됩니다. 하지만, 기존 슬라이스에 충분한 공간이 없는 경우, append
는 새로운 메모리 주소에 기존 슬라이스를 복사한 후, 새로운 요소를 추가하게 됩니다.
이를 확인하기 위해, main.go
파일을 다음과 같이 수정합니다.
package main
import "fmt"
func main() {
slice1 := make([]int, 3)
slice2 := append(slice1, 4)
fmt.Println("[New splice]")
fmt.Printf("slice(%p): len=%d cap=%d %v\n", slice1, len(slice1), cap(slice1), slice1)
fmt.Printf("slice2(%p): len=%d cap=%d %v\n", slice2, len(slice2), cap(slice2), slice2)
fmt.Println("slice1 changed ========================================================")
slice1[0] = 100
fmt.Printf("slice(%p): len=%d cap=%d %v\n", slice1, len(slice1), cap(slice1), slice1)
fmt.Printf("slice2(%p): len=%d cap=%d %v\n", slice2, len(slice2), cap(slice2), slice2)
fmt.Println("slice2 changed ========================================================")
slice2[0] = 200
fmt.Printf("slice(%p): len=%d cap=%d %v\n", slice1, len(slice1), cap(slice1), slice1)
fmt.Printf("slice2(%p): len=%d cap=%d %v\n", slice2, len(slice2), cap(slice2), slice2)
slice1 = make([]int, 1, 3)
slice2 = append(slice1, 4)
fmt.Println("[Same slice]")
fmt.Printf("slice(%p): len=%d cap=%d %v\n", slice1, len(slice1), cap(slice1), slice1)
fmt.Printf("slice2(%p): len=%d cap=%d %v\n", slice2, len(slice2), cap(slice2), slice2)
fmt.Println("slice1 changed ========================================================")
slice1[0] = 100
fmt.Printf("slice(%p): len=%d cap=%d %v\n", slice1, len(slice1), cap(slice1), slice1)
fmt.Printf("slice2(%p): len=%d cap=%d %v\n", slice2, len(slice2), cap(slice2), slice2)
fmt.Println("slice2 changed ========================================================")
slice2[0] = 200
fmt.Printf("slice(%p): len=%d cap=%d %v\n", slice1, len(slice1), cap(slice1), slice1)
fmt.Printf("slice2(%p): len=%d cap=%d %v\n", slice2, len(slice2), cap(slice2), slice2)
}
이를 실행하면 다음과 같은 결과를 확인할 수 있습니다.
# go run main.go
[New splice]
slice(0xc00012a000): len=3 cap=3 [0 0 0]
slice2(0xc00012c000): len=4 cap=6 [0 0 0 4]
slice1 changed ========================================================
slice(0xc00012a000): len=3 cap=3 [100 0 0]
slice2(0xc00012c000): len=4 cap=6 [0 0 0 4]
slice2 changed ========================================================
slice(0xc00012a000): len=3 cap=3 [100 0 0]
slice2(0xc00012c000): len=4 cap=6 [200 0 0 4]
[Same slice]
slice(0xc00012a018): len=1 cap=3 [0]
slice2(0xc00012a018): len=2 cap=3 [0 4]
slice1 changed ========================================================
slice(0xc00012a018): len=1 cap=3 [100]
slice2(0xc00012a018): len=2 cap=3 [100 4]
slice2 changed ========================================================
slice(0xc00012a018): len=1 cap=3 [200]
slice2(0xc00012a018): len=2 cap=3 [200 4]
슬라이스의 cap
이 새로운 요소를 추가할 여유가 있는 경우, 동일한 메모리 주소를 사용하는 것을 확인할 수 있습니다. 따라서, 슬라이스의 append
를 사용할 때, 기존 슬라이스와 새로운 슬라이스가 동시에 변경될 수 있으므로 주의하여 사용해야 합니다.
슬라이스 복사
다음과 같이 슬라이싱을 통해 슬라이스를 복사할 수 있습니다.
slice1 = []int{1, 2, 3, 4, 5}
slice2 = slice1[:]
slice2[1] = 100
fmt.Println(slice1)
fmt.Println(slice2)
하지만, 슬라이싱은 새로운 슬라이스를 생성하는 것이 아니라 메모리 주소를 공유하는 것이므로, 위와 같이 slice2
의 값을 변경하는 경우 slice1
의 값도 변경이 됩니다.
이와 같은 문제를 해결하기 위해서는 다음과 같이 slice1
과 동일한 크기의 slice2
를 만들고, 루프를 통해 값을 복사할 수 있습니다.
slice1 = []int{1, 2, 3, 4, 5}
slice2 = make([]int, len(slice1))
for i, v := range slice1 {
slice2[i] = v
}
slice2[1] = 100
fmt.Println(slice1)
fmt.Println(slice2)
또는 다음과 같이 append
함수를 통해 새로운 슬라이스를 만들고, 모든 내용을 추가하여 복사할 수 있습니다.
slice1 = []int{1, 2, 3, 4, 5}
slice2 = append([]int{}, slice1...)
slice2[1] = 100
fmt.Println(slice1)
fmt.Println(slice2)
마지막으로, make
함수를 사용하여 동일한 크기의 슬라이스를 생성한 후, copy
함수를 사용하여 모든 값을 복사할 수 있습니다.
slice1 = []int{1, 2, 3, 4, 5}
slice2 = make([]int, len(slice1))
copy(slice2, slice1)
slice2[1] = 100
fmt.Println(slice1)
fmt.Println(slice2)
Golang에서 copy
함수는 다음과 같이 사용할 수 있습니다.
copy(dst, src)
삭제
다음과 같이 슬라이스에서 특정 요소를 삭제할 수 있습니다.
slice := []int{1, 2, 3, 4, 5, 6}
deleteIdx := 2
fmt.Println(slice)
for i := deleteIdx + 1; i < len(slice); i++ {
slice[i-1] = slice[i]
}
slice = slice[:len(slice)-1]
fmt.Println(slice)
또는 다음과 같이 append
를 사용하여 삭제할 수 있습니다.
slice = []int{1, 2, 3, 4, 5, 6}
deleteIdx = 2
fmt.Println(slice)
slice = append(slice[:deleteIdx], slice[deleteIdx+1:]...)
fmt.Println(slice)
마지막으로 copy
함수를 사용하여 삭제할 수 있습니다.
slice = []int{1, 2, 3, 4, 5, 6}
deleteIdx = 2
fmt.Println(slice)
copy(slice[deleteIdx:], slice[deleteIdx+1:])
slice = slice[:len(slice)-1]
fmt.Println(slice)
요소 삽입
Golang에서는 다음과 같이 for
루프를 사용하여 슬라이스에 새로운 요소를 삽입할 수 있습니다.
slice := []int{1, 2, 3, 4, 5, 6}
insertIdx := 2
fmt.Println(slice)
slice = append(slice, 0)
for i := len(slice) - 2; i >= insertIdx; i-- {
slice[i+1] = slice[i]
}
slice[insertIdx] = 100
fmt.Println(slice)
또는 다음과 같이 append
를 사용하여 슬라이스에 새로운 요소를 삽입할 수 있습니다.
slice = []int{1, 2, 3, 4, 5, 6}
insertIdx = 2
fmt.Println(slice)
slice = append(slice[:insertIdx], append([]int{100}, slice[insertIdx:]...)...)
fmt.Println(slice)
마지막으로, copy
함수를 사용하여 슬라이스에 새로운 요소를 삽입할 수 있습니다.
slice = []int{1, 2, 3, 4, 5, 6}
insertIdx = 2
fmt.Println(slice)
slice = append(slice, 0)
copy(slice[insertIdx+1:], slice[insertIdx:])
slice[insertIdx] = 100
fmt.Println(slice)
슬라이스 정렬
Golang에서 기본적으로 제공하는 sort
패키지를 사용하여, 슬라이스를 정렬할 수 있습니다.
package main
import (
"fmt"
"sort"
)
func main() {
slice := []int{6, 3, 1, 5, 4, 2}
fmt.Println(slice)
sort.Ints(slice)
fmt.Println(slice)
}
다음과 같이 구조체 슬라이스도 sort
를 사용하여 정렬할 수 있습니다.
package main
import (
"fmt"
"sort"
)
type Student struct {
Name string
Age int
}
type Students []Student
func (s Students) Len() int { return len(s) }
func (s Students) Less(i, j int) bool { return s[i].Age < s[j].Age }
func (s Students) Swap(i, j int) { s[i], s[j] = s[j], s[i] }
func main() {
students := []Student{
{"c", 31},
{"a", 20},
{"b", 21},
{"d", 19},
}
fmt.Println(students)
sort.Sort(Students(students))
fmt.Println(students)
}
완료
이것으로 Golang에서 슬라이스를 선언하고 사용하는 방법에 대해서 알아보았습니다. 또한 배열과 슬라이스의 차이점, 슬라이싱을 통해 배열에서 슬라이스를 만드는 방법도 알아보았습니다.
제 블로그가 도움이 되셨나요? 하단의 댓글을 달아주시면 저에게 큰 힘이 됩니다!
앱 홍보
Deku
가 개발한 앱을 한번 사용해보세요.Deku
가 개발한 앱은 Flutter로 개발되었습니다.관심있으신 분들은 앱을 다운로드하여 사용해 주시면 정말 감사하겠습니다.