"일꾼이 일을 잘하려면 먼저 도구를 갈고 닦아야 한다." - 공자, 『논어』.
첫 장 > 프로그램 작성 > Golang에서 지도를 안전하게 사용하기: 선언과 초기화의 차이점

Golang에서 지도를 안전하게 사용하기: 선언과 초기화의 차이점

2024-11-08에 게시됨
검색:301

Safely using Maps in Golang: Differences in declaration and initialization

소개

이번 주에 저는 golang용 API 래퍼 패키지 중 하나를 작업하고 있었는데, 그 패키지에서는 URL 인코딩 값이 포함된 게시물 요청 전송, 쿠키 설정 및 모든 재미있는 작업을 다루었습니다. 하지만 본문을 구성하는 동안 url.Value 유형을 사용하여 본문을 구성하고 이를 사용하여 키-값 쌍을 추가하고 설정했습니다. 그러나 일부 부분에서 유선 nil 포인터 참조 오류가 발생했습니다. 이는 제가 수동으로 설정한 일부 변수 때문이라고 생각했습니다. 그러나 더 자세히 디버깅하면서 유형을 선언하기만 하고 초기화하여 nil 참조 오류를 일으키는 일반적인 함정이나 나쁜 습관을 발견했습니다.

이 게시물에서는 맵이 무엇인지, 맵을 만드는 방법, 특히 맵을 올바르게 선언하고 초기화하는 방법을 다루겠습니다. golang에서 맵 또는 유사한 데이터 유형의 선언과 초기화를 적절하게 구분하세요.

Golang의 지도란 무엇인가요?

golang의 맵 또는 해시맵은 키-값 쌍을 저장할 수 있는 기본 데이터 유형입니다. 내부적으로는 기본적으로 버킷 배열(연속 메모리)에 대한 포인터인 버킷을 보유하는 헤더 맵과 같은 데이터 구조입니다. 실제 키-값 쌍을 저장하는 해시 코드와 현재 키 수가 오버플로되는 경우 새 버킷에 대한 포인터가 있습니다. 이는 거의 일정한 시간 액세스를 제공하는 정말 스마트한 데이터 구조입니다.

Golang에서 지도를 만드는 방법

golang에서 간단한 맵을 생성하려면 문자열과 정수 맵을 사용하여 문자 빈도 카운터의 예를 들 수 있습니다. 지도는 문자를 키로 저장하고 빈도를 값으로 저장합니다.

package main

import "fmt"

func main() {
    words := "hello how are you"
    letters := map[string]int{}

    for _, word := range words {
        wordCount[word]  
    }

    fmt.Println("Word counts:")
    for word, count := range wordCount {
        fmt.Printf("%s: %d\n", word, count)
    }
}
$ go run main.go

Word counts:
e: 2
 : 3
w: 1
r: 1
y: 1
u: 1
h: 2
l: 2
o: 3
a: 1

따라서 지도를 map[string]int{}로 초기화하면 빈 지도를 얻게 됩니다. 그런 다음 키와 값을 채우는 데 사용할 수 있으며 문자열을 반복하고 각 문자(룬)에 대해 해당 문자 바이트를 문자열로 캐스팅하고 값을 증가시킵니다. int의 0 값은 0이므로 기본적으로 키가 없으면 0이 됩니다. 하지만 이는 양날의 검입니다. 하지만 키가 맵에 값 0으로 존재하는지, 키가 없지만 기본값이 0인지 알 수 없습니다. 이를 위해서는 다음이 필요합니다. 지도에 키가 존재하는지 확인합니다.

자세한 내용은 제 Golang 지도 포스팅에서 자세히 확인하실 수 있습니다.

선언과 초기화의 차이점

프로그래밍 언어에서는 변수를 선언하고 초기화하는 데 차이가 있으며 기본 유형의 구현을 통해 더 많은 작업을 수행해야 합니다. int, string, float 등과 같은 기본 데이터 유형의 경우 기본값/0 값이 있으므로 이는 변수 선언 및 초기화와 동일합니다. 그러나 맵과 슬라이스의 경우 선언은 프로그램 범위에서 변수를 사용할 수 있는지 확인하는 것뿐이지만 초기화를 위해서는 변수를 기본값/0 값 또는 할당해야 하는 실제 값으로 설정합니다.

