Go言語のforを使ってループ処理を行う方法についてわかりやすく解説

スポンサーリンク
スポンサーリンク
amazon
スマイルSALE
--:--:--
ad. 価格範囲を指定して商品を探せます

forの概要

繰り返し処理(ループ)を行うGoの予約語

for

概要 forは、Go言語における唯一のループ(繰り返し)制御構文であり、指定した条件式が真(true)である間、または対象データ構造を走査しきるまでブロック内の命令群を反復実行するために使用します。

  • Go言語の言語仕様には whiledo-while キーワードが存在せず、すべての反復処理(条件式ループ、無限ループ、コレクション走査)を for 構文のみに一本化している。
  • 標準的な「初期化・条件・後処理」によるカウンタ制御のほか、条件式のみ、あるいは完全省略による無限ループまで柔軟に変形可能。
  • range キーワードと協調することで、非同期処理のチャネル(channel)からの継続的なデータ受信、スライス、マップなどの要素走査を安全に記述できる。

forの考え方をイメージで理解

基本的なforループの使い方(カウンタによる回数指定制御)

C言語やJavaなどと同様に、「初期化文」「条件式」「後処理文」をセミコロン(;)で区切って指定する、最も伝統的なループ構造です。

package main

import "fmt"

func main() {
    // iの初期化、継続条件、各ステップのインクリメントを1行で定義
    for i := 1; i <= 5; i++ {
        fmt.Println("カウント:", i)
    }
}

構造解説:

  • i := 1 がループ突入時に一度だけ実行され、i <= 5 が満たされている間、波括弧内のロジックが繰り返されます。
  • ブロック内の最終行を実行し終えるたびに i++ が計算され、再び条件評価へと戻ります。この i はこの for ブロック内のみのローカルスコープに縛られます。

実行結果:

カウント: 1
カウント: 2
カウント: 3
カウント: 4
カウント: 5

whileのように使うforループ(単一の判定式による制御)

Go言語には while がないため、セミコロンによる区切りをすべて省略し、「継続条件式」のみを for の後ろに記述することで、他言語の while ループと完全に同等の挙動を表現します。

package main

import "fmt"

func main() {
    running := true
    count := 1

    // runningがtrueである限り、無限にループを継続
    for running {
        fmt.Println("処理を実行中... 回数:", count)
        if count >= 3 {
            running = false // フラグを倒して次の評価時にループを抜ける
        }
        count++
    }
}

構造解説:

  • for running { ... } は、running == true であるかを毎周評価します。
  • ループの内部で条件に関わる変数(runningcount)を書き換えることで、動的な終了タイミングを制御するスタンスに最適です。

実行結果:

処理を実行中... 回数: 1
処理を実行中... 回数: 2
処理を実行中... 回数: 3

無限ループの構築と内部割込による脱出

for キーワードの後に条件式を一切記述しない(空にする)ことで、意図的な永久無限ループを生成できます。この場合、内部から breakreturn を用いて明示的に離脱させる必要があります。

package main

import "fmt"

func main() {
    cycle := 0
    
    // 条件なし = 常にtrueとみなされる無限ループ空間
    for {
        cycle++
        if cycle == 3 {
            fmt.Println("ブレイク条件に達したため無限ループを強制遮断します")
            break // このコマンドで直近のforブロックから即時離脱
        }
        fmt.Println("常時待機タスクを処理中...", cycle)
    }
}

構造解説:

  • for { ... } はイベント駆動型サーバーの接続待機ルーチンなどで多用されます。
  • 内部の if cycle == 3 というガード文がトリガーとなり、break 命令が発動した瞬間にループ構造の終端よりも後ろの行へとプログラムの制御が移ります。

実行結果:

常時待機タスクを処理中... 1
常時待機タスクを処理中... 2
ブレイク条件に達したため無限ループを強制遮断します

range演算子と組み合わせたコレクション走査(イテレータ制御)

スライス、配列、マップ(連想配列)などの集合データを安全かつインデックスの範囲外例外(レイズ)を起こさずに先頭から走査するための最もクリーンなアプローチです。

package main

import "fmt"

func main() {
    languages := []string{"Go", "Python", "Rust"}

    // rangeにより、インデックス番号と格納データが自動分解されて代入される
    for idx, name := range languages {
        fmt.Printf("位置 [%d] のプログラミング言語: %s\n", idx, name)
    }
}

構造解説:

  • range languages は対象オブジェクトの要素数に応じてループ回数を自動的に静的確定させます。
  • 1番目の変数(idx)には要素の最小インデックスから最大値までが割り当たり、2番目の変数(name)にはそのインデックスの実要素のデータ(値のコピー)が自動的にマッピングされます。

実行結果:

