Golang でマップを安全に使用する: 宣言と初期化の違い
導入
今週、私は golang の API ラッパー パッケージの 1 つに取り組んでいました。これは、URL エンコードされた値を含む投稿リクエストの送信、Cookie の設定、その他すべての楽しいことを処理していました。ただし、本文を構築している間、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 であるかはわかりません。そのためには、キーがマップ内に存在するかどうかを確認する必要があります。
さらに詳しくは、私の 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
In the above code, we declared the global map configSettings but didn't initialize it at that point, until we wanted to access the map. We initialize the map in the main function, this main function could be other specific parts of the code, and the global variable configSettings a map from another part of the code, by initializing it in the required scope, we prevent it from causing nil pointer access errors. We only initialize the map if it is nil i.e. it has not been initialized elsewhere in the code. This prevents overriding the map/flushing out the config set from other parts of the scope.
Pitfalls in access of un-initialized maps
But since it deals with pointers, it comes with its own pitfalls like nil pointers access when the map is not initialized.
Let's take a look at an example, a real case where this might happen.
package main import ( "fmt" "net/url" ) func main() { var vals url.Values vals.Add("foo", "bar") fmt.Println(vals) }
This will result in a runtime panic.
$ 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
This is because the url.Values is a map of string and a list of string values. Since the underlying type is a map for Values, and in the example, we only have declared the variable vals with the type url.Values, it will point to a nil reference, hence the message on adding the value to the type. So, it is a good practice to use make while declaring or initializing a map data type. If you are not sure the underlying type is map then you could use Type{} to initialize an empty value of that 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
It is also recommended by the golang team to use the make function while initializing a map. So, either use make for maps, slices, and channels, or initialize the empty value variable with Type{}. Both of them work similarly, but the latter is more generally applicable to structs as well.
Conclusion
Understanding the difference between declaring and initializing maps in Golang is essential for any developer, not just in golang, but in general. As we've explored, simply declaring a map variable without initializing it can lead to runtime errors, such as panics when attempting to access or modify a nil map. Initializing a map ensures that it is properly allocated in memory and ready for use, thereby avoiding these pitfalls.
By following best practices—such as using the make function or initializing with Type{}—you can prevent common issues related to uninitialized maps. Always ensure that maps and slices are explicitly initialized before use to safeguard against unexpected nil pointer dereferences
Thank you for reading this post, If you have any questions, feedback, and suggestions, feel free to drop them in the comments.
Happy Coding :)
以上がGolang でマップを安全に使用する: 宣言と初期化の違いの詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。

ホットAIツール

Undress AI Tool
脱衣画像を無料で

Undresser.AI Undress
リアルなヌード写真を作成する AI 搭載アプリ

AI Clothes Remover
写真から衣服を削除するオンライン AI ツール。

Stock Market GPT
AIを活用した投資調査により賢明な意思決定を実現

人気の記事

ホットツール

メモ帳++7.3.1
使いやすく無料のコードエディター

SublimeText3 中国語版
中国語版、とても使いやすい

ゼンドスタジオ 13.0.1
強力な PHP 統合開発環境

ドリームウィーバー CS6
ビジュアル Web 開発ツール

SublimeText3 Mac版
神レベルのコード編集ソフト(SublimeText3)

GOのインターフェイスは、タイプの明示的な宣言の実装を強制しませんが、それらは依然として多型とコード分離を実装する上で重要です。メソッドシグネチャのセットを定義することにより、インターフェイスを使用すると、さまざまなタイプを統一された方法で処理し、柔軟なコード設計とスケーラビリティを可能にします。この記事では、GOインターフェイスの特性を詳細に調査し、例を通じて実際の開発におけるアプリケーション値を示します。

この記事は、特にシステム固有のファイルが存在する場合、開発者がGOプロジェクトでコンパイルおよびリンクされるファイルを決定する方法を理解するのを支援することを目的としています。 Go Build -Nコマンドを使用して出力を解析し、Go/Buildパッケージのインポート関数を使用します。これらの方法を使用すると、ビルドプロセスを明確に理解し、プロジェクトをより適切に管理できます。

この記事は、GOを使用してWebSocketを開発するときに遭遇するEOF(ファイルの終了)エラーを解決することを目的としています。通常、このエラーは、サーバーがクライアントメッセージを受信し、接続が予期せず閉じられている場合に発生し、その後のメッセージを正常に配信できません。この記事では、問題の原因を分析し、コードの例を提供し、対応するソリューションを提供して、開発者が安定した信頼できるWebSocketアプリケーションを構築できるようにします。

Goprovidessimpleandefficientfilehandlingusingtheosandbufiopackages.Toreadasmallfileentirely,useos.ReadFile,whichloadsthecontentintomemorysafelyandautomaticallymanagesfileoperations.Forlargefilesorincrementalprocessing,bufio.Scannerallowsline-by-liner

この記事では、GOプログラムで外部エディター(VIMやNanoなど)を開始し、プログラムが実行され続ける前にユーザーがエディターを閉じるのを待つ方法について説明します。 cmd.stdin、cmd.stdout、およびcmd.stderrを設定することにより、編集者は端末と対話して、起動の障害の問題を解決できます。同時に、完全なコードの例が表示され、開発者がこの機能をスムーズに実装するのに役立つ予防策が提供されます。

struct {}はgoのフィールドレス構造であり、ゼロバイトを占有し、データが不要なシナリオでよく使用されます。 Goroutine同期など、チャネル内の信号として使用されます。 2。効率的なメモリの重要な存在チェックを実現するために、値の種類のコレクションとして使用されます。 3.依存関係の注入または組織機能に適した定義可能なステートレスメソッドレシーバー。このタイプは、制御フローと明確な意図を表現するために広く使用されています。

標準ライブラリのエンコード/JSONパッケージを使用して、JSON構成ファイルを読み取ります。 2。GOPKG.in/Yaml.v3ライブラリを使用して、YAML形式の構成を読み取ります。 3. os.getenvまたはgodotenvライブラリを使用して、ファイル構成を上書きします。 4. Viperライブラリを使用して、マルチフォーマット構成、環境変数、自動リロードなどの高度な機能をサポートします。タイプの安全性を確保し、ファイルと解析エラーを適切に処理し、構造タグマッピングフィールドを正しく使用し、ハードコーディングパスを避け、環境変数または生産環境での安全な構成ストレージを使用することをお勧めするために、構造を定義する必要があります。単純なJSONから始めて、要件が複雑な場合にViperに移行できます。

ミドルウェアワーシングウェブシュアレーバーは、interceptttprequestSeyreatheyreachtheTheTheHandlerを使用して、カットカッティングの機能性を有効にします