따라서 선언은 단순히 프로그램 범위 내에서 변수를 사용할 수 있게 만듭니다. 맵과 슬라이스의 경우 초기화 없이 변수를 선언하면 nil로 설정됩니다. 즉, 할당된 메모리가 없음을 가리키며 직접 사용할 수 없음을 의미합니다.

반면 초기화는 메모리를 할당하고 변수를 사용 가능한 상태로 설정합니다. 맵과 슬라이스의 경우 myMap = make(map[keyType]valueType) 또는 Slice = []type{}과 같은 구문을 사용하여 명시적으로 초기화해야 합니다. 이 초기화 없이 맵이나 슬라이스를 사용하려고 하면 nil 맵이나 슬라이스에 액세스하거나 수정할 때 패닉과 같은 런타임 오류가 발생합니다.

맵이 선언/초기화/비초기화되었을 때의 값을 살펴보겠습니다.

지도에서 설정을 읽는 구성 관리자를 구축한다고 상상해 보세요. 지도는 전역적으로 선언되지만 구성이 로드될 때만 초기화됩니다.

  1. 선언되었지만 초기화되지 않음

아래 코드는 초기화되지 않은 지도 액세스를 보여줍니다.

package main

import (
    "fmt"
    "log"
)

// Global map to store configuration settings
var configSettings map[string]string // Declared but not initialized

func main() {
    // Attempt to get a configuration setting before initializing the map
    serverPort := getConfigSetting("server_port")
    fmt.Printf("Server port: %s\n", serverPort)
}

func getConfigSetting(key string) string {
    if configSettings == nil {
        log.Fatal("Configuration settings map is not initialized")
    }
    value, exists := configSettings[key]
    if !exists {
        return "Setting not found"
    }
    return value
}
$ go run main.go
Server port: Setting not found
  1. 선언과 동시에 초기화됨

아래 코드는 동시에 초기화되는 지도 액세스를 보여줍니다.

package main

import (
    "fmt"
    "log"
)

// Global map to store configuration settings
var configSettings = map[string]string{
    "server_port":  "8080",
    "database_url": "localhost:5432",
}

func main() {
    serverPort := getConfigSetting("server_port")
    fmt.Printf("Server port: %s\n", serverPort)
}

func getConfigSetting(key string) string {
    value, exists := configSettings[key]
    if !exists {
        return "Setting not found"
    }
    return value
}
$ go run main.go
Server port: 8080
  1. 선언되고 나중에 초기화됨

아래 코드는 나중에 초기화되는 지도 액세스를 보여줍니다.

package main

import (
    "fmt"
    "log"
)

// Global map to store configuration settings
var configSettings map[string]string // declared but not initialized

func main() {
    // Initialize configuration settings
    initializeConfigSettings()
    // if the function is not called, the map will be nil

    // Get a configuration setting safely
    serverPort := getConfigSetting("server_port")
    fmt.Printf("Server port: %s\n", serverPort)
}

func initializeConfigSettings() {
    if configSettings == nil {
        configSettings = make(map[string]string) // Properly initialize the map
        configSettings["server_port"] = "8080"
        configSettings["database_url"] = "localhost:5432"
        fmt.Println("Configuration settings initialized")
    }
}

func getConfigSetting(key string) string {
    if configSettings == nil {
        log.Fatal("Configuration settings map is not initialized")
    }
    value, exists := configSettings[key]
    if !exists {
        return "Setting not found"
    }
    return value
}
$ go run main.go
Configuration settings initialized
Server port: 8080

위 코드에서는 전역 지도 configSettings를 선언했지만 지도에 액세스하기 전까지는 그 시점에서 초기화하지 않았습니다. 우리는 메인 함수에서 맵을 초기화합니다. 이 메인 함수는 코드의 다른 특정 부분일 수 있으며, 전역 변수 configSettings는 코드의 다른 부분에서 맵을 필요한 범위에서 초기화함으로써 nil 포인터가 발생하는 것을 방지합니다. 액세스 오류. 맵이 nil인 경우에만 초기화합니다. 즉, 코드의 다른 곳에서는 초기화되지 않았습니다. 이렇게 하면 맵을 재정의하거나 범위의 다른 부분에서 구성 세트를 플러시하는 것을 방지할 수 있습니다.

