go-slice

kyunhyun94·2021년 12월 30일

💯Slice

  • 슬라이스는 배열과 같지만,길이가 고정되어 있지 않으며 동적으로 크기가 늘어난다
  • 배열과는 달리 레퍼런스 타입
var a []int // int형 슬라이스 선언
  • 슬라이스는 make 함수를 사용하여 공간을 할당해야 값을 넣을 수 있다
  • 슬라이스 선언과 동시에 make 함수를 사용하면 대괄호와 자료형은 생략할 수 있다
  • 일반 변수와 마찬가지로 :=를 사용하여 var를 생략할 수 있다
var a []int = make([]int, 5) // make 함수로 int형에 길이가 5인 슬라이스에 공간 할당
var b = make([]int, 5)       // 슬라이스를 선언할 때 자료형과 [] 생략
c := make([]int, 5)          // 슬라이스를 선언할 때 var 키워드, 자료형과 [] 생략
  • 슬라이스에 공간을 할당할 것이므로 make 함수의 첫 번째 매개변수에는 대괄호에 길이를 설정하지 않은 상태로 자료형을 지정한다 그리고 두 번째 매개변수에는 슬라이스의 길이를 설정한다 이렇게 슬라이스를 생성하고 공간을 할당하면 슬라이스의 요소는 모두 0으로 초기화된다

  • 슬라이스를 생성하면서 값을 초기화하려면 { } (중괄호)를 사용한다. 중괄호에서 값은 한 줄로 나열해도 되고 여러 줄로 나열해도 (여러 줄로 나열할 때는 마지막 요소에도 콤마를 붙여준다). 슬라이스이므로 대괄호에는 길이를 설정하지 않는다

a := []int{32, 29, 78, 16, 81} // 슬라이스를 생성하면서 값을 초기화

b := []int{
	32,
	29,
	78,
	16,
	81,  // 여러 줄로 나열할 때는 마지막 요소에 콤마를 붙임
}
var s = make([]int, 5, 10) // 길이가 5이고 용량이 10인 슬라이스 생성
  • 미리 슬라이스의 용량을 크게 할당하면 요소가 추가될 때마다 메모리를 새로 할당하지 않아도 되므로 성능상 이점이 있다
  • 처음부터 메모리 공간을 많이 차지게 된다. 반대로 슬라이스 용량을 적게 할당하면 처음부터 메모리 공간은 적게 차지하지만, 요소가 추가될 때마다 메모리를 새로 할당하게 되므로 성능이 떨어질 수 있다
a := make([]int, 5, 10)

fmt.Println(len(a)) // 길이는 5
fmt.Println(cap(a)) // 용량은 10
5
10
a := make([]int, 5, 10) // 길이가 5이면 a[0], a[1], a[2], a[3], a[4]가 생성

fmt.Println(a[4]) // 0: make 함수를 사용하면 슬라이스의 요소는 모두 0으로 초기화
fmt.Println(a[5]) // 길이를 벗어난 인덱스에 접근했으므로 런타임 에러 발생
fmt.Println(a[8]) // 길이를 벗어난 인덱스에 접근했으므로 런타임 에러 발생
0
panic: runtime error: index out of range

💯슬라이스에 값 추가

  • append(슬라이스, 값1, 값2, 값3)
a := []int{1, 2, 3}

a = append(a, 4, 5, 6)

fmt.Println(a) // [1 2 3 4 5 6]
  • 첫 번째 매개변수에는 값을 추가할 슬라이스를 지정하고, 두 번째 매개변수부터는 추가할 값을 나열한다 추가할 수 있는 값의 개수에는 제한이 없다

슬라이스에 다른 슬라이스를 붙이려면 append 함수를 사용할 때 ...을 사용한다

v
a := []int{1, 2, 3}
b := []int{4, 5, 6}

a = append(a, b...) // 슬라이스 a에 슬라이스 b를 붙일 때는 b...을 씀

fmt.Println(a) // [1 2 3 4 5 6]
[1 2 3 4 5 6]
  • append 함수의 두 번째 매개변수에서 슬라이스 b에 ...를 붙였으며, append 함수는 가변인자 함수이므로 ...를 사용하여 슬라이스에 들어있는 요소를 각각 넘겨준다. …는 가변인자 함수를 만들때도 사용할 수 있고 가변인자 함수에 값을 넘겨줄 때도 사용할 수 있다

💯레퍼런스 타입

  • 슬라이스는 레퍼런스 타입!!, 내장된 배열에 대한 포인터이므로 슬라이스끼리 대입하면 값이 복사되지 않고 참조(레퍼런스)만 한다
a := [3]int{1, 2, 3}
var b [3]int

b = a          // 배열의 요소가 모두 복사됨
b[0] = 9       // b[0]에 9를 대입하면 b의 첫 번째 요소만 바뀜

fmt.Println(a) // [1 2 3]
fmt.Println(b) // [9 2 3]
a := []int{1, 2, 3}
var b []int    // 슬라이스로 선언

b = a          // a를 b에 대입해도 요소가 모두 복사되지 않고 참조만 함
b[0] = 9       // 슬라이스는 참조이므로 a[0], b[0]의 값이 모두 바뀜

