Go 에서는 Array 보다는 Slice 더 자주 사용한다. 이유는 Slice의 유연함 때문이다.
슬라이스는 배열과 비슷하지만 길이가 가변적이다.
-
가변적인 길이(length)
- 길이 함수 :
len()
- Index로 접근할 수 있는 공간.
- 길이가 고정적이지 않음. 그렇기에
[]
안에 길이를 지정하지 않음. - 슬라이스 길이는 용량보다 크게 설정할 수 없다.
- 길이 함수 :
-
용량(capacity)을 지님
- 용량 함수 :
cap()
- 용량은 실제 메모리에 할당된 공간 을 말한다.
- 용량을 생략하면 슬라이스의 길이와 동일하게 설정된다.
- 용량이 길이보다 크더라도 길이를 벗어난 Index에는 접근할 수 없다.
--> 접근 시도시runtime error
발생 - 용량이 가득차면 용량은 자동으로 늘어난다.
- 용량 함수 :
-
Reference Type
- 슬라이스를 참조가 아니라 복사하고자 한다면
copy()
함수를 사용해야 한다.
- 슬라이스를 참조가 아니라 복사하고자 한다면
-
nil
- 슬라이스를 선언만 하고 초기화 하지 않은 경우 값은 nil이 자동 할당 된다.
-
비교 연산자 사용 불가
- 슬라이스에는 비교연산자(
==
)를 사용할 수 없다.- 슬라이스의 원소는 배열의 원소와는 달리 간접 참조이기 때문이다. 그로인해 슬라이스의 값은 내부 배열이 변경됨에 따라 비교 시 다른 값을 가질 수 있게 된다.
sl1 := []int{1, 2} sl2 := []int{1, 2} fmt.Println("sl1 == sl2 : ", sl1 == sl2) // invalid operation: sl1 == sl2 (slice can only be compared to nil)
- 슬라이스끼리의 비교는
nil
만이 유일하게 가능하다.
- 슬라이스에는 비교연산자(
-
빈 슬라이스의 확인
- 빈 슬라이스인지 확인하기 위해서는
s == nil
이 아닌len(s) == 0
를 사용.- 빈 슬라이스는
nil
과 비교할 때 외에는 길이가 0인 슬라이스처럼 동작하기 때문.
- 빈 슬라이스는
- 빈 슬라이스인지 확인하기 위해서는
슬라이스 내부의 모습을 살펴보자.
슬라이스를 구성하는 요소는 세가지가 있다.
- ptr | Pointer 포인터
슬라이스의 포인터는 배열의 첫 번재 요소를 가리킨다. - len | Length 길이
- cap | Capacity 용량
용량과 길이가 구분되어 있는 이유는?
- 동적 배열을 구현하기 위해서 길이와 용량을 구분해 놓았다.
- 슬라이스는 길이를 동적으로 늘릴 수 있다.
- 만약 슬라이스의 요소가 늘어난다면 Go runtime은 정해진 알고리즘에 의해 슬라이스의 용량을 늘려놓는다.
Syntax
- 슬라이스 선언
var <sliceName> []<dataType>
var a[]int var b[]string var c[]float32 fmt.Println(a) // [] fmt.Println(reflect.ValueOf(a).Kind()) // slice fmt.Println(b) // [] fmt.Println(c) // []
[]
로 생성된 슬라이스의 길이는 0이다.var a []int fmt.Println(a[0]) // panic: runtime error: index out of range [0] with length 0 // 길이가 0인 슬라이스에 접근하려 했기에 오류가 발생한다.
- 슬라이스에 길이와 용량 설정
make() 함수를 사용하여 값을 넣는다.- make 함수의 첫번째 매개변수 :
[]<dataType>
- make 함수의 두번째 매개변수 : length (생략 불가)
생략시 오류 :missing len argument
- make 함수의 세번째 매개변수 : capacity (생략 가능)
var <sliceName> = make([]int, length, capacity)
var aa = make([]int, 3, 4) fmt.Println("len(aa) : ", len(aa)) // 3 fmt.Println("cap(aa) : ", cap(aa)) // 4
<sliceName> := make([]<dataType>, length, capacity)
capacity는 생략 가능bb := make([]int, 5, 10) fmt.Println(len(bb)) // 5 fmt.Println(cap(bb)) // 10 // bb슬라이스의 길이는 5 용량은 10
- 용량을 생략하면 슬라이스의 길이와 동일하게 설정된다.
// 용량 생략 bbb := make([]int, 3) fmt.Println("len(bbb)", len(bbb)) // 3 fmt.Println("cap(bbb)", cap(bbb)) // 3
- 이렇게 생성된 슬라이스의 요소는 Zero Values를 갖는다.
b1 := make([]int, 3) fmt.Println("b1[0]", b1[0]) // 0 b2 := make([]string, 3) fmt.Println("b2[0]", b2[0]) // "" b3 := make([]bool, 3) fmt.Println("b3[0]", b3[0]) // false
- make 함수의 첫번째 매개변수 :
- 슬라이스 선언 및 초기화
{}
중괄호를 사용하여 값을 할당한다.- 한줄로 선언 및 초기화
<sliceName> :=[]<dataType>{<value1>,<value2>}
cc := []int{1, 2, 3}
- 여러줄로 선언 및 초기화
반드시 Comma(,
)로 마무리<sliceName> := []{ <value1>, <value2>, ... <lastValue>, }
cc := []int{ 1, 2, 3, }
- 한줄로 선언 및 초기화
append() 함수는 슬라이스 동작 원리를 이해하는데 매우 중요하다.
append() 함수를 통해 슬라이스의 끝에 값을 추가할 수 있다.
- 추가할 수 있는 개수에는 제한이 없다.
cc := []int{1, 2, 3}
cc = append(cc, 7, 0, 88)
fmt.Println(cc) // [1 2 3 7 0 88]
slice 에 다른 slice를 append 시킬 때는 ...
를 사용한다.
Syntax
slice1 = append(slice1, slice2...)
- slice1에 slice2를 붙임.
- ellipsis notation(
...
)을 두 번째 매개변수, 추가되는 슬라이스 뒤에 적어준다.
d := []int{10, 100, 1000}
e := []int{11, 111, 1111}
d = append(d, e...) // 슬라이스 d에 e를 추가
fmt.Println(d) // Slice d : [10 100 1000 11 111 1111]
슬라이스는 레퍼런스 타입이다.
슬라이스는 내장된 배열에 대한 Pointer이다.
sliceA에 또 다른 슬라이스인 sliceB를 대입한다면 값이 복사되는 것이 아니라 참조가 된다.
- 슬라이스를 복사하고 싶다면 copy() 함수를 사용할 것.
- sliceB의 요소의 값을 바꾸게 되면 sliceA도 함께 영향을 받는다.
// Reference Type
sliceA := []int{1, 2, 3}
var sliceB []int
sliceB = sliceA // sliceB 에 sliceA 대입
sliceB[0] = 100000 // sliceB의 첫 번째 요소 변경
fmt.Println(sliceA) // [100000 2 3]
fmt.Println(sliceB) // [100000 2 3]
슬라이스의 요소를 모두 복사하고자 한다면 copy()
함수를 사용한다.
Syntax
copy(<copySlice>, <originalSlice>)
- 복사된 slice에는 make함수로 공간을 항당해주어야 한다.
- 공간이 할당되지 않은 빈 슬라이스에는 요소를 복사할 수 없기 때문이다.
// Copy one slice into another slice
s1 := []int{1, 2, 3, 4, 5}
s2 := make([]int, 5) // make 함수로 공간을 할당
copy(s2, s1)
fmt.Println(s1) // [1 2 3 4 5]
fmt.Println(s2) // [1 2 3 4 5]
복사를 완료하였으니 복사된 슬라이스(
s2
)의 요소를 변경한 후 원본 슬라이스(s1
)에 영향을 미치는지 살펴보도록 하자.
// Slice modification
s2[0] = 1001010
fmt.Println("Original s1 : ", s1) // [1 2 3 4 5]
fmt.Println("Coppeid s2 : ", s2) // [1001010 2 3]
복사한 s2의 첫 번째 요소만 변경되었고, 원본 슬라이스는 영향을 받지 않았다.
- 만약 공간을 길이보다 적게 설정한다면 설정한 공간만큼의 요소까지만 복사된다.
// if cap < len
s1 := []int{1, 2, 3, 4, 5}
s2 := make([]int, 3) // make 함수로 공간을 할당
copy(s2, s1)
fmt.Println(s1) // [1 2 3 4 5]
fmt.Println(s2) // [1 2 3]
슬라이스의 일부를 떼어 부분 슬라이스를 만들 수 있다.
Syntax
<subSlice> := <originalSlice>[start_index:end_index]
end index는 인덱스보다 1 많다.
- end index == Total index + 1
- 만약 길이가 7인 index의 슬라이스의 끝까지 참조하고자 한다면
[0:7]
이 아닌[0:8]
이 되어야 한다.
- 만약 길이가 7인 index의 슬라이스의 끝까지 참조하고자 한다면
- 참조이기 때문에 부분 슬라이스의 요소를 바꾸면 배열의 요소도 바뀐다.
s := []int{1, 2, 3, 4, 5, 6, 7, 8}
subS := s[2:5]
fmt.Println(subS) // [3 4 5]
subAll := s[0:8]
fmt.Println(subAll) // [1 2 3 4 5 6 7 8]
subEnd := s[0:len(s)]
fmt.Println(subEnd) // [1 2 3 4 5 6 7 8]
ss := []string{"Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun"}
fmt.Println(ss[1:2]) // [Tue]
fmt.Println(ss[1:1]) // []
fmt.Println(ss[3:5]) // [Thu Fri]
fmt.Println(ss[:]) // [Mon Tue Wed Thu Fri Sat Sun]
fmt.Println(ss[:2]) // [Mon Tue]
fmt.Println(ss[1:]) // [Tue Wed Thu Fri Sat Sun]
- 용량을 설정할 때 기존 슬라이스의 용량을 넘길 수는 없다.
// Sub-slice + Capacity
ss1 := []int{1, 2, 3, 2, 1, 2, 3, 1, 2}
ss2 := ss1[0:6:8] // Capacity : 8
fmt.Println("len(ss2) : ", len(ss2), "cap(ss2) : ", cap(ss2))
// len(ss2) : 6 cap(ss2) : 8
fmt.Println("ss2 : ", ss2) // [1 2 3 2 1 2]
Reverse Slice의 구현
func reverse(s []int) {
for i, j := 0, len(s)-1; i < j; i, j = i+1, j-1 {
s[i], s[j] = s[j], s[i]
}
}