はじめに
こんにちは、Gophersの皆さん。この新しい章へようこそ。
この章では、マップの基本的な使い方を学び、Goにおけるnil
の存在について分析します。
この章を終えると、マップを日常の開発タスクに適用できるようになります。
学習要点:
- マップの宣言
- マップの初期化
- マップ要素の追加、削除、更新、および検索
- マップ要素が存在するかどうかの確認
- マップの反復処理
こんにちは、Gophersの皆さん。この新しい章へようこそ。
この章では、マップの基本的な使い方を学び、Goにおけるnil
の存在について分析します。
この章を終えると、マップを日常の開発タスクに適用できるようになります。
学習要点:
では、辞書とは何でしょうか?
コンピュータサイエンスにおいて、辞書はキーと値のペアから構成される特殊なデータ構造です。
キーと値のペア:要素のペアで、一方の要素をキーと呼び、もう一方を値と呼ぶ。
ではなぜ辞書を使う必要があるのでしょうか?
辞書は要素の追加、削除、更新、および検索などの操作において高い効率を持っているため、皆さんにとってこのデータ構造は非常に便利であると思います。
新しいデータ構造の場合、最初のステップはそれをどのように宣言するかを学ぶことです。
map
の場合、それを定義する方法は次のとおりです。
var variableName map[keyType]valueType
例えば:
var a map[string]int
ただし、map
は参照型であるため、上記のコードのように宣言すると問題が発生します。
~/project
ディレクトリにmap.go
ファイルを作成しましょう。
touch ~/project/map.go
map.go
に次のコードを記述します。
package main
func main() {
// キー型がstringで値型が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
に要素を割り当てることは誤った動作であることを意味します。
これは、スライス、マップ、チャネル、ポインターなどのデータ構造は使用前に初期化する必要があるためです。
そして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
の意味を理解した後、辞書のメモリ割り当ての問題を解決しましょう。
ここでは、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
キーワードを使わずに空のマップを作成する迅速な方法を提供します。空の辞書を初期化できるので、いくつかの初期値を与えることもできます。
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で辞書を初期化する際には、最後の要素を含め、各要素の後にコンマを追加する必要があることに注意してください。
辞書に要素を追加するのは非常に簡単で、上のコード例に示されています。
構文は次の通りです。
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
が新しい値に更新されます。
これが辞書を更新または変更する方法です。
辞書から要素を削除するにはどうすればよいでしょうか。
組み込みの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
関数は2つの引数を取ります。最初の引数は操作対象の辞書で、2番目の引数は削除するキーです。
もちろん、delete
関数には他にも用途がありますが、この実験では辞書におけるその使い方についてのみ議論します。
存在しない辞書要素を検索するとどうなりますか。
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の開発者たちはこの問題を既に考えていました。辞書内の要素を照会する際、1つまたは2つの変数戻り値があります。
つまり:
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
これで、照会の2番目の戻り値を使って、返された結果が存在する初期値か、存在しない初期値かを判断することができます。
特定のシナリオでは、辞書全体を反復処理し、各キー-値ペアを照会して処理する必要があります。これをどのように実現できるでしょうか。
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
の特性