개요
슬라이스를 생성하고 조작하는 데 사용되는 세 가지 중요한 내장 함수인 make, copy, 그리고 append에 대해 살펴보겠습니다.
1. make 함수
`make` 함수는 타입, 길이, 용량 이렇게 세 가지 인수를 받습니다.
내장함수 주석은 다음과 같습니다.
// The make built-in function allocates and initializes an object of type
// slice, map, or chan (only). Like new, the first argument is a type, not a
// value. Unlike new, make's return type is the same as the type of its
// argument, not a pointer to it. The specification of the result depends on
// the type:
//
// Slice: The size specifies the length. The capacity of the slice is
// equal to its length. A second integer argument may be provided to
// specify a different capacity; it must be no smaller than the
// length. For example, make([]int, 0, 10) allocates an underlying array
// of size 10 and returns a slice of length 0 and capacity 10 that is
// backed by this underlying array.
// Map: An empty map is allocated with enough space to hold the
// specified number of elements. The size may be omitted, in which case
// a small starting size is allocated.
// Channel: The channel's buffer is initialized with the specified
// buffer capacity. If zero, or the size is omitted, the channel is
// unbuffered.
func make(t Type, size ...IntegerType) Type
정리하면,
- make 함수는 슬라이스(slice), 맵(map), 채널(chan) 타입의 객체만 할당하고 초기화합니다.
- 반환되는 결과의 구체적인 사항은 타입에 따라 다릅니다:
- 슬라이스(Slice): 인수로 전달된 크기는 슬라이스의 길이를 지정합니다. 슬라이스의 용량은 길이와 동일하며, 두 번째 정수 인수를 사용하여 다른 용량을 지정할 수 있습니다. 이때, 용량은 길이보다 작을 수 없습니다. 예를 들어, make([]int, 0, 10)은 크기가 10인 기본 배열을 할당하고, 길이가 0이고 용량이 10인 슬라이스를 반환합니다.
- 맵(Map): 지정된 요소 수를 담을 수 있는 충분한 공간을 갖춘 빈 맵이 할당됩니다. 크기를 생략할 수 있으며, 이 경우 작은 초기 크기로 할당됩니다.
- 채널(Channel): 채널의 버퍼는 지정된 버퍼 용량으로 초기화됩니다. 크기가 0이거나 생략된 경우, 채널은 버퍼가 없는 채널이 됩니다.
그렇다면, 실제 예제를 통해 알아보겠습니다.
package main
import "fmt"
func main() {
// 길이가 5, 용량이 10인 문자열 슬라이스 생성
fruits := make([]string, 5, 10)
fmt.Printf("fruits 길이: %d, 용량: %d\n", len(fruits), cap(fruits))
// 길이와 용량이 모두 3인 정수 슬라이스 생성
numbers := make([]int, 3)
fmt.Printf("numbers 길이: %d, 용량: %d\n", len(numbers), cap(numbers))
}
// 출력
// fruits 길이: 5, 용량: 10
// numbers 길이: 3, 용량: 3
배열 용량 늘리기
package main
import "fmt"
func main() {
// 초기 슬라이스 생성: 길이 10, 용량 15
slice := make([]int, 10, 15)
// 슬라이스에 초기 값 설정
for i := range slice {
slice[i] = i + 1
}
// 새로운 슬라이스 생성: 길이는 유지, 용량 2배로 지정
newSlice := make([]int, len(slice), 2*cap(slice))
// 기존 슬라이스의 데이터를 새 슬라이스로 복사
for i := range slice {
newSlice[i] = slice[i]
}
// 원래 슬라이스 변수에 새 슬라이스 할당
slice = newSlice
// 슬라이스 확장 후 데이터 추가
slice = append(slice, 11, 12, 13, 14, 15)
}
이렇게 용량을 늘리는 로직을 실행하게 되면, 일시적으로 메모리가 기존 슬라이스와 새로운슬라이스 메모리가 합쳐져서 메모리 사용량이 증가되게 됩니다.
단축선언 방법
단축형으로 사용할때 아래와 같이 표현 할 수 있습니다.
slice := make([]slice, 5) // 슬라이스의 길이와 용량이 모두 5
2. copy 함수
copy 함수는 한 슬라이스의 요소를 다른 슬라이스로 복사합니다. 이 함수는 두 슬라이스의 길이 중 더 작은 값만큼의 요소를 복사합니다.
copy(dst, src)
- dst: 대상 슬라이스
- src: 원본 슬라이스
package main
import "fmt"
func main() {
// 원본 슬라이스
src := []int{1, 2, 3, 4, 5}
// 대상 슬라이스 (길이가 더 긴 경우)
dst1 := make([]int, 7)
copied1 := copy(dst1, src)
fmt.Printf("복사된 요소 수: %d\n", copied1)
fmt.Printf("dst1: %v\n", dst1)
// 대상 슬라이스 (길이가 더 짧은 경우)
dst2 := make([]int, 3)
copied2 := copy(dst2, src)
fmt.Printf("복사된 요소 수: %d\n", copied2)
fmt.Printf("dst2: %v\n", dst2)
}
// 출력
// 복사된 요소 수: 5
// dst1: [1 2 3 4 5 0 0]
// 복사된 요소 수: 3
// dst2: [1 2 3]
슬라이스 중간 index에 데이터 삽입
// Insert는 지정된 인덱스에 값을 삽입합니다. 인덱스는 범위 내에 있어야 하며,
// 슬라이스는 새 요소를 위한 공간이 있어야 합니다.
func Insert(slice []int, index, value int) []int {
// 슬라이스를 하나의 요소로 확장합니다.
slice = slice[0 : len(slice)+1]
// copy를 사용하여 슬라이스의 상위 부분을 이동시키고 빈 공간을 만듭니다.
copy(slice[index+1:], slice[index:])
// 새 값을 저장합니다.
slice[index] = value
// 결과를 반환합니다.
return slice
}
실행 중 데이터 상태를 살펴보겠습니다.
1. 초기 함수 전달된 상태의 슬라이스 상태입니다.
2. copy 함수를 수행 후 insert index 부터 배열이 복제되어 추가된것을 알수 있습니다.
`len`이 10 -> 11로 늘어 났습니다.
3. 추가하고자 하는 index에 데이터를 넣은 후 상태입니다.
index:5 의 데이터가 99로 변경된것을 확인할 수 있습니다.
슬라이스에 요소를 삽입하는 로직을 살펴보았습니다.
하지만, 위 코드는 용량이 가득찼을때는 에러가 발생 합니다.
용량을 벗어 났을때의 상황을 처리하는 코드를 알아 보겠습니다.
func Extend(slice []int, element int) []int {
n := len(slice)
if n == cap(slice) {
// 슬라이스가 가득 찼습니다; 확장해야 합니다.
// 크기를 두 배로 늘리고 1을 추가합니다. 크기가 0일 때도 여전히 확장합니다.
newSlice := make([]int, len(slice), 2*len(slice)+1)
copy(newSlice, slice)
slice = newSlice
}
slice = slice[0 : n+1]
slice[n] = element
return slice
}
이렇게 한다면, 용량을 넘어가게 되면 배열을 2배늘려서 용량을 확보하고 데이터를 삽입할 수 있습니다.
이처럼 Extend 함수 매커니즘을 사용하여 Go언어에서 내장함수로 지원하는 함수가 있습니다.
바로 `append` 함수 입니다. 다음은 `append`함수에 대해 알아 보겠습니다.
3. append 함수
Append 함수 구조
go팀에서 append를 설명하기위한 `Append` 예시함수부터 살펴 보겠습니다.
func Append(slice []int, items ...int) []int
...int 는 0개 이상의 인자를 받겠다는 의미 입니다.
구현체는 아래 예시코드입니다.
// Append는 항목을 슬라이스에 추가합니다.
// 첫 번째 버전: 단순히 Extend를 반복 호출합니다.
func Append(slice []int, items ...int) []int {
for _, item := range items {
slice = Extend(slice, item)
}
return slice
}
효율을 높이기 위해, 용량을 2배가 아닌 사용자 정의로 변경하고 싶다면 다음 코드를 참고 해주세요
// Append는 요소를 슬라이스에 추가합니다.
// 효율적인 버전입니다.
func Append(slice []int, elements ...int) []int {
n := len(slice)
total := len(slice) + len(elements)
if total > cap(slice) {
// 재할당합니다. 새 크기의 1.5배로 성장하여 계속 확장할 수 있습니다.
newSize := total*3/2 + 1
newSlice := make([]int, total, newSize)
copy(newSlice, slice)
slice = newSlice
}
slice = slice[:total]
copy(slice[n:], elements)
return slice
}
golang 에서 내장함수로 지원하는 append 함수!
append 함수는 슬라이스에 하나 이상의 요소를 추가합니다. 필요한 경우 자동으로 슬라이스의 용량을 늘립니다. (위 Extend 함수에서 살펴 보았습니다.)
slice = append(slice, elements...)
- slice: 요소를 추가할 슬라이스
- elements: 추가할 요소들 (가변 인자)
1. 단일 항목 추가하기
// 두 개의 시작 슬라이스 생성
slice := []int{1, 2, 3}
fmt.Println("Start slice: ", slice)
// 슬라이스에 항목 추가하기
slice = append(slice, 4)
fmt.Println("Add one item:", slice)
2. 하나의 슬라이스를 다른 슬라이스에 추가하기
slice2 := []int{55, 66, 77}
fmt.Println("Start slice2:", slice2)
// 하나의 슬라이스를 다른 슬라이스에 추가하기
slice = append(slice, slice2...)
fmt.Println("Add one slice:", slice)
3. 슬라이스 복사하기
// 슬라이스 복사하기 (int 타입)
slice3 := append([]int(nil), slice...)
fmt.Println("Copy a slice:", slice3)
`[]int(nil)`은 `slice`와 같은 타입의 새로운 빈 슬라이스를 만들고, `append`를 사용하여 `slice`의 요소를 `slice3`로 복사합니다.
4. 슬라이스를 자기 자신에 복사하기
// 슬라이스를 자기 자신에 복사하기
fmt.Println("Before append to self:", slice)
slice = append(slice, slice...)
fmt.Println("After append to self:", slice)
// 출력
// Before append to self: [1 1111 3 4 55 66 77]
// After append to self: [1 1111 3 4 55 66 77 1 1111 3 4 55 66 77]
정리
go.dev 참고하여 make, copy, append 함수에 대해서 알아 보았습니다.
시리즈
Golang Arrays(배열), Slice(슬라이스) - 선언, 초기화 방법
Golang Slice(슬라이스) : make, copy, append
참고
'프로그래밍 > Golang' 카테고리의 다른 글
Golang map(맵) : 기본개념 (0) | 2024.08.01 |
---|---|
Golang Slice(슬라이스) 요소 제거하기 (0) | 2024.07.31 |
Golang Arrays(배열), Slice(슬라이스) - 선언, 초기화 방법 (0) | 2024.07.31 |
Golang Interface - 빈 인터페이스 (interface{}) (0) | 2024.07.27 |
Golang Interface - 암시적 인터페이스: 코드 유연성과 재사용성 (0) | 2024.07.27 |