Slice

컴파일타임에 데이터 크기가 고정되어 런타임에 변경이 되지 않는 일반 배열과 달리 변경이 가능한 동적 배열 타입을 slice라고 한다. 정확하게 얘기하면 go에서 제공하는 배열을 가리키는 포인터 타입이다.


1. 선언 방법

var a []int         //길이가 0인 slice
fmt.Println(len(a))   //0

b := []int{1,2,3}   //길이가 3인 slice
fmt.Println(len(b))   //3

var c = []int{1, 5:2, 9:3}    //길이가 10인 slice
fmt.Println(c)    //[1 0 0 0 0 2 0 0 0 3]

배열선언방식과 비슷하지만 [] 안에 배열크기를 지정하지 않으면 slice로 선언이 된다.


1) make()이용한 초기화

var a = make([]int,3)  //길이가 3인 slice
a[1] = 1
fmt.Println(a)    //[0 3 0]



2. 순회

slice := []int{1,2,3}

for i :=0; i < len(slice); i++ {
  fmt.Print(slice[i]," ")   //1 2 3
}

fmt.Println()
for _,v := range slice{
  fmt.Print(v," ") //1 2 3
}



3. 슬라이스 요소 추가

배열과 달리 동적배열인 slice는 append()함수를 이용하여 배열에 값을 추가해줄 수 있다.

slice := int[]{1,2,3}
slice2 := append(slice,4)

fmt.Println(slice)    //[1 2 3]
fmt.Println(slice2)   //[1 2 3 4]

//기존의 변수에 값을 추가해주고 싶다면 아래와 같이 이용
slice = append(slice,4)
fmt.Println(slice)    //[1 2 3 4]

append() 함수는 기존의 slice에 값을 추가해주는 것이 아니라 값을 추가한 slice를 반환해주는 함수라는 점을 명심해야한다.

append()는 빈공간이 충분하다면 빈공간에 요소가 추가되고 충분하지 않다면, 새로운 배열을 생성해서 반환하게 되고 이때 빈공간은 cap-len이다.



4. 동작원리

실제 값을 가르키는 pointer와 len으로 구성되어있다고 앞서 설명한 string처럼 slice도 실제 배열을 가르키는 포인터와 len으로 구성되어 있으며 cap이라고 capacity의 약자이며 최대 배열의 크기를 가르키는 변수가 한개가 더 존재하는 구조체로 구성되어 있다.

type SliceHeader struct{
    Data uintptr
    Len  int
    Cap  int
}

위 구조체가 slice의 구조로 실제 data는 배열포인터로 표현하는 것을 볼 수 있다. 그래서 *[10]int 와는 엄연히 다른 데이터형태이다.


var slice = make([]int,3)   // len | cap이 3인 slice
var slice2 = make([]int,3,5)  //len이 3, cap이 5인 slice

make를 이용해 slice를 초기화하면 위와 같이 len과 cap을 별도로 정의해줄 수 있다.


func changeArray(array [5]int){
  array[2] = 200
}

func changeSlice(slice []int){
  slice[2] = 200
}

func main(){
  arr := [5]int{1,2,3,4,5}
  slice := []int{1,2,3,4,5}

  changeArray(arr)
  changeSlice(slice)

  fmt.Println(arr)      //[1 2 3 4 5]
  fmt.Println(slice)    //[1 2 200 4 5]
}

기본적으로 array타입을 함수의 매개변수로 주어지면 배열그대로를 복사해서 이용하는 call by value형태라서 값이 변하지 않지만, slice는 내부 구조를 이해하면 값이 변하는 이유를 이해할 수 있다.

slice는 내부에 배열을 직접 가지고 있는 것이 아니라 포인터형태로 값을 가르키고 있기 때문에 slice내부의 값이 변경되는 것이다. 따라서 실제 배열의 크기가 1024byte이더라고 slice변수의 데이터 크기는 24byte로 1024보다 현저히 적은 데이터 크기를 갖는다.



5. 슬라이싱

슬라이싱은 배열의 일부를 가리키는 기능으로 슬라이싱의 결과는 슬라이스이다. [:] 으로 표현할 수 있으며, :앞에는 시작 index가 오고 :이후에는 끝나는 index를 주는데 끝index는 포함하지 않는다.

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

