두 개 이상의 변수를 모아 놓은 것을 '컬렉션'이라고 합니다. 컬렉션은 그 기능에 따라 이름이 다른 세 가지 용법이 있습니다. 배열, 슬라이스, 맵이 그것입니다.
array (배열)
배열은 연속된 메모리이며, 인덱스와 타입 크기를 사용해서 메모리 주소를 찾습니다.
[size]type 꼴로 array를 선언할 수 있고, [size]type{...} 꼴로 곧바로 초기화할 수도 있다.
아래와 같이 별도로 index 마다 값을 넣어줄 수도 있다.
설정한 size 이상의 값을 할당하려고 하면 out of bounds 에러가 발생한다.
Go언어에서는 배열의 크기는 자료형을 구성하는 한 요소입니다. 따라서, [3]int와 [5]int는 string과 float32처럼 타입 자체가 다른 것입니다. The size of an array is part of its type. The types [10]int and [20]int are distinct.(effective go)
실제로 그런지, 타입을 찍어보았떠니 [5]int라는 값을 반환합니다.
func main() {
arr := [5]int{0, 1, 2, 3, 4}
fmt.Printf("%T\n", arr) // [5]int 라는 타입을 반환함.
}
아래와 같이 array를 생성할 수 있습니다. 아무래도 번거로우니 := 를 쓰는게 좋죠.
func main() {
var x [5]int
x[0] = 1
x[1] = 2
fmt.Println(x)
y := [5]int{1, 2, 3, 4, 5}
fmt.Println(y)
}
배열 선언시 배열의 크기는 '상수'로 전달해야 합니다. 변수로 전달하면 에러를 냅니다.
아래 코드는 직관적으로 될 것 같지만, 실은 에러가 발생합니다.
package main
const Y = 3;
func main() {
x := 3
a := [x]int{1, 2, 3} // Error!
b := [Y]int{1, 2, 3} // OK
}
이미 할당된 array라 하더라도 새로이 값을 넣을 수 있도 있고, overwriting할 수도 있습니다.
func main() {
names := [5]string{"foo", "bar", "baz"}
names[3] = "robaz"
names[4] = "dodo"
names[5] = "should be not in arr!" // out of bounds for 5-element array
fmt.Println(names)
}
배열을 초기화하면서 동시에 특정한 부분만 값을 할당할수도 있습니다.
func main() {
a := [5]int{1:2, 4:3} // 0 2 0 0 3
for i := 0; i < 5; i++ {
fmt.Println(a[i])
}
}
아래 같이 ...을 이용해서 배열의 크기를 자동 할당할 수 있습니다. 슬라이스와는 다른 겁니다!
func main() {
var arr = [...]int{9, 8, 7, 6} //[...]을 이용한 배열 크기 자동 설정
fmt.Println(arr, len(arr)) //arr3 전체와 배열 크기 출력해보기
}
for문으로 순회를 돌 수도 있지만, range를 통해 반복하는게 훨씬 Go스럽습니다!
func main() {
a := [3]int{100, 200, 300}
for i, v := range a {
fmt.Println(i, v)
}
}
n차원 배열
n차원 배열은 메모리상에 어떻게 구현이 될까요?
간단히 생각하면, [2][5]의 경우, [5]배열이 2개 있는 겁니다.
[2][5][100] 같은 경우에는 [5][100] 배열이 2개 있고, [5][100] 배열은 [100] 배열이 5개 있다고 생각하면 됩니다.
배열의 크기을 계산하자면, [2][5]int이라면 int는 8바이트이다. 그렇다면 2 * 5 * 8 = 80바이트이다.
func main() {
a := [2][5]int{{1, 2, 3, 4, 5}, {3, 4, 5, 6, 7}}
fmt.Println(a) // [[1 2 3 4 5] [3 4 5 6 7]]
}
다중 배열을 출력하는데 range를 활용하여 go스럽게 표현하면 다음과 같습니다.
func main() {
a := [2][5]int{{1, 2, 3, 4, 5}, {3, 4, 5, 6, 7}}
for _, arr := range a {
for _, v := range arr {
fmt.Println(v)
}
}
}
array는 값입니다. so what? 객체 복사를 줄여야 한다.
golang.org/doc/effective_go.html
effecctive go에서 보면 아래와 같은 설명을 합니다. array는 값이므로 array를 함수에 넘기면 array의 포인터가 아니라 카피를 받습니다. 당연히 카피가 이루어지는 것은 메모리를 사용해야 하는 일이구요
- Arrays are values. Assigning one array to another copies all the elements.
- In particular, if you pass an array to a function, it will receive a copy of the array, not a pointer to it.
- The size of an array is part of its type. The types [10]int and [20]int are distinct.
그래서 C-like 한 동작을 원한다면 아래와 같이 포인터를 넘기는 것이 좋습니다.
But even this style isn't idiomatic Go. Use slices instead. (아 공식문서에서조차 슬라이스를 쓰라고...)
func Sum(a *[3]float64) (sum float64) {
for _, v := range *a {
sum += v
}
return
}
array := [...]float64{7.0, 8.5, 9.1}
x := Sum(&array) // Note the explicit address-of operator
map
key 값과 그에 해당하는 value 값을 매핑해 저장하는 'Hash table'이다. 그렇지만 python의 dict과는 조금 다르다.
참조 타입(Reference type)이기 때문에 슬라이스와 마찬가지로 기본값으로 자동 초기화를 진행하지 않기 때문에 선언만 한다면 nil 이 됩니다.
아래와 같이 선언하고 사용할 수 있다.
func main() {
// map[key의 타입]value의 타입
me := map[string]string{"name": "darren", "job": "entrepreneur"}
me["interest"] = "Go" // 추가 가능
delete(me, "job") // 삭제 가능
fmt.Println(me)
for key, value := range me {
fmt.Println(key, value)
}
}
독특한 점은, map은 두번재 반환으로 해당 값이 존재하는지, 하지 않는지를 bool값으로 반환합니다.
map의 값이 존재하는지 체킹하는데 유용합니다.
val, exists := tickers["MSFT"]
그 외로 map을 선언하는 방식은 아래와 같습니다. 앞서 언급한대로 초기화를 안해주면 nil이 되어 panic이 발생합니다.
이는 slice와 비슷합니다. 보통 Go는 int와 같은 타입을 설정해주면 기본값(0, "")으로 자동 초기화가 되는데 map, slice는 그렇지 않고 nil이 됩니다. 이는 레퍼런스 타입의 특성이며, 할당될 원소의 값들을 담을 메모리 주소가 확정되지 않았기 때문에 그런 것입니다.
var results = map[string]string{}
var results = make(map[string]string)
results := make(map[string]string)
var results map[string]string // 이건 nil이 되어 조작하면 panic이 일어남
func main() {
var a map[int]string // 초기화하지 않고 선언만 했으므로 nil
if a == nil {
fmt.Println("nil map")
}
var m = map[string]string{ //key:value, 형식으로 초기화한다
"apple": "red",
"grape": "purple",
"banana": "yellow",
}
fmt.Println(m, "\nm의 길이는", len(m))
}
'Programming Language > 🐿️ Go (Golang)' 카테고리의 다른 글
struct (1) : struct(구조체)를 생성, 메모리 패딩, struct factory (2) | 2021.05.15 |
---|---|
Go의 기초 문법 (5) : slice(동적 배열). cap에 대한 이해 (0) | 2021.05.15 |
Go의 기초 문법 (3) : 포인터와 역참조 (0) | 2021.05.14 |
Go의 기초 문법 (2) : 반복문 for, range, 조건문 if, switch (0) | 2021.05.14 |
Go의 기초 문법 (1) : 변수, iota, 함수, 타입, underlying type,... (0) | 2021.05.14 |