본문 바로가기
Study/Go

[Go] Slice와 Array 알고 쓰기

by _royJang 2021. 7. 21.

Slice

C++의 Vector, Java의 ArrayList 등과 같이 사용할 수 있는 Go의 Slice.
코딩 테스트든 개발이든 가장 많이 사용하게 될 자료구조가 아닌 듯싶다.
그렇기에 Slice를 잘 사용하기 위해 기본적으로 알아야 하는 내용들의 이해를 돕기 위해 Array와 비교하여 요약하려 한다.

Array와 Slice의 차이

기본적으로 Array는 memory에 동적으로 할당할 수 없다(정적인 자료구조). 생성 시에 크기를 지정해 주어야 하며 그에 맞춰 const 한 메모리를 할당하게 된다.
하지만 Slice는 그렇지 않다. Slice는 동적으로 할당할 수 있으며 크기를 따로 지정해 주지 않아도 된다(해도 됨).

Array와 Slice 생성 방법

Array

몇 가지의 방법을 소개하겠다. 사실 모두 비슷비슷하니 편한 것을 사용하면 되겠다.

    fruits := [3]string{}
    fruits[0] = "apple"
    fruits[1] = "banana"
    fruits[2] = "tomato"

\

    fruits := [3]string{"apple", "banana", "tomato"}

\

    fruits := [...]string{"apple", "banana", "tomato"} // 배열 안의 데이터 수에따라 Array의 크기가 정해진다.

\

    //Go에서는 전역으로 변수를 설정할 경우에 shortcut 방식인 ':='기호를 사용할 수 없다.
    var fruits [3]string
    fruits[0] = "apple"
    fruits[1] = "banana"
    fruits[2] = "tomato"

Slice

    var fruits = make([]string, 3, 4) // make의 매개변수 첫 번째는 Slice의 type을 의미한다. 두 번째는 길이를 의미한다. 세 번째는 크기를 의미한다.
    fruits[0] = "apple"
    fruits[1] = "banana"
    fruits[2] = "tomato"

\

    var fruits = make([]string, 3) // Slice의 크기를 의미하는 세 번째 매개변수는 생략 가능하며 이 경우 길이로 설정한 값과 같이 크기가 설정된다.
    fruits[0] = "apple"
    fruits[1] = "banana"
    fruits[2] = "tomato"

\

    fruits := []string{"apple", "banana", "tomato"}

\

    fruits := []string{}
    fruits = append(fruits, "apple")
    fruits = append(fruits, "banana")
    fruits = append(fruits, "tomato")

\

    fruits := []string{}
    fruits = append(fruits, "apple", "banana", "tomato")

\

    var fruits []string
    fruits = append(fruits, "apple")
    fruits = append(fruits, "banana")
    fruits = append(fruits, "tomato")

방법이 더 있을 수 있지만 이 정도면 충분할 듯..

Array와 Slice의 순회 방법

Array와 Slice 순회 방법은 같다.

index 사용

    for i := 0; i < len(fruits); i++ {
        fmt.Println(fruits[i])
    }

range 사용

    for _, food := range fruits {
        fmt.Println(food)
    }

Slice의 memory 사용 방법

make를 사용하여 Slice를 생성하는 것을 위에서 보였다. memory를 사용하는 것에서 make의 세 번째 매개변수인 Slice의 크기가 중요하게 작용한다.

Slice는 구조체로 이루어져 있다.

type slice structure{
    Elem* // 요소가 저장된 시작 메모리
    len // slice의 길이
    cap // slice의 크기 
}

Go에서 Slice에 값을 추가하기 위해선 append 함수를 사용하는데 윗 코드에서 보면 같은 변수에 재 할당해 주는 모습을 볼 수 있다. 이를 보면 append 함수를 적용한 이후의 slice와 기존의 slice는 다른 값을 가진다는 것을 유추할 수 있을 것이다. 그렇다면 어떻게 다른 값을 가질까? 두 가지를 생각해 볼 수 있다.

  1. cap>len
  2. cap<=len

아래 코드로 확인해 보자.

    var numbers = make([]int, 2, 3)
    numbers[0] = 1
    numbers[1] = 2
    fmt.Printf("numbers의 길이 : %d numbers의 크기 : %d numbers의 주소 : %p\n\n", len(numbers), cap(numbers), numbers)

    numbers = append(numbers, 3)
    fmt.Printf("numbers의 길이 : %d numbers의 크기 : %d numbers의 주소 : %p\n\n", len(numbers), cap(numbers), numbers)

    numbers = append(numbers, 4)
    fmt.Printf("numbers의 길이 : %d numbers의 크기 : %d numbers의 주소 : %p\n\n", len(numbers), cap(numbers), numbers)

실행 결과

 

len이 cap보다 작을 때 append 함수를 적용하면 주소가 그대로 인 것을 확인할 수 있다.

그러나 len이 cap과 같을 때는 전혀 다른 주소를 가리키는 것을 확인할 수 있다.

이렇게 원래 할당받은 메모리의 크기가 가득 차게 된다면 크기가 2배가 되고 새로운 memory를 할당받는 것을 알 수 있다.