초기화되지 않은 지도에 접근할 때의 함정

하지만 포인터를 다루기 때문에 맵이 초기화되지 않으면 포인터에 액세스할 수 없는 것과 같은 자체적인 함정이 있습니다.

이런 일이 발생할 수 있는 실제 사례를 살펴보겠습니다.

package main

import (
    "fmt"
    "net/url"
)

func main() {
        var vals url.Values
        vals.Add("foo", "bar")
        fmt.Println(vals)
}

이로 인해 런타임 패닉이 발생합니다.

$ go run main.go
panic: assignment to entry in nil map

goroutine 1 [running]:
net/url.Values.Add(...)
        /usr/local/go/src/net/url/url.go:902
main.main()
        /home/meet/code/playground/go/main.go:10  0x2d
exit status 2

이는 url.Values가 문자열의 맵이자 문자열 값의 목록이기 때문입니다. 기본 유형은 값에 대한 맵이고 예제에서는 url.Values ​​유형으로 vals 변수만 선언했기 때문에 nil 참조를 가리키므로 유형에 값을 추가하라는 메시지가 표시됩니다. 따라서 지도 데이터 유형을 선언하거나 초기화할 때 make를 사용하는 것이 좋습니다. 기본 유형이 맵인지 확실하지 않은 경우 Type{}을 사용하여 해당 유형의 빈 값을 초기화할 수 있습니다.

package main

import (
    "fmt"
    "net/url"
)

func main() {
        vals := make(url.Values)
        // OR
        // vals := url.Values{}
        vals.Add("foo", "bar")
        fmt.Println(vals)
}
$ go run urlvals.go
map[foo:[bar]]
foo=bar

golang 팀에서는 지도를 초기화하는 동안 make 기능을 사용하는 것도 권장합니다. 따라서 맵, 슬라이스 및 채널에 대해 make를 사용하거나 Type{}을 사용하여 빈 값 변수를 초기화하세요. 둘 다 비슷하게 작동하지만 후자가 구조체에도 더 일반적으로 적용 가능합니다.

결론

Golang에서 맵 선언과 초기화의 차이점을 이해하는 것은 golang뿐만 아니라 일반적인 모든 개발자에게 필수적입니다. 우리가 살펴본 것처럼 초기화하지 않고 단순히 맵 변수를 선언하면 nil 맵에 액세스하거나 수정하려고 할 때 패닉과 같은 런타임 오류가 발생할 수 있습니다. 맵을 초기화하면 맵이 메모리에 적절하게 할당되고 사용할 준비가 되므로 이러한 함정을 피할 수 있습니다.

make 함수 사용 또는 Type{}으로 초기화와 같은 모범 사례를 따르면 초기화되지 않은 지도와 관련된 일반적인 문제를 방지할 수 있습니다. 예상치 못한 nil 포인터 역참조를 방지하기 위해 사용하기 전에 항상 맵과 슬라이스가 명시적으로 초기화되었는지 확인하세요.

이 게시물을 읽어주셔서 감사합니다. 질문, 피드백, 제안 사항이 있으면 언제든지 댓글에 남겨주세요.

행복한 코딩하세요 :)

릴리스 선언문 이 기사는 https://dev.to/mr_destructive/safely-using-maps-in-golang-differences-in-declaration-and-initialization-2jfi?1에서 복제됩니다. 침해가 있는 경우에는 Study_golang@163으로 문의하시기 바랍니다. .com에서 삭제하세요
최신 튜토리얼 더>

부인 성명: 제공된 모든 리소스는 부분적으로 인터넷에서 가져온 것입니다. 귀하의 저작권이나 기타 권리 및 이익이 침해된 경우 자세한 이유를 설명하고 저작권 또는 권리 및 이익에 대한 증거를 제공한 후 이메일([email protected])로 보내주십시오. 최대한 빨리 처리해 드리겠습니다.

Copyright© 2022 湘ICP备2022001581号-3