fmt.Println(array)  //[1 2 3 4 5]
fmt.Println(slice, len(slice), cap(slice))  //[2] 1 4

slice[0] = 0
slice = array[1:2]
fmt.Println(array)  //[1 0 3 4 5]
fmt.Println(slice, len(slice), cap(slice))  //[0] 1 4

슬라이스는 특정 구간을 잘라낸 슬라이스를 반환하는 것이 아니기 때문에 slice의 값을 바꾸면 array의 값도 바뀌는 것을 볼 수 있다.

slice하게 되면 len은 요소 개수로 바뀌고 cap은 원본 배열의 len에서 시작 인덱스만큼 뺀 값으로 바뀌게 된다.

슬라이스도 배열과 마찬가지로 슬라이스를 수행할 수 있다.


1) 특정위치부터 끝까지 슬라이스

array := [5]int{1,2,3,4,5}
slice1 := array[1:len(array)]
slice2 := array[1:]

끝까지 슬라이스 하고 싶을때는 배열길이를 주면 되지만 이는 위 예시처럼 생략이 가능하다.


2) 전체 슬라이싱

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

배열을 슬라이스로 바꾸고 싶을때 사용할 수 있다.


3) 캡사이즈 조절 슬라이싱

slice[시작인덱스:끝 인덱스: 최대 인덱스]

위처럼 세번째 인자로 최대 인덱스를 입력하면 cap사이즈가 최대인덱스-시작인덱스로 조절이 된다.



6. 슬라이스 깊은 복사

1) for

slice1 := []int{1,2,3,4,5}
slice2 := make([]int{}, len(slice1))

for i,v := range slice1{
  slice2[i] = v
}

2) append()

slice1 := []int{1,2,3,4,5}
slice2 := append([]int{}, slice1...)

append가 슬라이스를 반환하는 점을 이용해서 빈 슬라이스에 기존의 slice를 append하면 깊은 복사를 수행할 수 있다.


3) copy()

slice1 := []int{1,2,3,4,5}
slice2 := make([]int, len(slice1))
copy(slice2,slice1)

go 내장 함수중에 copy()메서드를 이용하는 방법도 있다. 이때 copy() 리턴값은 복사한 개수이다.




Reference

『Tucker의 Go 언어 프로그래밍』 스터디 요약 노트

Tags :

Related Posts

5분 와인

5분 와인

  • Books
  • 2021년 4월 21일

제목에서 그대로 보이듯이 와인에 대해 깊고 많은 역사를 알려주는 책이 아닌 집에서 보관방법, 와인 구매장소, 마트에서 좋은 와인 고르기, 선물용 와인 등 과 같이 가벼운 내용위주의 책들이라 간단하게 보기 좋고 책에서 언급하는 대로 아는 체,있어보이는 척 하기에 괜찮은 책이다. 샴페인을 한번 먹어본 이후로 화이트와인과 스파클링 와인에 빠져서 화이트와인을...

Read More
[SPSP] Bellman Ford 알고리즘

[SPSP] Bellman Ford 알고리즘

그래프 중에서 최단 경로를 찾는 알고리즘중에 하나로 하나의 정점에서 다른 모든 정점까지의 최단경로를 구하는 알고리즘 (single-source shortest path algorithmm)으로 음의 가중치도 계산 할수 있는 알고리즘이다. Vertex의 개수가 N개일 때, 한 vertex에서 다른 vertex까지 가는데 거치는 edge수는 최소 1개부터 최대 N-1번 거치게 된다. 이...

Read More
인터페이스

인터페이스

  • Java
  • 2021년 12월 8일

1. 인터페이스란? 자바의 다형성을 극대화하여 개발코드 수정을 줄이고 유지보수를 용이하게 하기 위함 다형성? 동일한 메시지를 수신했을 때 객체의 타입에 따라 다르게 응답할 수 있는 능력 1) 추상클래스와 인터페이스 차이 추상메서드를 가짐으로써 다형성을 극대화하면서 어떤 역할을 구현하는 방법(객체들이 따라야 하는 책임의 집합을 서술한 것)이라는 공통...

Read More