본문 바로가기
golang

golang array와slice

by PudgeKim 2021. 3. 23.

이번 글에서는 golang에서 햇갈릴만한 array와 slice의 차이점을 알아보겠습니다.

1
2
3
4
5
6
func main() {
    var a = [3]int{1,2,3}
    b := a
    b[1] = 22
    
}
cs

[]안에 숫자를 넣으면 길이가 늘어나지 않는 array가 됩니다.

위 코드의 경우 b의 값을 바꾸었는데 a[1]의 값도 바뀔까요?

a의 값은 바뀌지 않습니다. array의 경우 := 연산자를 이용했을 때 call by value 형식으로 넘어가기 때문입니다.

 

1
2
3
4
5
6
7
func main() {
    var a = []int{1,2,3}
    b := a
    b[1] = 22
 
 
}
cs

이번에는 slice 코드입니다. 첫번째 코드와 다른점은 []안에 숫자가 없다는 점입니다.

slice의 경우에는 := 연산자를 이용했을 때 call by reference 형식으로 넘어가기 때문에 a[1]도 22로 바뀝니다.

정확히는 a와 b 둘다 같은 배열을 포인팅하고 있기 때문입니다.

 

1
2
3
4
5
6
func main() {
    var a = []int{1,2}
    b := append(a, 3)
    b[1] = 22
 
}
cs

slice는 append 함수를 이용해서 변수를 추가 시킬 수 있습니다.

그런데 위 코드에서는 a[1]은 바뀌지 않습니다.

왜냐하면 slice는 cap과 len이 있는데 append를 해야하는데 cap(수용량)이 꽉 찰 경우 길이를 늘린 새로운 slice를 만들고 그 새로운 slice를 쓰기 때문입니다.

즉, b는 a가 사용하던 배열이 아니라 길이를 늘린 새로 만들어진 배열을 포인팅하고 있기 때문에 서로 영향을 끼치지 않습니다.

이렇게 수용량에 따라 배열이 새로 만들어지기도 하기때문에 주의해야합니다.

 

 

slice는 make함수로도 만들 수 있습니다.

s1 := make([]int, 5, 10) 이런식으로 말입니다. 여기서 5는 현재 길이를, 10은 수용량을 나타냅니다.

위와 같이 선언할 경우 s1은 [0, 0, 0, 0, 0] 이렇게 초기화 됩니다. 그리고 append 함수를 통해 길이를 늘려나가다가 11번째 원소를 집어넣어야 하면 그때 수용량이 더 늘어나게 됩니다.

make 함수의 두번째 인자에 숫자만큼 초기화가 되기 때문에 s1[3] = 3 이런식의 접근은 가능하지만 s1[7] = 10 같은 접근시 오류가 나게 됩니다.

 

또한 copy 함수를 이용하여 slice를 복사할 수 있습니다.

그런데 s1은 길이가 6인데 s2는 길이가 3입니다. 어떻게 될까요?

결과는 [1, 2, 3]으로 선언한 길이 만큼만 복사되어집니다.

** 위의 예에서 copy 함수로 복사를 하였는데 s1과 s2는 서로 다른 slice입니다. 즉, s2[1] = 111 이렇게 바꾸더라도 s1에는 영향을 끼치지 않습니다. 즉, copy 함수는 서로 다른 곳을 가리키는 배열을 만들어줍니다.

** 그러나 아래 경우는 다릅니다.

위 코드의 결과 값은 아래와 같습니다.

slicing을 이용한 후 s2에 전달하면 원본 slice에도 영향을 끼치게 됩니다.

 

이번에는 slice를 함수 인자로 쓰는 경우를 알아보겠습니다.

1
2
3
4
5
6
7
8
9
func ChangeSlice(s []int) {
    s[1] = 22
}
 
func main() {
    s := []int{1,2,3}
    ChangeSlice(s)
 
}
cs

위 코드의 경우 s[1]의 값은 22로 바뀌게 됩니다. (원본 슬라이스도 값이 변경됩니다.)

즉, 함수 내에서 slice값을 바꾸기만 하는 목적이라면 그냥 슬라이스를 넘겨주면 됩니다.

 

1
2
3
4
5
6
7
8
9
10
func AppendSlice(s []int) {
    s = append(s, 22)
}
 
func main() {
    s := []int{1,2,3}
    AppendSlice(s)
    fmt.Println(s)
 
}
cs

이 예제가 중요합니다.

함수에서 append를 하고 append한 slice를 받는 변수도 s이므로 s는 [1 2 3 22]가 될 것 같지만 s는 [1 2 3]으로 바뀌지 않습니다.

golang은 함수 인자가 다 pass by value이기 때문에 함수 인자로 받은 s도 copy 된것이기 때문에 영향을 끼치지 못합니다.

조금 더 자세히 설명하자면 slice는 struct 구조로 이루어져 있는데 len, cap, 그리고 배열을 포인팅하는 pointer 이렇게 3가지로 이루어져 있습니다. AppendSlice의 인자인 s는 main의 s를 복사한 slice 구조체입니다.

함수 내에서 append를 하면 함수 내에 있는 s가 [1 2 3 22]가 되고 함수가 끝나면 이 복사된 s는 사라져버리기 때문에
main에 있는 s는 그대로 [1 2 3]으로 남게 됩니다. 

main에 있는 s는 구조체 내에 len이 업데이트되지 않았기 때문에 그대로 [1 2 3]만 존재하는줄 압니다.
즉, main에 있는 s와 함수에 있는 s의 slice header 정보가 다르기 때문에 차이가 발생합니다.

 

함수내에서 바꾸고 싶다면 s *[]int 이렇게 포인터형식으로 받던가 아니면 아래처럼 s를 return해서 return한 값을 가리키게 하면 됩니다.

1
2
3
4
5
6
7
8
9
10
11
func AppendSlice(s []int) []int {
    s = append(s, 22)
    return s
}
 
func main() {
    s := []int{1,2,3}
    s = AppendSlice(s)
    fmt.Println(s)
 
}
cs

포인터 형식으로 받는 방법보다는 위 방법이 더 권장됩니다.

'golang' 카테고리의 다른 글

golang 인터페이스 활용  (0) 2021.03.25
golang의 is a 관계  (0) 2021.03.25
golang과 함께 알아보는 쿠키  (0) 2020.12.09
golang 정렬하기  (0) 2020.10.22
golang에서 HandlerFunc란?  (0) 2020.10.19