소개
새로운 챕터에 오신 Gopher 여러분, 환영합니다.
이번 챕터에서는 맵 (map) 의 기본적인 사용법을 배우고 Go 에서 nil의 존재 여부를 분석해 보겠습니다.
이 챕터를 완료하면 맵을 일상적인 개발 작업에 적용할 수 있게 됩니다.
지식 포인트:
- 맵 선언
- 맵 초기화
- 맵 요소 추가, 삭제, 업데이트 및 검색
- 맵 요소 존재 여부 확인
- 맵 순회
딕셔너리 소개
자, 딕셔너리란 무엇일까요?
컴퓨터 과학에서 딕셔너리는 **키 - 값 쌍 (key-value pairs)**으로 구성된 특별한 데이터 구조입니다.
키 - 값 쌍: 한 요소는 키 (key) 라고 하고 다른 요소는 값 (value) 이라고 하는 두 요소의 쌍입니다.
그렇다면 왜 딕셔너리를 사용해야 할까요?
딕셔너리는 요소 추가, 삭제, 업데이트 및 검색과 같은 작업에서 높은 효율성을 가지기 때문에, 이 데이터 구조가 매우 유용하다는 것을 모두 알게 되리라고 생각합니다.

맵 선언
새로운 데이터 구조의 경우, 첫 번째 단계는 이를 선언하는 방법을 배우는 것입니다.
map의 경우, 이를 정의하는 방법은 다음과 같습니다.
var variableName map[keyType]valueType
예를 들어:
var a map[string]int
하지만, map은 참조 타입 (reference type) 이므로, 위 코드처럼 선언하면 문제가 발생합니다.
~/project 디렉토리에 map.go 파일을 생성해 보겠습니다.
touch ~/project/map.go
map.go에 다음 코드를 작성합니다.
package main
func main() {
// key 타입이 string 이고 value 타입이 int 인 m 이라는 맵을 선언합니다.
var m map[string]int
// "labex"->1 데이터 항목을 추가합니다.
m["labex"] = 1 // 프로그램이 충돌합니다.
}
프로그램을 실행합니다.
go run map.go
프로그램이 충돌하고 다음과 같은 오류를 발생시키는 것을 확인했습니다.
panic: assignment to entry in nil map
이는 nil map에 요소를 할당하는 것이 오류가 발생하는 동작임을 의미합니다.
이는 슬라이스 (slice), 맵 (map), 채널 (channel), 포인터 (pointer) 와 같은 데이터 구조의 경우 사용하기 전에 초기화해야 하기 때문입니다.
그리고 nil은 맵의 초기 기본값이며, 이는 정의하는 동안 변수에 메모리가 할당되지 않음을 의미합니다.
초기 값 nil
이전 섹션에서, nil 맵에 요소를 할당하는 것은 오류가 발생하는 동작이라고 언급했습니다.
이번 기회에 Go 에서 nil의 진정한 의미를 탐구해 보겠습니다.
nil의 본질은 미리 선언된 식별자입니다.
기본 데이터 타입의 경우, 초기 값은 다음과 같습니다.
- 부울 값
- 숫자 값
- 문자열
하지만 슬라이스, 딕셔너리, 포인터, 채널 및 함수의 경우, 초기 값은 nil입니다. 이는 우리가 익숙한 초기 기본값이 아닙니다.
이는 nil이 할당된 인스턴스 객체에 반영됩니다. 비록 출력은 가능하지만, 사용할 수는 없습니다.
또한, nil에 대해 주목해야 할 몇 가지 사항이 있습니다.
서로 다른 타입의 nil 비교 불가
package main
import "fmt"
func main() {
var m map[int]string
var p *int
fmt.Printf("%v", m == p)
}
프로그램 출력은 다음과 같습니다.
invalid operation: m == p (mismatched types map[int]string and *int)
즉, int 타입 포인터의 nil과 맵의 nil을 비교할 수 없습니다.
그들은 비교할 수 없습니다.
nil은 키워드가 아님
package main
import "fmt"
func main() {
var nilValue = "= =$"
fmt.Println(nilValue)
}
프로그램 출력은 다음과 같습니다.
= =$
nil이라는 변수를 정의할 수 있으며, 오류 없이 컴파일됩니다. 하지만 실제 개발에서는 이렇게 하지 않기를 강력히 권장합니다.
nil 자체 비교 불가
package main
import "fmt"
func main() {
fmt.Println(nil == nil)
}
프로그램 출력은 다음과 같습니다.
invalid operation: nil == nil (operator == not defined on nil)
make 키워드를 사용한 선언
nil의 의미를 이해했으니, 딕셔너리 (dictionary) 에 대한 메모리 할당 문제를 해결해 보겠습니다.
여기서는 make 키워드를 사용하여 메모리를 할당합니다.
make 함수는 반환 값으로 초기화된 지정된 타입의 값을 생성합니다.
package main
import "fmt"
func main() {
var m map[string]int // 딕셔너리 선언
m = make(map[string]int) // 딕셔너리에 메모리 할당
m["labex"] = 1 // 딕셔너리에 데이터 추가
fmt.Println(m)
}
프로그램 출력은 다음과 같습니다.
map[labex:1]
위 코드는 make 키워드를 사용하여 선언된 딕셔너리에 메모리를 할당하는 방법을 보여줍니다.
이 과정을 단순화할 수도 있습니다.
map.go에 다음 코드를 작성합니다.
package main
import "fmt"
func main() {
m := make(map[string]int) // 딕셔너리 선언 및 초기화
m["labex"] = 666 // 딕셔너리에 데이터 추가
fmt.Println(m)
}
프로그램의 출력은 다음과 같습니다.
map[labex:666]
위 코드는 make 키워드를 사용하여 딕셔너리를 선언하는 방법을 보여줍니다.
수동으로 빈 맵 초기화
이전 섹션에서는 make 키워드를 사용하여 맵을 초기화하는 방법을 보여드렸습니다. 이제 다른 방법, 즉 리터럴 구문을 사용하여 빈 맵을 수동으로 초기화하는 방법을 살펴보겠습니다.
이 방법은 간결하며, 메모리를 명시적으로 할당하지 않고도 바로 사용할 수 있는 빈 맵을 생성할 수 있습니다.
map.go에 다음 코드를 작성합니다.
package main
import "fmt"
func main() {
// 리터럴 구문을 사용하여 빈 맵 초기화
m := map[string]int{}
// 맵에 요소 추가
m["labex"] = 777
m["labs"] = 11
fmt.Println(m) // 추가된 요소가 있는 맵 출력
}
구문 map[keyType]valueType{}은 사용할 준비가 된 빈 맵을 생성합니다. 초기화되면 map[key] = value 구문을 사용하여 맵에 요소를 추가할 수 있습니다.
위 코드를 실행하면 프로그램은 다음을 출력합니다.
map[labex:777 labs:11]
수동 초기화의 장점:
make키워드를 사용하지 않고 빈 맵을 생성하는 빠른 방법을 제공합니다.- 빈 맵으로 시작하여 동적으로 채워야 할 때 유용합니다.
사전 (Dictionary) 실제 초기화
빈 딕셔너리를 초기화할 수 있으므로, 초기 값을 제공할 수도 있습니다.
map.go에 다음 코드를 작성합니다.
package main
import "fmt"
func main() {
m := map[string]int{
"labex": 777,
"labs": 11,
}
m["labby"] = 666
fmt.Println(m)
}
프로그램 출력은 다음과 같습니다.
map[labby:666 labs:11 labex:777]
Go 에서 딕셔너리를 초기화할 때, 마지막 요소를 포함하여 각 요소 뒤에 쉼표를 추가해야 합니다.
사전 (Dictionary) 요소 추가 및 업데이트
딕셔너리에 요소를 추가하는 것은 위 코드 예제에서 보여드린 것처럼 매우 간단합니다.
구문은 다음과 같습니다.
dictionaryInstance[keyToAdd] = valueToAdd
예를 들어:
m := map[string]int{}
m["labby"] = 666
참고: Go 에서 map의 각 key는 고유해야 합니다.
map.go에 다음 코드를 작성합니다.
package main
import "fmt"
func main() {
m := map[string]int{}
m["labby"] = 666
m["labby"] = 777
fmt.Println(m)
}
프로그램 출력은 다음과 같습니다.
map[labby:777]
출력 값이 777로 변경된 것을 확인했습니다. 즉, 동일한 key에 다른 값을 쓰면 해당 value가 새 값으로 업데이트됩니다.
이것이 딕셔너리를 업데이트하거나 수정하는 방법입니다.
사전 (Dictionary) 요소 삭제
딕셔너리에서 요소를 어떻게 삭제할 수 있을까요?
내장 함수인 delete를 사용해야 합니다.
map.go에 다음 코드를 작성합니다.
package main
import "fmt"
func main() {
m := map[string]int{
"labex": 777,
"labs": 11,
"labby": 666,
}
fmt.Println(m)
delete(m, "labs")
fmt.Println(m)
}
프로그램 출력은 다음과 같습니다.
map[labby:666 labs:11 labex:777]
map[labby:666 labex:777]
delete 함수는 두 개의 인수를 받습니다. 첫 번째 인수는 조작할 딕셔너리이고, 두 번째 인수는 삭제할 키입니다.
물론, delete 함수는 다른 용도로도 사용되지만, 이 랩에서는 딕셔너리에서의 사용법만 다룹니다.
사전 (Dictionary) 요소 검색
딕셔너리에 존재하지 않는 요소를 검색하면 어떻게 될까요?
package main
import "fmt"
func main() {
m := map[string]int{
"labex": 0,
}
fmt.Print("The value of labs is: ")
fmt.Println(m["labs"])
}
프로그램 출력은 다음과 같습니다.
The value of labs is: 0
딕셔너리에 요소가 존재하지 않으면, 쿼리 시 해당 값 타입의 기본값이 반환되는 것을 확인했습니다.
값이 0인 딕셔너리의 키는 어떻게 될까요?
package main
import "fmt"
func main() {
m := map[string]int{
"labex": 0,
}
fmt.Print("The value of labs is: ")
fmt.Println(m["labs"])
fmt.Print("The value of labex is: ")
fmt.Println(m["labex"])
}
프로그램 출력은 다음과 같습니다.
The value of labs is: 0
The value of labex is: 0
딕셔너리의 경우, 존재하지 않는 값과 존재하지만 기본값을 갖는 값의 표현이 동일하다는 것을 확인했습니다.
이로 인해 많은 혼란이 발생합니다. 어떻게 해결할 수 있을까요?
알고 보니, Go 개발자들은 이미 이 문제를 생각했습니다. 딕셔너리에서 요소를 쿼리할 때, 하나 또는 두 개의 변수 반환 값이 있습니다.
즉, 다음과 같습니다.
labs, ok := m["labs"]
map.go를 수정합니다.
package main
import "fmt"
func main() {
m := map[string]int{
"labex": 0,
}
labs, ok := m["labs"]
fmt.Print("The value of labs is: ")
fmt.Print(labs)
fmt.Print(" Does it exist? ")
fmt.Println(ok)
labex, ok2 := m["labex"]
fmt.Print("The value of labex is: ")
fmt.Print(labex)
fmt.Print(" Does it exist? ")
fmt.Println(ok2)
}
프로그램 출력은 다음과 같습니다.
The value of labs is: 0 Does it exist? false
The value of labex is: 0 Does it exist? true
이제 쿼리의 두 번째 반환 값을 사용하여 반환된 결과가 기존의 초기 기본값인지, 아니면 존재하지 않는 초기 기본값인지 확인할 수 있습니다.
사전 (Dictionary) 순회
특정 시나리오에서 전체 딕셔너리를 순회하고, 각 **키 - 값 쌍 (key-value pair)**을 쿼리하고 처리해야 할 필요가 있습니다. 어떻게 이를 달성할 수 있을까요?
package main
import (
"fmt"
)
func main() {
m := map[string]int{
"labex": 777,
"labs": 11,
"labby": 666,
}
for key, value := range m {
fmt.Println(key, value)
}
}
프로그램 출력은 다음과 같습니다.
labby 666
labs 11
labex 777
출력에서 딕셔너리를 순회하는 방식이 슬라이스나 배열을 순회하는 방식과 매우 유사하다는 것을 알 수 있습니다.
요약
이 랩에서는 딕셔너리의 기본적인 사용법에 대해 배웠습니다. 여기에는 다음이 포함됩니다.
- 딕셔너리 선언 및 선언 문제 해결 방법
- 초기 값
nil의 특징 - 딕셔너리 초기화 방법
- 딕셔너리 요소 추가, 제거, 업데이트 및 검색
- 딕셔너리 요소의 존재 여부 감지
- 딕셔너리 순회



