Goにおけるイテレーションのマスター:forループの徹底解説
Lukas Schneider
DevOps Engineer · Leapcell

Goのイテレーションへのアプローチは、エレガントかつ実用的です。多くの他の言語がwhile
、do-while
、その他のさまざまなループ構造を提供しているのに対し、Goはすべてを単一で非常に柔軟なfor
キーワードに集約しています。この一見制限された選択肢は、実際には計り知れない力と明確さを与え、開発者がさまざまなループパターンを容易に表現できるようにします。この記事では、Goのfor
ループのさまざまな形式、すなわち従来のC言語スタイル3要素ループと慣用的なfor-range
ループを深く掘り下げていきます。
従来のfor
ループ:3要素形式
Goのfor
ループの最も基本的で広く認識されている形式は、C言語スタイルのfor
ループと同等であり、セミコロンで区切られた3つのオプションコンポーネント(初期化
、条件
、事後ステートメント
)で構成されます。
一般的な構文は次のとおりです。
for 初期化; 条件; 事後ステートメント { // ループ本体 }
各コンポーネントを分解してみましょう。
-
初期化
: このステートメントは、ループが開始される前に一度だけ実行されます。通常、ループカウンターまたはループの実行に必要な変数を宣言および初期化するために使用されます。ここで宣言された変数は、for
ループにのみスコープされます。 -
条件
: このブール式は、各イテレーションの前に評価されます。true
と評価された場合、ループ本体が実行されます。false
と評価された場合、ループは終了します。省略された場合、条件はデフォルトでtrue
になり、無限ループが作成されます(break
を使用して中断できます)。 -
事後ステートメント
: このステートメントは、ループ本体の各イテレーションの後に実行されます。ループカウンターの更新(例:i++
)によく使用されます。
以下は、0から4までをイテレートする古典的な例です。
package main import "fmt" func main() { fmt.Println("--- Traditional for loop ---") for i := 0; i < 5; i++ { fmt.Printf("Iteration %d\n", i) } }
出力:
--- Traditional for loop ---
Iteration 0
Iteration 1
Iteration 2
Iteration 3
Iteration 4
柔軟性:コンポーネントの省略
Goのfor
ループのデザインは、3つのコンポーネントすべてがオプションであるため、信じられないほど柔軟です。
1. 初期化
と事後ステートメント
の省略(「while」ループ)
初期化
と事後ステートメント
の部分を省略すると、for
ループは他の言語のwhile
ループのように動作します。条件
が存在する場合は、セミコロンが必要です。
package main import "fmt" func main() { sum := 1 fmt.Println("\n--- For loop as 'while' loop ---") for sum < 1000 { // 初期化も事後ステートメントもなし sum += sum fmt.Printf("Current sum: %d\n", sum) } fmt.Printf("Final sum after 'while' loop: %d\n", sum) }
出力:
--- For loop as 'while' loop ---
Current sum: 2
Current sum: 4
Current sum: 8
Current sum: 16
Current sum: 32
Current sum: 64
Current sum: 128
Current sum: 256
Current sum: 512
Current sum: 1024
Final sum after 'while' loop: 1024
初期化
が省略された場合、sum
はループ内およびループ終了後にアクセスできるように、ループの外で宣言する必要があることに注意してください。
2. すべてのコンポーネントの省略(無限ループ)
3つのコンポーネントすべてを省略すると、無限ループが作成されます。このパターンは、サーバー、バックグラウンドプロセス、またはbreak
ステートメントに依存してループを終了する状況に役立ちます。
package main import ( "fmt" "time" ) func main() { fmt.Println("\n--- Infinite for loop ---") counter := 0 for { // 無限ループ fmt.Printf("Tick %d...\n", counter) counter++ time.Sleep(500 * time.Millisecond) // 一部の作業をシミュレート if counter >= 3 { fmt.Println("Breaking infinite loop.") break // ループを終了 } } fmt.Println("Infinite loop terminated.") }
出力:
--- Infinite for loop ---
Tick 0...
Tick 1...
Tick 2...
Breaking infinite loop.
Infinite loop terminated.
for-range
ループ:コレクションのイテレーション
3要素のfor
ループはカウンターベースのイテレーションに優れていますが、Goは配列、スライス、文字列、マップ、チャネルなどのコレクションをイテレートするための、より便利で慣用的な方法を提供します。それがfor-range
ループです。
for-range
ループは、コレクションの要素のインデックス/キーと値の両方にアクセスできるようにすることでイテレーションを簡素化し、手動のインデックス管理の必要性をなくします。
一般的な構文は次のとおりです。
for インデックス, 要素 := range コレクション { // ループ本体 }
または、値のみが必要な場合:
for _, 要素 := range コレクション { // ループ本体 }
インデックス/キーのみが必要な場合:
for インデックス := range コレクション { // 要素は暗黙的に破棄される // ループ本体 }
さまざまなデータ型での使用方法を探ってみましょう。
1. スライスと配列のイテレーション
スライスまたは配列をイテレートすると、range
は各要素に対して2つの値、つまりインデックスと要素の値のコピーを返します。
package main import "fmt" func main() { numbers := []int{10, 20, 30, 40, 50} fmt.Println("\n--- For-range over a slice ---") for i, num := range numbers { fmt.Printf("Index: %d, Value: %d\n", i, num) } // 値のみに関心がある場合 fmt.Println("\n--- For-range over a slice (values only) ---") total := 0 for _, num := range numbers { // インデックスを無視するために空白識別子'_'を使用 total += num } fmt.Printf("Total sum of numbers: %d\n", total) // インデックスのみに関心がある場合 fmt.Println("\n--- For-range over a slice (indices only) ---") for i := range numbers { // 値は返されず、使用されません fmt.Printf("Processing element at index: %d\n", i) } }
出力:
--- For-range over a slice ---
Index: 0, Value: 10
Index: 1, Value: 20
Index: 2, Value: 30
Index: 3, Value: 40
Index: 4, Value: 50
--- For-range over a slice (values only) ---
Total sum of numbers: 150
--- For-range over a slice (indices only) ---
Processing element at index: 0
Processing element at index: 1
Processing element at index: 2
Processing element at index: 3
Processing element at index: 4
2. 文字列のイテレーション
文字列をイテレートすると、range
は先頭文字のバイトインデックスとUnicodeのrune(文字)自体を提供します。これは、マルチバイトUnicode文字を正しく処理するために重要です。
package main import "fmt" func main() { sentence := "Hello, 世界" // "世界" はマルチバイトUnicode文字です fmt.Println("\n--- For-range over a string ---") for i, r := range sentence { fmt.Printf("Byte Index: %2d, Rune: '%c' (Unicode: %U)\n", i, r, r) } }
出力:
--- For-range over a string ---
Byte Index: 0, Rune: 'H' (Unicode: U+0048)
Byte Index: 1, Rune: 'e' (Unicode: U+0065)
Byte Index: 2, Rune: 'l' (Unicode: U+006C)
Byte Index: 3, Rune: 'l' (Unicode: U+006C)
Byte Index: 4, Rune: 'o' (Unicode: U+006F)
Byte Index: 5, Rune: ',' (Unicode: U+002C)
Byte Index: 6, Rune: ' ' (Unicode: U+0020)
Byte Index: 7, Rune: '世' (Unicode: U+4E16)
Byte Index: 10, Rune: '界' (Unicode: U+754C)
バイトインデックスが6から7へ、そしてマルチバイト文字世
と界
の場合は7から10へとスキップされることに注意してください。range
はバイトではなく、runeを正しくイテレートします。
3. マップのイテレーション
マップをイテレートすると、range
はキーと値のペアを返します。マップ要素のイテレーション順序は保証されておらず、変動する可能性があります。
package main import "fmt" func main() { scores := map[string]int{ "Alice": 95, "Bob": 88, "Charlie": 92, } fmt.Println("\n--- For-range over a map ---") for name, score := range scores { fmt.Printf("%s scored %d\n", name, score) } // キーのみに関心がある場合 fmt.Println("\n--- For-range over a map (keys only) ---") fmt.Println("Students in the class:") for name := range scores { // スコアは返されず、使用されません fmt.Printf("- %s\n", name) } }
出力(順序は変動する可能性があります):
--- For-range over a map ---
Alice scored 95
Bob scored 88
Charlie scored 92
--- For-range over a map (keys only) ---
Students in the class:
- Alice
- Bob
- Charlie
4. チャネルのイテレーション
チャネルをイテレートすると、range
はチャネルが閉じられるまでチャネルから値を読み取ります。
package main import ( "fmt" "time" ) func main() { fmt.Println("\n--- For-range over a channel ---") ch := make(chan int) // チャネルに数値を送信するゴルーチン go func() { for i := 0; i < 3; i++ { ch <- i * 10 time.Sleep(100 * time.Millisecond) } close(ch) // 送信完了したらチャネルを閉じる(重要) }() // チャネルから数値を受信するメインゴルーチン for num := range ch { fmt.Printf("Received: %d\n", num) } fmt.Println("Channel closed, iteration complete.") }
出力:
--- For-range over a channel ---
Received: 0
Received: 10
Received: 20
Channel closed, iteration complete.
for-range
ループは、ch
チャネルが送信者によって明示的にclose
されるまで値の受信を続けます。チャネルが閉じられていない場合、ループは無期限にブロックされ、それ以上値が送信されないとデッドロックが発生する可能性があります。
どちらを使うべきか?
-
3要素
for
ループ:- ループカウンターを正確に制御する必要がある場合(例:指定回数イテレート、1より大きいステップでイテレート、逆順にイテレート)。
- イテレーションロジックがコレクションに直接マッピングされない場合。
- 「while」または「無限」ループパターンを実装する場合。
-
for-range
ループ:- スライス、配列、文字列、マップ、チャネルの要素をイテレートするための推奨される方法。
- インデックス/キーと値の両方(またはどちらか一方)が必要な場合。
- コレクションの走査のための、明確で簡潔で慣用的なコードを望む場合。
- コレクションの長さを自動的に処理し、文字列のUnicodeの複雑さを管理するため、一般的なオフバイワンエラーを減らします。
結論
Goの統合されたfor
ループは、シンプルさとパワーという設計哲学の証です。すべてのイテレーション構造を単一のキーワードに集約することにより、Goは学習曲線を合理化し、一貫性を促進します。従来の3要素for
ループは、順次および条件付きイテレーションのための詳細な制御を提供し、for-range
ループはコレクションを走査するためのエレガントで安全な方法を提供します。両方の形式をマスターすることは、効果的で慣用的なGoプログラムを作成するための基本です。これらの強力なイテレーションツールを受け入れることで、データを効率的に処理し、堅牢なアプリケーションを構築できます。