안녕하세요, 주니어 개발자 Eon입니다.
이번 포스트는 슬라이스에 관한 내용입니다.
Golang의 동적 배열입니다.
배열과 다르게 배열 길이의 확장과 축소가 가능합니다.
var intSlice1 []int var intSlice2 []int = []int{1, 2, 3, 4, 5} var intSlice3 = []int{6, 7, 8, 9, 10} intSlice4 := []int{} intSlice5 := []int{11, 12, 13, 14, 15}
슬라이스 변수의 요소 타입을
int
로 선언하려면 위와 같이[]int
로 타입을 지정하면 됩니다.
var slSlice1 [][]int var slSlice2 [][]int = [][]int{{1, 2, 3, 4, 5}, {6, 7, 8}} var slSlice3 = [][]int{{6, 7, 8}, {1, 2, 3, 4, 5}} slSlice4 := [][]int{} slSlice5 := [][]int{{}} // [][]int{{}, {}} // [][]int{{}, {}, {}} slSlice6 := [][]int{{6, 7, 8}, {1, 2, 3, 4, 5}}
위와 같이 슬라이스를 이중으로 사용할 수도 있습니다.
이 경우, 동적 이차원 배열이 됩니다.
var intSlice1 []int = []int{0: 1, 3: 4, 5, 6} fmt.Println(intSlice1) /* output [1 0 0 4 5 6] */
위와 같이 인덱스를 지정하여 선언할 수 있습니다.
초기화되지 않은 인덱스는zero-value
로 초기화됩니다.
var intSlice1 []int = []int{3: 4, 7, 8, 9, 0: 1, 5, 6} fmt.Println(intSlice1) /* output [1 5 6 4 7 8 9] */
이렇게 인덱스 순서를 다르게 하면 마지막에 지정한 인덱스 뒤에, 인덱스를 지정하지 않은 값들을 순서대로 초기화됩니다.
위의 상황에서5
,6
으로 초기화 시킨 값 뒤에 인덱스 하나를 더 추가할 경우,0
번 인덱스 초기화 뒤에 이미 2개의 값이 들어갔고 그 뒤3
번 인덱스로 인식이 되어duplicated index
에러가 발생합니다.
slice
는 string
과 비슷합니다.
단순 값 복사를 하면 헤더가 복사돼, 서로 다른 변수라도 같은 slice
를 가리키게 됩니다.
string
을 복사할 때, StringHeader
가 복사된 것처럼slice
도 SliceHeader
가 복사되기 때문입니다.
// SliceHeader is the runtime representation of a slice. // It cannot be used safely or portably and its representation may // change in a later release. // Moreover, the Data field is not sufficient to guarantee the data // it references will not be garbage collected, so programs must keep // a separate, correctly typed pointer to the underlying data. type SliceHeader struct { Data uintptr Len int Cap int } ... type StringHeader struct { Data uintptr Len int } ...
SliceHeader
는Data
,Len
,Cap
필드를 가지고 있습니다.
StringHeader
는Data
와Len
필드를 가지고 있습니다.
실제 데이터의 위치를 가리킵니다.
데이터의 길이를 나타냅니다.
데이터를 수용할 수 있는 범위를 나타냅니다.
// 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
slice
, map
, chan
의 선언 및 초기화하는 방법 중 하나이며, golang 내장함수
입니다.
new
는 포인터 타입을 반환하지만, make
는 지정한 타입으로 반환합니다.
make
의 인자 (for slice)요소의 타입
Len
Cap
var slice []int = make([]int, 10, 12) fmt.Println(slice) /* output [0 0 0 0 0] */
길이가
10
이고 최대 길이가12
인[]int
슬라이스를 생성합니다.
생성된 슬라이스는zero-value
로 초기화됩니다.
// The append built-in function appends elements to the end of a slice. If
// it has sufficient capacity, the destination is resliced to accommodate the
// new elements. If it does not, a new underlying array will be allocated.
// Append returns the updated slice. It is therefore necessary to store the
// result of append, often in the variable holding the slice itself:
// slice = append(slice, elem1, elem2)
// slice = append(slice, anotherSlice...)
// As a special case, it is legal to append a string to a byte slice, like this:
// slice = append([]byte("hello "), "world"...)
func append(slice []Type, elems ...Type) []Type
슬라이스의 끝에 요소를 '덧붙이는' golang 내장함수
입니다.
capacity
가 여유있는 경우엔 새 요소를 그대로 붙이고, 여유가 없는 경우에는 새로운 슬라이스가 할당됩니다.
append()
내장함수는 새로 업데이트된 슬라이스를 반환합니다.
slice = append(slice, elem1, elem2)
slice = append(slice, anotherSlice...)
위와 같이 사용 가능합니다. 물론 요소의 자료형은 slice
요소의 자료형과 동일해야 합니다.
(slice...
의 ...
은 슬라이스를 덧붙일 때 사용합니다.)
append()의 메모리 할당
func main() { slice := []int{1, 2, 3, 4, 5} slHeader := (*reflect.SliceHeader)(unsafe.Pointer(&slice)) fmt.Printf("slHeader.Data : %v\n", slHeader.Data) fmt.Printf("slHeader.Len : %v\n", slHeader.Len) fmt.Printf("slHeader.Cap : %v\n", slHeader.Cap) fmt.Println() slice = append(slice, 6, 7, 8) slHeader = (*reflect.SliceHeader)(unsafe.Pointer(&slice)) fmt.Printf("slHeader.Data : %v\n", slHeader.Data) fmt.Printf("slHeader.Len : %v\n", slHeader.Len) fmt.Printf("slHeader.Cap : %v\n", slHeader.Cap) } /* output slHeader.Data : 824634273488 slHeader.Len : 5 slHeader.Cap : 5 slHeader.Data : 824634433536 slHeader.Len : 8 slHeader.Cap : 10 */
append()
를 사용했을 때, 기존 슬라이스의 공간이 부족하면 두 배 크기의 공간을 할당해서 완전히 새로운 슬라이스를 만들어서 반환합니다.... slice2 := []int{6, 7, 8, 9, 10, 11} slice = append(slice, slice2...) ... /* output slHeader.Data : 824634318848 slHeader.Len : 11 slHeader.Cap : 12 */
만약 위와 같이 새로 덧붙여지는 요소 / 슬라이스의 크기가 기존의 슬라이스보다 큰 경우, 더 큰 크기의 슬라이스를 기준으로 두 배의 공간을 할당해서 새로운 슬라이스를 만들고 반환합니다.
// The copy built-in function copies elements from a source slice into a
// destination slice. (As a special case, it also will copy bytes from a
// string to a slice of bytes.) The source and destination may overlap. Copy
// returns the number of elements copied, which will be the minimum of
// len(src) and len(dst).
func copy(dst, src []Type) int
src
슬라이스를 dst
슬라이스로 복사합니다. string
의 byte
값을 []byte
자료형으로 하여 복사하기도 합니다.
copy()
내장함수
는 복사된 요소의 개수를 반환합니다.
func main() { slice1 := []int{1, 2, 3, 4, 5} slice2 := slice1 fmt.Println(slice2) } /* output [1 2 3 4 5] */
위와 같이 슬라이스 대입을 수행하면, 같은 값을 가지는 걸 볼 수 있습니다.
slice1[0] = 100 fmt.Println(slice2) /* output [100 2 3 4 5] */
이렇게
slice1
을 수정하면slice2
까지 영향을 받는 걸 볼 수 있습니다.
슬라이스를 대입 연산을 하면 슬라이스 헤더가 대입되어 두 변수 모두 하나의 인스턴스를 가리키게 됩니다.func main() { slice1 := []int{1, 2, 3, 4, 5} slice2 := slice1 fmt.Println(slice2) slice1[0] = 100 fmt.Println(slice2) slHeader1 := (*reflect.SliceHeader)(unsafe.Pointer(&slice1)) slHeader2 := (*reflect.SliceHeader)(unsafe.Pointer(&slice2)) fmt.Println() fmt.Println("*-*-*-*-* slice1 *-*-*-*-*") fmt.Printf("slHeader1.Data : %v\n", slHeader1.Data) fmt.Printf("slHeader1.Len : %v\n", slHeader1.Len) fmt.Printf("slHeader1.Cap : %v\n", slHeader1.Cap) fmt.Println() fmt.Println("*-*-*-*-* slice2 *-*-*-*-*") fmt.Printf("slHeader2.Data : %v\n", slHeader2.Data) fmt.Printf("slHeader2.Len : %v\n", slHeader2.Len) fmt.Printf("slHeader2.Cap : %v\n", slHeader2.Cap) } /* output [1 2 3 4 5] [100 2 3 4 5] *-*-*-*-* slice1 *-*-*-*-* slHeader1.Data : 824634376240 slHeader1.Len : 5 slHeader1.Cap : 5 *-*-*-*-* slice2 *-*-*-*-* slHeader2.Data : 824634376240 slHeader2.Len : 5 slHeader2.Cap : 5 */
같은
Data
필드를 가진 것을 볼 수 있습니다.
슬라이스 순회를 이용한 복사
func main() { slice1 := []int{1, 2, 3, 4, 5} slice2 := make([]int, len(slice1)) for i, v := range slice1 { slice2[i] = v } fmt.Println(slice2) } /* output [1 2 3 4 5] */
이 방법은
slice1
와 같은 길이를 가지는slice2
를 빈 값으로 선언 및 초기화를 먼저 진행합니다.
그리고 빈 슬라이스인slice2
에slice1
의 요소를 순회하며 대입하는 방식으로, 값은 같으나 서로 다른 인스턴스를 가리키게 합니다.
(서로 같은 인스턴스를 가리키는지 확인하려면 위의SliceHeader
를 출력하는 구문을 사용하면 됩니다.)
append() 내장함수를 이용한 복사
func main() { slice1 := []int{1, 2, 3, 4, 5} slice2 := append([]int{}, slice1...) fmt.Println(slice2) } /* output [1 2 3 4 5] */
append()
내장함수
를 사용하여 새로운 빈 슬라이스에 기존 슬라이스를 덧붙여, 같은 값을 가지지만 서로 다른 인스턴스인 슬라이스를 생성할 수 있습니다.
copy() 내장함수를 이용한 복사
func main() { slice1 := []int{1, 2, 3, 4, 5} slice2 := make([]int, 3) copy(slice2, slice1) fmt.Println(slice2) } /* output [1 2 3] */
func copy(dst, src []Type) int
의dst
의 크기 내에서src
를 복사합니다.
dst
의 범위를 벗어나는 요소는 복사해오지 않습니다.
특정 인덱스를 제거한 새로운 슬라이스
func SubElement(index int, slice []int) (subSlice []int) { subSlice = []int{} for i, v := range slice { if index == i { continue } subSlice = append(subSlice, v) } return } func main() { slice1 := []int{1, 2, 3, 4, 5} slice2 := SubElement(3, slice1) slice2Header := (*reflect.SliceHeader)(unsafe.Pointer(&slice2)) fmt.Println("slice2 :", slice2) fmt.Println("*-*-*-*-* slice2 *-*-*-*-*") fmt.Printf("slice2Header.Data : %v\n", slice2Header.Data) fmt.Printf("slice2Header.Len : %v\n", slice2Header.Len) fmt.Printf("slice2Header.Cap : %v\n", slice2Header.Cap) } /* output slice2 : [1 2 3 5] *-*-*-*-* slice2 *-*-*-*-* slice2Header.Data : 824634425344 slice2Header.Len : 4 slice2Header.Cap : 4 */
기존 슬라이스를 보존하면서 특정 인덱스를 뺀 슬라이스를 새로 만듭니다.
기존 슬라이스에서 특정 인덱스를 제거
func main() { slice := []int{1, 2, 3, 4, 5} index := 3 beforeHeader := (*reflect.SliceHeader)(unsafe.Pointer(&slice)) fmt.Println("slice :", slice) fmt.Println("*-*-*-*-* slice *-*-*-*-*") fmt.Println(beforeHeader) for i := index + 1; i < len(slice); i++ { slice[i-1] = slice[i] } slice = slice[:len(slice)-1] fmt.Println() resultHeader := (*reflect.SliceHeader)(unsafe.Pointer(&slice)) fmt.Println("slice :", slice) fmt.Println("*-*-*-*-* slice *-*-*-*-*") fmt.Println(resultHeader) } /* output slice : [1 2 3 4 5] *-*-*-*-* slice *-*-*-*-* &{824634376240 5 5} slice : [1 2 3 5] *-*-*-*-* slice *-*-*-*-* &{824634376240 4 5} */
슬라이스를 처음 만들었을 때의
capacity
가 유지되는 것을 볼 수 있습니다.
func main() {
slice1 := []int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}
fmt.Println(slice1[:3])
fmt.Println(slice1[5:])
fmt.Println(slice1[:])
fmt.Println(slice1[2:5])
}
/* output
[1 2 3]
[6 7 8 9 10]
[1 2 3 4 5 6 7 8 9 10]
[3 4 5]
*/
slice[포함부터 : 전까지]
슬라이스를 위와 같은 방법으로 절단할 수 있습니다.
슬라이싱으로 특정 인덱스 제거
func main() { slice := []int{1, 2, 3, 4, 5} index := 3 beforeHeader := (*reflect.SliceHeader)(unsafe.Pointer(&slice)) fmt.Println("slice :", slice) fmt.Println("*-*-*-*-* slice *-*-*-*-*") fmt.Println(beforeHeader) slice = append(slice[:index], slice[index+1:]...) fmt.Println() resultHeader := (*reflect.SliceHeader)(unsafe.Pointer(&slice)) fmt.Println("slice :", slice) fmt.Println("*-*-*-*-* slice *-*-*-*-*") fmt.Println(resultHeader) } /* output slice : [1 2 3 4 5] *-*-*-*-* slice *-*-*-*-* &{824634384432 5 5} slice : [1 2 3 5] *-*-*-*-* slice *-*-*-*-* &{824634384432 4 5} */
슬라이스를 처음 만들었을 때의
capacity
가 유지되는 것을 볼 수 있습니다.
copy() 내장함수로 특정 인덱스 제거
func main() { slice := []int{1, 2, 3, 4, 5} index := 3 beforeHeader := (*reflect.SliceHeader)(unsafe.Pointer(&slice)) fmt.Println("slice :", slice) fmt.Println("*-*-*-*-* slice *-*-*-*-*") fmt.Println(beforeHeader) copy(slice[index:], slice[index+1:]) slice = slice[:len(slice)-1] fmt.Println() resultHeader := (*reflect.SliceHeader)(unsafe.Pointer(&slice)) fmt.Println("slice :", slice) fmt.Println("*-*-*-*-* slice *-*-*-*-*") fmt.Println(resultHeader) } /* output slice : [1 2 3 4 5] *-*-*-*-* slice *-*-*-*-* &{824634376240 5 5} slice : [1 2 3 5] *-*-*-*-* slice *-*-*-*-* &{824634376240 4 5} */
슬라이스를 처음 만들었을 때의
capacity
가 유지되는 것을 볼 수 있습니다.
슬라이스 요소 밀어내어 삽입
func main() { slice := []int{1, 2, 3, 4, 5} slice = append(slice, 0) index := 3 const location = 2 for i := len(slice) - location; i >= index; i-- { slice[i+1] = slice[i] } slice[index] = 100 fmt.Println(slice) } /* output [1 2 3 100 4 5] */
길이를 하나 늘리면, 기존 슬라이스의 마지막 인덱스는
len(바뀐 슬라이스)-2
에 위치합니다.
슬라이스의 요소들을index
기준으로 하나씩 뒤로 미루고,index
위치에 새로운 요소를 삽입합니다.
append() 내장함수와 슬라이싱으로 요소 삽입
func main() { slice := []int{1, 2, 3, 4, 5} index := 3 slice = append(slice[:index], append([]int{100}, slice[index:]...)...) fmt.Println(slice) } /* output [1 2 3 100 4 5] */
(미포함)까지인 슬라이스
에,append(추가하려는 요소 + (포함)슬라이스)
를 덧붙입니다.
단,append()
내에서 임시로 만든 새 슬라이스에(포함)슬라이스
를 덧붙이는 과정에 임시 버퍼를 사용한다는 단점이 존재합니다.
copy() 내장함수와 슬라이싱으로 요소 삽입
func main() { slice := []int{1, 2, 3, 4, 5} index := 3 slice = append(slice, 0) copy(slice[index+1:], slice[index:]) slice[index] = 100 fmt.Println(slice) } /* output [1 2 3 100 4 5] */
zero-value
요소를 추가하고, 특정 인덱스부터의 요소를 한 칸씩 밀어냅니다.
이 과정에서slice[index]
의 값은 유지가 되어,slice[index+1]
과 동일한 값을 가집니다.
이번 포스트는 슬라이스에 관한 내용이었습니다.
감사합니다.👍