본문 바로가기

IT 일기/GO

Go Slice 사용법, 구조 그리고 append() 원리까지 Deep Dive

728x90

Go array는 정적 배열이고 동적 배열은 Slice이다.

기본적인 사용법은 간단하다.

선언 및 초기화 방법들

var slice []int //크기가 0이고 요소 타입이 int인 slice 생성
var slice []int = []int{1,2,3} // 크기가 3이고 요소로 1,2,3을 갖는 슬라이스
var slice = []int{1, 5: 2, 10: 3} //[1 0 0 0 0 2 0 0 0 0 3] 5번째에 2를 10번째에 3을 넣어서 초기화
var slice = make([]int, 3) // 크기가 3이고 각 요소는 type별 default로 초기화 (int의 경우 0임)\
var slice = make([]int, 3,5) // len == 3 , cap == 5
순회
for i:=0; i < len(slice); i++{ // 요소별 직접 접근
	slice[i] += 10
}

for i, v := range slice { //range를 이용한 순회
			slice[i] = v * 2
}

요소 추가
var slice = []int{1, 2, 3}
slice2 := append(slice, 4) // 기존의 slice에서 4를 추가해 반환해준다.
slice2 = append(slice2,5,6,7,8) //여러개도 가능!
slice2 = append(slice2, slice2...) //slice에 slice 더하기도 가능!
//append는 언제나 새로운 값을 만들어 반환해줌

기본 사용법만 보고 싶다면 여기까지만 보자!

 

 

 

슬라이스의 구조 및 동작 원리를 살펴보자

 

			type SliceHeader struct{
				Data uintptr 	// 실제 배열을 가리키는 포인터
				Len int			// 요소 개수
				Cap int			// 실제 배열 길이 (이놈이 String과 다름) 최대로 몇개까지 쓸 수 있느냐를 알려줌.
			}

이것이 슬라이스의 정체이다. 슬라이스는 그저 array를 참조하는 값일 뿐이다. 그럼 얘가 어떻게 동적으로 배열 길이를 조절하는가? 바로 Len과 Cap을 이용해 조절한다.

 

Data부터 살펴보겠다.

		var s []int
		a := [5]int{1, 2, 3, 4, 5}
		s = a[:]
		fmt.Println(a)
		fmt.Println(s)
		a[0] = 120
		fmt.Println(a)
		fmt.Println(s)

이 경우

[1 2 3 4 5]
[1 2 3 4 5]
[120 2 3 4 5]
[120 2 3 4 5]

이와 같은 값이 도출된다. 즉, slice s가 array a를 참조한다는 의미이다! slice struct의 필드값인 Data가 array의 시작주소를 갖고있다는 것이다~

 

그럼 len과 cap에 대해 함 봐보자..

		... (위의 코드에서 이어쓰는 상황) ...
        fmt.Println("len s:", len(s), "cap s:", cap(s))

"len s: 5 cap s: 5" 라는 결과가 나온다. 아직 뭔지 감이 안온다. 좀만 만져보자!

		fmt.Println("len s:", len(s), "cap s:", cap(s))
		_ = append(s, 3)
		fmt.Println("len s:", len(s), "cap s:", cap(s))

len s: 5 cap s: 5
len s: 5 cap s: 5

ㅇㅅㅇ???

이게 무슨 일인가? 분명 s에 append를 했는데 결과가 같다! 값이 s에 append가 되지 않은 것이다!! 추측을 조금 해보면 append의 과정에서 받는 반환 값에 기존의 s에 3을 append한 slice가 반환되는것 같다! 그럼 저 값을 새로운 변수로 받아서 보자!

 

		fmt.Println("len s:", len(s), "cap s:", cap(s))
		s1 := append(s, 3)
		fmt.Println("len s1:", len(s1), "cap s1:", cap(s1))

len s: 5 cap s: 5
len s1: 6 cap s1: 10

ㅇㅁㅇ!! 추측이 맞았다! append는 기존의 s가 아닌 새로운 slice를 만들어 반환해준 것이다! 근데 기존의 s와 다른점이 보인다. 바로 len의 값이 5 -> 6으로 cap의 값이 5 -> 10으로 늘어났다는 것이다.

len은 array의 길이이니 6인걸 이해하겠는데.. 저 cap은 뭔데 2배나 늘어난 것이냐? 신기하니까 한번만 더 append 해보자 히히히히

		fmt.Println("len s:", len(s), "cap s:", cap(s))
		s1 := append(s, 3)
		fmt.Println("len s1:", len(s1), "cap s1:", cap(s1))
		s2 := append(s1, 4)
		fmt.Println("len s2:", len(s2), "cap s2:", cap(s2))

len s: 5 cap s: 5
len s1: 6 cap s1: 10
len s2: 7 cap s2: 10

두둥탁!

이번엔 cap이 유지됐다... len만 늘어난걸로 보아 len은 내가 흔히 아는 그 len이 맞는것 같다.(배열의 길이) 그럼 저 염병할 cap은 뭘까? 

 

cap은 capacity의 줄임말로 해당 slice struct가 최대로 가질 수 있는 len의 길이라고 한다. 즉! 저 s1,s2는 최대 len이 10까지 저장할 수 있는 slice이고 s는 5까지 저장할 수 있는 slice였던 것이다.

 

아 그럼 아까 s는 len == 5, cap == 5 였으니까 더이상 값을 append할 수 없었던거구나!!

