Range - Golang learning step 1-11
- 公開日
- カテゴリ:LearnTheBasics
- タグ:Golang,roadmap.sh,学習メモ
roadmap.sh > Go > Learn the Basics > Range の学習を進めていきます。
※ 学習メモとしての記録ですが、後にこのセクションを学ぶ道しるべとなるよう、ですます調で記載しています。
contents
開発環境
- チップ: Apple M2 Pro
- OS: macOS Sonoma
- go version: go1.23.2 darwin/arm64
Range(レンジ)
Go の range は、配列やスライス、マップ、文字列、チャネルなどのデータ構造をループ処理するために使われるキーワードです。特定のコレクションに対して要素を繰り返し処理するのに非常に便利です。
- [official] Go Ranges
- [article] Go by Example: Range
- [article] Go ranges basic patterns
配列やスライスに対する range
range を配列やスライスに使用すると、インデックスとそのインデックスにある要素の 2 つの値が返されます。
numbers := []int{1, 2, 3, 4, 5}
for i, v := range numbers {
fmt.Printf("インデックス: %d, 値: %d\n", i, v)
}
- i は現在のインデックスを表します。
- v はそのインデックスにある値です。
実行結果:
インデックス: 0, 値: 1
インデックス: 1, 値: 2
インデックス: 2, 値: 3
インデックス: 3, 値: 4
インデックス: 4, 値: 5
もしインデックスが不要で値だけを扱いたい場合は、次のようにインデックスを無視することができます。
numbers := []int{1, 2, 3, 4, 5}
for _, v := range numbers {
fmt.Printf("値: %d\n", v)
}
// 出力:
// 値: 1
// 値: 2
// 値: 3
// 値: 4
// 値: 5
インデックスのみ使用することも可能です。
numbers := []int{1, 2, 3, 4, 5}
for i := range numbers {
fmt.Printf("インデックス: %d\n", i)
}
// 出力:
// インデックス: 0
// インデックス: 1
// インデックス: 2
// インデックス: 3
// インデックス: 4
注意点
range によってスライスや配列の要素が値として渡されるため、ループ内で値を変更しても元のスライスや配列には影響しません。これは、要素がコピーされてループ内で処理されるためです。元のスライスの要素を変更するには、インデックスを使ってアクセスする必要があります。
numbers := []int{1, 2, 3, 4, 5}
for _, v := range numbers {
v += 10 // この操作は元のスライスに影響を与えません
}
fmt.Println(numbers) // 出力: [1 2 3 4 5]
for i := range numbers {
numbers[i] += 10 // 直接スライスの要素を変更
}
fmt.Println(numbers) // 出力: [11 12 13 14 15]
マップに対する range
マップに range を使用すると、キーと値のペアが返されます。
ages := map[string]int{"Alice": 25, "Bob": 30}
for k, v := range ages {
fmt.Printf("キー: %s, 値: %d\n", k, v)
}
- k はマップのキーを表します。
- v はそのキーに対応する値です。
実行結果:
キー: Alice, 値: 25
キー: Bob, 値: 30
配列同様、キーのみを使用することも可能です。
ages := map[string]int{"Alice": 25, "Bob": 30}
for k := range ages {
fmt.Printf("キー: %s\n", k)
}
// 出力:
// キー: Alice
// キー: Bob
ただし、Go のマップはキーの順序が保証されないため、ループの順序は毎回異なる場合があります。
文字列に対する range
文字列に対して range を使用すると、インデックス(バイト位置)と Unicode コードポイント(ルーン)が返されます。Go の文字列は UTF-8 でエンコードされており、1 文字が複数バイトで構成される場合があります。
// ASCII文字(1バイト)の場合
word1 := "Hello"
for i, r := range word1 {
fmt.Printf("バイト位置: %d, ルーン: %c, バイト数: %d\n", i, r, utf8.RuneLen(r))
}
// 日本語(3バイト)の場合
word2 := "こんにちは"
for i, r := range word2 {
fmt.Printf("バイト位置: %d, ルーン: %c, バイト数: %d\n", i, r, utf8.RuneLen(r))
}
- i は文字の開始バイト位置を表します(UTF-8エンコーディングのため、1文字ごとに1ずつ増えるわけではありません)
- r は Unicode コードポイントとしての文字です
実行結果:
バイト位置: 0, ルーン: H, バイト数: 1
バイト位置: 1, ルーン: e, バイト数: 1
バイト位置: 2, ルーン: l, バイト数: 1
バイト位置: 3, ルーン: l, バイト数: 1
バイト位置: 4, ルーン: o, バイト数: 1
バイト位置: 0, ルーン: こ, バイト数: 3
バイト位置: 3, ルーン: ん, バイト数: 3
バイト位置: 6, ルーン: に, バイト数: 3
バイト位置: 9, ルーン: ち, バイト数: 3
バイト位置: 12, ルーン: は, バイト数: 3
この例から分かるように、ASCII 文字は 1 バイトで表現されるため、インデックスは 1 ずつ増加します。一方で日本語などの Unicode 文字は 3 バイトで表現されるため、インデックスは 3 ずつ増加します。
また、絵文字などの一部のUnicode文字は 4 バイト使用する場合もあります。
注意点
- バイト数を取得するには utf8.RuneLen() を使用する(要 import "unicode/utf8")
- 文字列の長さを文字数として取得するには len(string) ではなく utf8.RuneCountInString() を使用する
チャネルに対する range
チャネルに対して range を使うと、チャネルから送信される値を反復処理できます。チャネルが閉じられるまで値を受信し続けます。
ch := make(chan int, 2)
ch <- 1
ch <- 2
close(ch)
for v := range ch {
fmt.Println(v)
}
この例では、チャネルが閉じられると range ループは自動的に終了します。
実行結果:
1
2
もしチャネルが閉じられない場合、range ループは無限に待機し続けるため、チャネルを閉じることが重要です。
まとめ
- range は、配列やスライス、マップ、文字列、チャネルに対してループ処理を行うためのキーワード。
- 配列やスライスに対する range は、インデックスと要素の値を返す。インデックスや値を無視することもできる。
- 値はコピーされるため、ループ内での変更は元のデータに影響しない
- 元のデータを変更するにはインデックスを使用する必要がある
- マップに対する range は、キーと値のペアを返すが、マップ内の順序は保証されない。
- 文字列に対する range は、バイト位置と Unicode コードポイントを返す。UTF-8 文字列の特性上、1文字が複数バイトに対応する場合がある。
- バイト位置(インデックス)とUnicodeコードポイント(ルーン)を取得
- ASCII文字は1バイト、日本語などは3バイト、絵文字は4バイトで表現
- 正確な文字数を取得するにはutf8.RuneCountInString()を使用
- チャネルに対する range は、チャネルから送信された値を反復処理し、チャネルが閉じられると自動的にループが終了する。
- チャネルから送信される値を連続して受信
- チャネルが閉じられるまでループが継続
- チャネルを適切に閉じることが重要
[Next] Step 1-12: Errors/Panic/Recover
[Prev] Step 1-10: for 文(For Loop)