Tucker의 Go 언어 프로그래밍 책과 유튜브를 통해 학습 중입니다.
★슬라이스(Slice)★
- Go에서 제공하는 동적 배열 타입 → Go에서 제공하는 배열을 가리키는 포인터 타입
→ 동적(Dynamic), 프로그램 실행 도중에 결정(실행도중에 바뀔 수 있는 값)
정적(Static), 코드를 기계어로 바꿀 때 결정(실행도중에 절대 바뀌지 않는 값)
var slice []int
// var <변수명> <타입>
- 타입의 [](가운데) 사이즈를 적지 않고 뒤에 요소 타입을 적음(ex. int, str 등)
package main
import "fmt"
func main() {
var slice []int
if len(slice) == 0 {
fmt.Println("slice is empty", slice)
}
slice[1] = 10
fmt.Println(slice)
}
// 결과
slice is empty []
panic: runtime error: index out of range [1] with length 0
goroutine 1 [running]:
main.main()
/tmp/sandbox2626503554/prog.go:12 +0x7b
- 실행 도중에 에러가 발생되고, index의 범위를 벗어났다는 에러를 볼 수 있음(두 번째 값에 접근하다 에러)
- 슬라이스의 길이가 0인데(요소가 하나도 없음), slice[1] 두번째 요소 값에 접근하였기 때문에 runtime 에러 발생
package main
import "fmt"
func main() {
slice := []int{1, 2, 3}
if len(slice) == 0 {
fmt.Println("slice is empty", slice)
}
slice[1] = 10
fmt.Println(slice)
}
// 결과
[1 10 3]
- slice에서 int형으로 초기값은 3개의 요소를 가지고 있고, 동적 배열이기 때문에 길이는 줄거나 늘어날 수 있음.
- 길이가 0이 아니고 3이기 때문에 두 번째 값 (slice[1] = 10)이 변경된 것을 확인할 수 있음.
슬라이스 초기화
- 배열처럼 {}을 사용해 요솟값을 지정하는 방법
var slice1 = []int{1, 2, 3}
var slice2 = []int{1, 5:2, 10:3}
- slice1로 초기화 하면 1, 2, 3을 값으로 갖는 슬라이스가 됨
- slice2는 첫 번째 요소가 1이 되고, 인덱스 5가 2, 인덱스 10이 3이 되어 총 11개의 요소를 갖는 슬라이스가 됨.
(= [1000200003])
✓ slice1을 초기화 할 때, [...]int{1, 2, 3}으로 하게되면 이는 배열이 되니, 슬라이스를 사용할 땐 []int{1, 2, 3}으로 사용!
var slice = make([]int, 3)
- Go의 내장 함수인 make()를 사용하여 초기화할 수 있음(Compress 타입을 만들어 주는 함수).
- int 타입 슬라이스를 만드는데 3개의 요소를 가진 슬라이스를 만든다는 의미(기본값 0).
→ slice := []int{0, 0, 0}과 같음
package main
import "fmt"
func main() {
slice := make([]int, 3) // make 함수 사용
if len(slice) == 0 {
fmt.Println("slice is empty", slice)
}
slice[1] = 10
fmt.Println(slice)
}
// 결과
[0 10 0]
슬라이스 요소 추가 - append()
- append()도 내장함수이며, slice에 값을 추가할 때 사용
- 슬라이스는 요소를 추가해 길이를 늘릴 수 있음.
package main
import "fmt"
func main() {
slice := []int{1, 2, 3}
slice2 := append(slice, 4)
fmt.Println(slice)
fmt.Println(slice2)
}
// 결과
[1 2 3]
[1 2 3 4]
- slice는 요소가 3개인 슬라이스
- slice2는 append()함수를 사용해 slice에 1개의 요소를 추가하여 새로운 슬라이스를 만들어서 반환(요소 4개)
✓ append는 슬라이스에 요소를 추가하여 새로운 슬라이스를 반환함(기존의 슬라이스가 바뀌지 않음)
✓ 기존 슬라이스에 추가하고 싶으면, 반환하는 값이 slice2가 아닌 slice = append(slice, 4)를 사용하면 됨
★ (주의) append()는 새로운 슬라이스를 반환 ★
여러 값 추가
- append()를 사용하여 값을 하나 이상 추가할 수 있음
// 기존 슬라이스
var slice = []int{1, 2, 3}
// 여러 값 추가
slice = append(slice, 3, 4, 5, 6, 7)
// 결과
[1 2 3 3 4 5 6 7]
- 첫 번째 인수로 들어온 슬라이스의 값을 변경하는게 아닌, 요소가 추가된 새로운 슬라이스를 반환
★슬라이스 동작원리★
- python의 슬라이스와 다름.
- 내부를 보면 struct 타입으로 구성되어 있음.
- 슬라이스는 실제 배열에서 현재 내가 사용하고 있는 정도를 잘라낸(슬라이스)한 개념으로 이해
→ 1) data 필드는 실제 배열을 나타내는 가르키는 포인터
2) len 필드는 길이를 나타냄 (내가 현재 몇 개를 사용하고 있는가)
3) cap 필드는 capacity의 약자로 최대공간 또는 최대길이 (최대 몇 개까지 사용할 수 있는지(=maximum))
type SliceHeader struct {
Data uintptr // 실제 배열을 가르키는 포인터
Len int // 요소 개수
Cap int // 실제 배열의 길이
}
make() 함수를 이용한 선언
- slice는 len이 3이고, cap이 3으로 총 배열 길이가 3, 요소 개수도 3이 됨.
- 각 요소는 기본 값인 0이 됨.
var slice = make([]int, 3)
✓ 인자를 두 개 기재하여 사용하는 make()
- 첫 번째 숫자가 Len, 두 번째 숫자가 Cap이 됨.
- 5개짜리 배열을 만들어 3개만 사용하고, 나머지 2개는 나중에 추가될 요소를 위해 비워둔 것으로 이해!
var slice2 = make([]int, 3, 5)
package main
import "fmt"
func changeArray(array2 [5]int) {
array2[2] = 200
}
func changeSlice(slice2 []int) {
slice2[2] = 200
}
func main() {
array := [5]int{1, 2, 3, 4, 5}
slice := []int{1, 2, 3, 4, 5}
changeArray(array)
changeSlice(slice)
fmt.Println("array:", array)
fmt.Println("slice:", slice)
}
// 결과
array: [1 2 3 4 5]
slice: [1 2 200 4 5]
- Go의 특징 중인 하나는 일관성으로 어떤 상황에서도 똑같이 작동함.
- Go에서는 모든 값의 대입은 복사로 일어남.
✓ 동작 차이의 원인
* Array의 경우
- 함수를 호출하면, 좌측은 r-value로 값으로 동작하며 array와 array2는 다른 변수/인스턴스임
- (array2 [5]int)는 대입연산자를 사용한 것과 같음 (array2 = array)
= array2는 함수 인자로 들어간 array의 대입연사자를 사용하여 만든 값과 같음
= array와 array2는 [5]int 로 동일한 타입이라 사용할 수 있음
- array와 array2는 40bit(8bit *5)로 별도의 인스턴스(메모리 공간)를 가지고 있음
- array에 있는 메모리 공간에 있는 값을 array2에 그대로 복사하는 것 = 서로 다른 메모리 공간
따라서, array2의 값을 바꾸면 array2의 인스턴스 3번째의 값이 200으로 바뀌고, array는 그대로 있는 것!
* Slice의 경우
- slice의 내부는 포인터, len, cap 세 개의 필드를 갖는 구조체
- 포인터 메모리 주소는 8bit 길이이며, len과 cap은 각각 int 타입으로 8bit라 해당 슬라이스의 크기는 24bit가 됨.
= 배열의 길이와 관계 없이 항상 구조체 사이즈로 copy가 됨!!
- 두 개가 동일한 배열을 가리키고 있음.
- 가리키고 있는 배열의 세 번째 값을 200으로 바꿨기 때문에 array랑 다른 값이 출력됨.
따라서, slice는 slice의 struct 정보가 복사되는 것이고 같은 배열을 가르키고 있기 때문에 변경된 배열의 값이 출력!
★append() 동작원리★
- append()의 기본적인 정의는 슬라이스에 요소를 추가한 새로운 슬라이스를 반환이지만,
실제로는 기존 슬라이스가 바뀔 수도 있고, 아닐 수도 있음!
= 기존 배열에 4를 추가해서 반환할 수도 있다는 것!
1. 처음에 들어오면 빈공간이 충분하다면, 빈공간에 요소를 추가하여 반환
2. 빈공간이 충분하지 않다면, 새로운 배열을 할당하여 기존 슬라이스가 가리키고 있는 배열에서 복사 후 요소 추가후 반환
✓ 빈 공간 확인
* 빈 공간은 cap - len을 한 공간
- cap이 5이고, len이 3인 배열에 요소를 추가할 때, if문으로 cap - len >= 요소 개수를 실행
true이면 기존 배열에 추가하고, false라면 새로운 공간을 할당하여 기존 len 개수만큼 복사하고 나머지에 요소 추가
만약, 기존 슬라이스에 요소를 추가하여 slice2를 새로 만들었는데, slice의 두 번째 값을 바꾼다면?
// slice1 값 변경
slice1[1]=100
→ 같은 배열을 가르키고 있기 때문에 slice2의 두 번째 값도 100을 리턴
slice1을 바꿨으나, slice2가 바뀌는 상황이 생김
만약, 공간이 없어 새로운 슬라이스를 만들게 되었을 때, slice1의 값을 변경한다면?
→ slice1의 배열은 바뀌지만, slice2의 값은 변경되지 않음(서로 다른 배열이기 때문)
append가 빈 공간이 있는지 없는지에 따라 배열을 만들거나 만들지 않기 때문에 기존 배열이 바뀔수도 바뀌지 않을 수도 있음
'Language > Golang' 카테고리의 다른 글
Golang (Go언어) 슬라이스(Slice) 2/2 (2) | 2024.11.20 |
---|---|
Golang (Go언어) 패키지(Package) (6) | 2024.11.09 |
Golang (Go언어) 문자열(String) (4) | 2024.11.08 |
Golang (Go언어) 포인터(Pointer) (3) | 2024.11.07 |
Golang (Go언어) 구조체(Structure) (9) | 2024.11.05 |