그래서 새롭게 cap == 10인 더 큰집을 지어서 나에게 return을 해준것이었어~~ 그럼 이런 저럼 만짐을 조금 해볼까...?

 

		fmt.Println("len s:", len(s), "cap s:", cap(s))
		s1 := append(s, 3)
		fmt.Println("len s1:", len(s1), "cap s1:", cap(s1))
		s2 := append(s1, 4)
		fmt.Println("len s2:", len(s2), "cap s2:", cap(s2))
		s1[0] = 12
		fmt.Println(s[0], s1[0], s2[0])

len s: 5 cap s: 5
len s1: 6 cap s1: 10
len s2: 7 cap s2: 10
120 12 12

오호 s1의 array와 s2의 array는 같은 array이구나!! 그럼 이러면 어떻게 될까?

 

		fmt.Println("len s:", len(s), "cap s:", cap(s))
		s1 := append(s, 3)
		fmt.Println("len s1:", len(s1), "cap s1:", cap(s1))
		s2 := append(s1, 4)
		fmt.Println("len s2:", len(s2), "cap s2:", cap(s2))
		s1[0] = 12
		fmt.Println(s[0], s1[0], s2[0])
		s2 = append(s2, 5)
		fmt.Println(s1, s2)

en s: 5 cap s: 5
len s1: 6 cap s1: 10


len s2: 7 cap s2: 10
120 12 12
[12 2 3 4 5 3] [12 2 3 4 5 3 4 5]

오... append는 영향을 안받는다... s2에 append한 내용은 s1의 array에 append되지 않았다...

=> 아 그럼 append는 할때마다 len이 cap을 초과하는 상황이 아니더라도 새로운 slice를 만들어서 반환해주나보다!!!

그럼 다시 한번 값을 바꿔서 확인해보자!!!

 

		fmt.Println("len s:", len(s), "cap s:", cap(s))
		s1 := append(s, 3)
		fmt.Println("len s1:", len(s1), "cap s1:", cap(s1))
		s2 := append(s1, 4)
		fmt.Println("len s2:", len(s2), "cap s2:", cap(s2))
		s1[0] = 12
		fmt.Println(s[0], s1[0], s2[0])
		s2 = append(s2, 5)
		fmt.Println(s1, s2)
		s2[0] = 22
        fmt.Println(s1, s2)
		fmt.Println("len s1:", len(s1), "cap s1:", cap(s1))
		fmt.Println("len s2:", len(s2), "cap s2:", cap(s2))

len s: 5 cap s: 5
len s1: 6 cap s1: 10
len s2: 7 cap s2: 10
120 12 12
[12 2 3 4 5 3] [12 2 3 4 5 3 4 5]
[22 2 3 4 5 3] [22 2 3 4 5 3 4 5]

len s1: 6 cap s1: 10
len s2: 8 cap s2: 10

...???????

값이 바뀐다!! 같은 array이다!! 뭐야!! 아직 발견하지 못한 새로운 무언가가 있나보다! 아직 하지 않았던 짓을 해보자! 

 

		fmt.Println("len s:", len(s), "cap s:", cap(s))
		s1 := append(s, 3)
		fmt.Println("len s1:", len(s1), "cap s1:", cap(s1))
		s2 := append(s1, 4)
		fmt.Println("len s2:", len(s2), "cap s2:", cap(s2))
		s1[0] = 12
		fmt.Println(s[0], s1[0], s2[0])
		s2 = append(s2, 5)
		fmt.Println(s1, s2)
		s2[0] = 22
		fmt.Println(s1, s2)
		fmt.Println("len s1:", len(s1), "cap s1:", cap(s1))
		fmt.Println("len s2:", len(s2), "cap s2:", cap(s2))
		s1 = append(s1, 8)
		fmt.Println(s1, s2)

... 중략

len s1: 6 cap s1: 10
len s2: 8 cap s2: 10
[22 2 3 4 5 3 8] [22 2 3 4 5 3 8 5]

????????????????????????????????????????

s1에서 append된 값이 s2에 영향을 미친다(s2의 요소가 8로 변함). 하지만 분명 아까 s2에 append된 값은 s1에 영향을 미치지 않았다. 이게 어찌된 일인가!!!

 

차이점은 바로 len이다. 단순하게 slice가 참조하고 있는 배열의 길이라고만 생각할게 아니라, slice가 참조해야 하는 배열의 끝! 이라고 생각하면 이 상황이 이해하기 쉬울것 같다.

 

그림으로 정리하면 이렇다.

s1, s2모두 크기 10짜리 배열 하나를 공유해서 참조하고 있는 상황이다. 하지만 차이점인 len으로 인해

s1은 8까지만 보이는거고 (s2의 작업이 실제로 적용은 됐지만 len 밖의 일이라 보이지 않음)

s2는 5까지 보이는 것이다!

 

정리하자면 다음과 같다!

1. append()를 통해 slice에 값을 추가 하는 과정에서 len의 값이 변한다

2. 새로운 slice 구조체를 생성해 return 한다. 

    a. len > cap

        기존의 slice에서 참조하는 array의 reference가 다르다. cap이 기존의 cap보다 2배 증가한다. len이 증가한다.

    b. len <= cap

        기존의 slice에서 참조하는 array의 reference가 같음. cap도 기존의 cap과 같다. len이 증가한다.

 

이렇다!!

 

Go의 동적 배열인 Slice... 너란놈 알기 어렵다!

728x90

'IT 일기 > GO' 카테고리의 다른 글

Go 채널이란?  (0) 2023.04.02
What is GoRoutine?  (0) 2023.03.31
Understand SOLID in Go  (0) 2023.03.24
vscode Terminal 설정 변경 (Go에서 sqlite3 사용하기)  (0) 2023.03.23
HTTP/1.1 persistent-connection 사용 예시  (0) 2023.03.18