fmt.Println(a) // [9 2 3]
fmt.Println(b) // [9 2 3]
  • 슬라이스 a를 슬라이스 b에 대입하였다. 그리고 b[0]에 값을 대입한 뒤 값을 출력해보면 a[0]과 b[0] 모두 값이 바뀐다. 슬라이스는 레퍼런스 타입이라 값이 복사되지 않고 참조만하기 때문이다.

  • 함수의 매개변수에 배열을 넘기면 복사가 되지만 슬라이스를 넘기면 참조만 하게된다. 따라서 함수 안에서 슬라이스의 요소를 변경하면 함수 바깥에 있는 슬라이스도 값이 바뀐다

💯슬라이스 복사하기

copy(복사될 슬라이스, 원본 슬라이스)

a := []int{1, 2, 3, 4, 5}
b := make([]int, 3) // make 함수로 공간을 할당

copy(b, a)     // 슬라이스 a의 요소를 슬라이스 b에 복사

fmt.Println(a) // [1 2 3 4 5]
fmt.Println(b) // [1 2 3]: 슬라이스 b의 길이는 3이므로 a의 요소 3개만 복사됨
  • 복사될 슬라이스 b에는 make 함수로 공간을 할당, 공간을 할당하지 않은 빈 슬라이스에는 요소를 복사할 수 없다. 여기서 슬라이스 b는 길이가 3이므로 슬라이스 a의 요소 3개만 복사가 된다.

슬라이스를 복사하였으므로 복사된 슬라이스의 요소를 바꾸어도 원본 슬라이스는 바뀌지 않는다.

a := []int{1, 2, 3}
b := make([]int, 3)

copy(b, a)     // 슬라이스를 복사하였으므로
b[0] = 9       // b[0]만 바뀌고, a[0]은 바뀌지 않음

fmt.Println(a) // [1 2 3]
fmt.Println(b) // [9 2 3]

🌱슬라이스 메모리

  • 왜 슬라이스는 길이와 용량이 따로 구분되어 있을까? 앞서 슬라이스는 길이를 동적으로 늘릴 수 있다고 했다.

  • 먼저 길이가 5이며 용량이 5인 슬라이스를 생성한다. 그리고 append 함수를 사용하여 값 두개를 추가한다

a := []int{1, 2, 3, 4, 5}

fmt.Println(len(a), cap(a)) // 5 5: 길이가 5이며 용량이 5인 슬라이스

a = append(a, 6, 7)         // 슬라이스 a에 값 6, 7을 추가

fmt.Println(len(a), cap(a)) // 7 10: 길이가 7이며 용량이 10인 슬라이스, 용량이 늘어남!
  • 처음에는 슬라이스 a의 길이가 5이며 용량도 5이다. append 함수로 값 두개를 추가했으므로 길이는 7이며, 그리고 용량을 보면 10으로 늘어나있다. 이처럼 Go 언어는 동적 배열을 구현하기 위해 길이와 용량을 구분하고 있다. 따라서 슬라이스의 요소가 늘어나면 Go 런타임은 정해진 알고리즘에 의해 슬라이스의 용량을 늘리는 것이다..

💯부분 슬라이스 만들기

슬라이스는 기존 슬라이스에서 일정 위치를 지정하여 부분 슬라이스를 만들 수 있다.

a := []int{1, 2, 3, 4, 5}

b := a[0:5]    // a의 인덱스 0부터 5까지 참조

fmt.Println(a) // [1 2 3 4 5]
fmt.Println(b) // [1 2 3 4 5]
  • 부분 슬라이스는 슬라이스의 시작 인덱스부터 끝 인덱스까지 일부만 참조합니다. 여기서 끝 인덱스는 실제 인덱스보다 1이 더 많다. 따라서 길이가 5인 슬라이스를 처음부터 끝까지 모두 참조하려면 [0:4]가 아닌 [0:5]가 된다. 또한, 부분 슬라이스를 만들더라도 슬라이스의 요소는 복사되지 않으므로 부분 슬라이스의 내용을 바꾸면 기존 슬라이스의 내용도 바뀐다.

  • 시작 인덱스와 끝 인덱스를 설정하여 일부만 참조

a := []int{1, 2, 3, 4, 5}

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

fmt.Println(a[:])        // [1 2 3 4 5]
fmt.Println(a[0:])       // [1 2 3 4 5]
fmt.Println(a[:5])       // [1 2 3 4 5]
fmt.Println(a[0:len(a)]) // [1 2 3 4 5]

fmt.Println(a[3:])       // [4 5]
fmt.Println(a[:3])       // [1 2 3]
fmt.Println(a[1:3])      // [2 3]
a := [5]int{1, 2, 3, 4, 5} // 배열 선언

b := a[:2]     // 배열 a의 일부를 부분 슬라이스로 참조
b[0] = 9       // !!!!부분 슬라이스는 참조이므로 a[0], b[0]의 값이 모두 바뀜

fmt.Println(a) // [9 2 3 4 5]
fmt.Println(b) // [9 2]

!! 부분 슬라이스는 참조이므로 모두 값이 바뀜

💯슬라이스[시작인덱스:끝인덱스:용량]

a := []int{1, 2, 3, 4, 5, 6, 7, 8}

b := a[0:6:8] // 인덱스 0부터 6까지 가져와서 부분 슬라이스로 만들고 용량을 8로 설정

fmt.Println(len(b), cap(b)) // 6 8: 길이가 6이며 용량이 8인 슬라이스
fmt.Println(b)              // [1 2 3 4 5 6]
profile
일하기싫어

0개의 댓글