位置 [0] のプログラミング言語: Go
位置 [1] のプログラミング言語: Python
位置 [2] のプログラミング言語: Rust

ラベル指定による多重ネストループのクロスレイヤー制御

二次元配列の特定探索などで、多重に組まれた外側と内側のループ関係に対し、内側から直接外側の周回へアプローチをかける場合は、ループの直上に大文字から始まる任意のラベルを定義して制御します。

package main

import "fmt"

func main() {
// 外側の親ループに対してマトリクスタグを定義
MatrixSearch:
    for i := 1; i <= 3; i++ {
        for j := 1; j <= 3; j++ {
            if j == 2 {
                // 内側のループの残りを破棄し、直接親ループの「i++」のステップへ跳躍する
                continue MatrixSearch
            }
            fmt.Printf("座標展開 -> i: %d, j: %d\n", i, j)
        }
    }
}

構造解説:

  • 内側ループ内の j == 2true になると、continue MatrixSearch が実行されます。
  • これにより、現在の j の残りの反復が終了するだけでなく、j = 3 の回もスキップされ、制御の軸が直接最外周の MatrixSearch の次のカウンタステップ(i のインクリメント)へと遷移します。

実行結果:

座標展開 -> i: 1, j: 1
座標展開 -> i: 2, j: 1
座標展開 -> i: 3, j: 1

学術的・設計的注意点

  • rangeによる値のコピー(ポインタ参照の誤解): for _, value := range slice と記述してデータを走査する際、変数 value に格納されるデータは、スライス内の実体ではなく「値のコピー」です。したがって、ループの内部で value = "NewData" のように上書き操作を加えても、元のスライスの中身は 1 ビットも変更されません。もし要素を直接書き換えたい場合は、インデックスを用いて slice[idx] = "NewData" のように実メモリ空間へ直接アクセスをかける設計にする必要があります。
  • ゴルーチン(並行処理)内でのループ変数のキャプチャ問題(Go歴史的背景): かつてのGo言語の仕様において、for i := 0; i < 5; i++ 内で go func() { fmt.Println(i) }() のように並行処理を立ち上げると、すべてのゴルーチンが同じメモリ番地の i を参照してしまい、実行時のタイミングによって全員が最後の「5」を出力してしまうという深刻な変数共有の罠(ループ変数キャプチャ問題)が存在しました。この問題は非常に多くの開発者を苦しめたため、近年の言語アップデート(Go 1.22以降)によって言語仕様そのものが改定され、ループのイテレーション(周)ごとに新しい変数インスタンスが内部的に再生成される挙動へと変更され、安全性が完全に担保されるようになりました。

よくある質問(FAQ)

Q: Go言語にはなぜ do-while 構文が用意されていないのですか?
A: 「言語仕様を極限までシンプルに保ち、キーワードの重複による認知負荷を下げる」というGo言語の設計哲学に基づいているためです。最初の1回目を確実に実行させたい do-while 相当の処理は、for の無限ループ(for {})を開始し、ブロックの「最末尾」に if 条件 { break } を配置することで、全く同じロジックを1つのキーワード(for)だけで過不足なく綺麗に再現可能です。
Q: rangeでインデックス(または値)の片方しか使わない場合、記述を省略できますか?
A: インデックス(1番目の戻り値)のみを使用する場合は、単純に2番目を書かずに for idx := range slice と記述するだけで省略が可能です。しかし、値(2番目の戻り値)だけを使用したい場合は、1番目の位置を空白識別子(アンダースコア:_)で明示的にスキップさせる必要があり、for _, val := range slice と記述しなければコンパイルエラーになります。
Q: マップ(map)を for range で走査した際、データが取り出される順番は常に一定ですか?
A: いいえ、完全にランダム(非決定的)です。Go言語の仕様上、マップを走査する際の開始位置や走査順序は、実行されるたびに内部のハッシュアルゴリズムによって意図的にシャッフルされます。これは、開発者が「たまたま順序通りに出力された結果」に依存したバグだらけの不安定なコードを執筆するのを防ぐための、Goランタイム側のあえての制約仕様です。順序を固定したい場合は、一度キーのリストをスライスに抽出してソートし、そのスライスを for で回す必要があります。

まとめ

  • for は、他言語の whiledo-while の役割をすべて包含・統合した、Go言語における単一にして万能の繰り返し制御予約語。
  • カウンタ制御、単一条件評価、完全省略による無限ループの3形態へ、セミコロンの配置方法1つで自在に変形させることが可能。
  • range 演算子との併用により、メモリ安全性を高レベルで維持したまま配列やスライス、マップ、チャネルなどの反復走査を定型化できる。
  • Go 1.22以降はループ変数のスコープがイテレーション毎に分離されるよう改善され、並行処理(Goroutine)を組み合わせる際のアクシデントが構造的に排除されている。