今週、私は golang の API ラッパー パッケージの 1 つに取り組んでいました。これは、URL エンコードされた値を含む投稿リクエストの送信、Cookie の設定、その他すべての楽しいことを扱いました。ただし、本文を構築している間、url.Value 型を使用して本文を構築し、それを使用してキーと値のペアを追加および設定していました。しかし、一部の部分で有線 nil ポインタ参照エラーが発生しました。手動で設定した変数のせいだと思いました。しかし、詳しくデバッグしてみると、型を宣言しただけで初期化し、nil 参照エラーが発生するというよくある落とし穴や悪い習慣が見つかりました。
この投稿では、マップとは何か、マップの作成方法、特にマップを適切に宣言および初期化する方法について説明します。 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 であるかはわかりません。そのためには、キーがマップに存在するかどうかを確認する必要があります。
さらに詳しくは、私の Golang マップの投稿をご覧ください。
プログラミング言語での変数の宣言と初期化には違いがあり、基になる型の実装でさらに多くのことを行う必要があります。 int、string、float などのプライマリ データ型の場合は、デフォルト/ゼロ値があるため、変数の宣言と初期化と同じになります。ただし、マップとスライスの場合、宣言は変数がプログラムのスコープで使用できることを確認するだけですが、初期化では変数をデフォルト/ゼロ値、または割り当てる必要がある実際の値に設定します。
したがって、宣言は単に変数をプログラムのスコープ内で使用できるようにするだけです。マップとスライスの場合、初期化せずに変数を宣言すると変数が nil に設定されます。これは、変数が割り当てられたメモリを指さないことを意味し、直接使用することはできません。
一方、初期化ではメモリが割り当てられ、変数が使用可能な状態に設定されます。マップとスライスの場合は、myMap = make(map[keyType]valueType) またはスライス = []type{} のような構文を使用して明示的に初期化する必要があります。この初期化を行わないと、マップまたはスライスを使用しようとすると、nil マップまたはスライスへのアクセスまたは変更によるパニックなどのランタイム エラーが発生します。
マップが宣言されたとき、初期化されたとき、または初期化されていないときのマップの値を見てみましょう。
マップから設定を読み取る構成マネージャーを構築していると想像してください。マップはグローバルに宣言されますが、設定がロードされたときにのみ初期化されます。
以下のコードは、初期化されていないマップ アクセスを示しています。
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
以下のコードは、同時に初期化されるマップ アクセスを示しています。
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
以下のコードは、後で初期化されるマップ アクセスを示しています。
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 の場合、つまりコード内の他の場所で初期化されていない場合にのみ、マップを初期化します。これにより、マップをオーバーライドしたり、スコープの他の部分から構成セットをフラッシュしたりすることがなくなります。
しかし、ポインタを扱うため、マップが初期化されていないときの 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 が文字列のマップと文字列値のリストであるためです。基になる型は Values のマップであり、この例では、変数 vals を url.Values 型で宣言しただけであるため、これは 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 ポインター逆参照を防ぐために、マップとスライスが使用前に明示的に初期化されていることを常に確認してください
この投稿をお読みいただきありがとうございます。ご質問、フィードバック、ご提案がございましたら、お気軽にコメント欄にお寄せください。
ハッピーコーディング:)
免責事項: 提供されるすべてのリソースの一部はインターネットからのものです。お客様の著作権またはその他の権利および利益の侵害がある場合は、詳細な理由を説明し、著作権または権利および利益の証拠を提出して、電子メール [email protected] に送信してください。 できるだけ早く対応させていただきます。
Copyright© 2022 湘ICP备2022001581号-3