하지만 Go에서 배열은 크기를 동적으로 증가할 수 없으며 부분 배열을 발췌할 수 없다. 그래서 Slice라는 자료형이 존재한다. 선언은 var ${변수명} []${자료형}으로 할 수 있다. 혹은 ${변수명} := []${자료형}{${초기화}} 및 ${변수명} := make([]${자료형}, ${길이}, ${용량})을 사용할 수 있다. (용량에 대해서는 아래서 설명)
길이의 제한을 받지 않고 늘어나는 순간마다 len()이 s1, s2 모두 증가하는 것을 확인할 수 있다. s1은 첫 용량이 0이기 때문에 첫 append()에서 1으로 증가하고 두 번째 append()에서는 1의 두 배인 2, 세 번째 append()에서는 2의 두 배인 4인 것을 확인할 수 있다. Slice는 용량을 초과하는 경우 현재 용량의 2배에 해당하는 용량을 새로운 underlying array를 생성하고 기존 배열 값들을 모두 새 배열에 복제하고 다시 슬라이스를 할당한다. 따라서 s2는 첫 용량이 3이기 때문에 첫 용량 초과에 용량이 6으로 증가한 것을 확인할 수 있다.
Underlying array가 뭐야?
Golang에서 슬라이스 (slice)는 underlying array (기본 배열)을 가리키는 포인터를 포함하고 있습니다. 이 underlying array는 실제 데이터가 저장되는 공간입니다. 슬라이스는 길이 (length)와 용량 (capacity) 정보와 함께 underlying array를 가리키는 포인터를 갖습니다. 슬라이스를 생성할 때, 기본 배열은 자동으로 할당되며, 슬라이스의 요소들은 이 기본 배열에 저장됩니다. 슬라이스는 해당 배열의 일부분을 가리키므로, 배열의 변경은 슬라이스에도 영향을 줍니다. 슬라이스의 일부를 수정하면 underlying array의 해당 부분도 수정됩니다. 마찬가지로, 다른 슬라이스를 생성하고 해당 슬라이스의 요소를 변경하면, 같은 underlying array를 공유하는 다른 슬라이스들도 영향을 받습니다. 예를 들어, 다음 코드를 살펴보겠습니다:
슬라이스 slice의 첫 번째 요소를 10으로 변경한 후, array와 slice를 출력하면, array의 해당 부분도 변경된 것을 확인할 수 있습니다. 이는 slice가 array의 underlying array를 공유하기 때문입니다. 따라서, underlying array는 슬라이스의 데이터를 저장하는 실제 공간이며, 슬라이스의 요소들은 해당 배열에 저장됩니다. 슬라이스를 통해 배열을 조작하면 underlying array도 함께 변경됩니다.
Python과 유사하게 slice를 복사할 때 단순히 :=를 사용하면 shallow copy된다. Deep copy를 하려면 copy() 함수를 아래와 같이 사용해야한다.
Map은 hash table을 구현한 자료 구조로 python의 dictionary와 유사하다. 선언은 var ${변수명} map[${Key_자료형}]${Value_자료형}와 같이 할 수 있다. 하지만 이 상태 (nil map)에서는 어떤 값도 쓸 수 없기 때문에 make(map[${Key_자료형}]${Value_자료형})로 변수를 초기화해야 사용할 수 있다. 혹은 ${변수명} = map[${Key_자료형}]${Value_자료형}{}와 같이 선언하면 초기화를 동시에 할 수 있다.
$ go run main.go m1: map[] m1: map[asdf:zxcv qw:er] m2: map[] m2: map[2:71 3:14]
val := map[key] 혹은 val, exists := map[key] 와 같이 map을 사용할 수 있다. 후자와 같이 사용 시 exists에 key가 존재하는지 여부를 저장한다. 또한 map은 반복문에서 for k, v := range m와 같이 사용하면 python dictionary의 items()와 유사하게 사용할 수 있다.
$ go run main.go test(): Hello, World! test(): Hello, World! test(): Changed test(): Changed main(): Hello, World! main(): Changed
Variadic Function
가변 파라미터를 함수에 전달할 때 ...을 사용한다. 하지만 ...은 마지막 파라미터에서만 사용한다. 즉, (x ...int, y ...string)과 같이 사용할 수 없다. 아래 예제에서는 단일 문자열을 전달할 수도 있고 여러 문자열을 전달할 수도 있다.
main.go
1 2 3 4 5 6 7 8 9 10 11 12 13 14
package main
import"fmt"
funcmain() { test("A", "B", "C") }
functest(msg ...string) { fmt.Println(msg) for idx, tmp := range msg { fmt.Println(idx, tmp) } }
1 2 3 4 5
$ go run main.go [A B C] 0 A 1 B 2 C
Return
함수의 출력이 2개 이상인 경우 func ${함수명}(...) 뒤에 (${자료형}, ${자료형}, ...)과 같이 정의할 수 있다. 혹은 (${변수명} ${자료형}, ${변수명} ${자료형}, ...)과 같이 정의할 수 있다. 아래 예제에서 첫 번째와 같이 정의한 함수가 sum() 함수이고, 두 번째와 같이 정의한 함수가 sum2()이다. sum2()와 같이 함수를 정의할 때 return 뒤에 별도의 변수가 존재하지 않더라도 꼭 return을 명시해야 한다. 결과는 동일한 것을 확인할 수 있다.