Golang 入門

Go 循環制御

Go 言語において、breakcontinue ステートメントは、通常のループ条件を打破し、ループを早期終了させたり、特定のイテレーション(反復)ステップをスキップしたりすることを可能にします。

単にループ条件が false になるのを待つだけでなく、これら 2 つのステートメントを柔軟に活用することで、コードのロジックをよりコンパクトにし、実行効率を高めることができます。

本章では、これらのメカニズムを深く掘り下げ、ラベル(Label) を利用して複雑なネストしたループ制御の問題を解決する方法を学びます。

1. break 文の深い理解

break ステートメントの役割は非常に明快です。最も内側の forswitch、または select ステートメントの実行を直ちに終了させます。プログラムが break に遭遇すると、現在のコードブロックを一瞬で抜け出し、ループ(または switch/select)構造の直後にある次の行のコードから実行を継続します。

1.1 for ループにおける基礎的な使い方

break の最も古典的な使い方は、データを走査(トラバース)している際、ターゲットが見つかった時点で無意味な後続の検索を即座に停止することです。

package main

import "fmt"

func main() {
	numbers := []int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}
	target := 5

	for i, num := range numbers {
		if num == target {
			fmt.Printf("インデックス %d でターゲット値 %d を見つけました\n", i, target)
			break // ターゲットが見つかったので、ループ全体を直ちに終了!
		}
		fmt.Printf("数字をチェック中: %d\n", num)
	}
	fmt.Println("ループが完全に終了しました")
}

実行結果:

数字をチェック中: 1
数字をチェック中: 2
数字をチェック中: 3
数字をチェック中: 4
インデックス 4 でターゲット値 5 を見つけました
ループが完全に終了しました

プログラムは 5 を検出し、break したため、残りの 6 から 10 は走査されませんでした。これにより、計算リソースを大幅に節約できます。

1.2 switch における break の罠

前章で学んだ通り、Go 言語の switch はデフォルトで break を必要とせず、マッチングに成功すると自動的に抜けます。

しかし、switchfor ループの中にネストさせ、switchcase 内で break を記述した場合は注意が必要です。この break は現在の switch を抜けるだけで、外側の for ループを抜けることはありません!

package main

import "fmt"

func main() {
	for i := 0; i < 3; i++ {
		switch i {
		case 1:
			fmt.Println("1に遭遇、breakを実行")
			break // 警告:ここでは switch を抜けるだけで、for ループは継続されます!
		default:
			fmt.Println("現在の数字:", i)
		}
		fmt.Println("--- for ループのイテレーションが1回終了 ---")
	}
}

もし、switch 内で特定の条件を満たした時に外側の for ループも同時に停止させたい場合は、ラベル付き break を使用する必要があります。

1.3 高度なテクニック:ラベル (Label) を使用した break

多重にネストしたループに直面した際、通常の break は最も内側のループしか抜けられません。内層から指定した外層のループを一気に、かつ「ピンポイント」で抜けるために、Go は ラベル (Label) メカニズムを導入しています。

package main

import "fmt"

func main() {
OuterLoop: // 1. OuterLoop という名前のラベルを定義し、外側の for ループの直上に配置
	for i := 0; i < 3; i++ {
		for j := 0; j < 3; j++ {
			fmt.Printf("i=%d, j=%d\n", i, j)

			if i == 1 && j == 1 {
				fmt.Println("条件達成。外側ループごと終了します!")
				break OuterLoop // 2. OuterLoop ラベルが指すループを抜けるよう指定
			}
		}
	}
	fmt.Println("外側のループが終了しました")
}

i=1 かつ j=1 の時、break OuterLoop は外側の for i ループを直接終了させ、プログラムは最後の一行である終了メッセージのプリントへと飛びます。

2. continue 文の深い理解

break が「完全に辞める」ことだとすれば、continue は「今回はパスして、次へ行く」というイメージです。

ループ内で continue に遭遇すると、プログラムは現在のイテレーションの残りのコードを即座に破棄し、ループの先頭へ戻って次のイテレーションの評価を開始します。

2.1 for ループにおける基礎的な使い方

continue は、関心のないデータを「フィルタリング」するのに非常に適しています。例えば、偶数だけをプリントする場合:

package main

import "fmt"

func main() {
	numbers := []int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}

	for _, num := range numbers {
		if num%2 != 0 { // 奇数の場合
			continue    // 以降のコードを無視し、次の num を取得しにいく
		}
		fmt.Printf("偶数を発見: %d\n", num)
	}
	fmt.Println("ループ終了")
}

奇数に遭遇すると continue がトリガーされ、後の fmt.Printf がスキップされ、ループは次の数字へと進みます。

2.2 ラベル (Label) を使用した continue

break と同様に、continue もラベルと組み合わせて使用できます。その役割は、現在の内層ループの実行を中断し、指定された外層ループの次のイテレーションを直接進めることです。

package main

import "fmt"

func main() {
OuterLoop:
	for i := 0; i < 3; i++ {
		for j := 0; j < 3; j++ {
			if i == 1 && j == 1 {
				fmt.Printf("i=1, j=1 に遭遇。現在の内ループを終了し、外ループの次のイテレーションを開始\n")
				continue OuterLoop // 内層ループの残りを放棄し、外側の i を 2 に進める
			}
			fmt.Printf("i=%d, j=%d\n", i, j)
		}
	}
	fmt.Println("外側ループ終了")
}

3. 実戦デモンストレーション:データクリーニング

continue を総合的に活用した実例を見てみましょう。大量のセンサーデータを読み取っており、負の数(無効なデータ)をすべて除外し、正の数のみを処理する必要があると仮定します。

package main

import "fmt"

func main() {
	data := []int{10, -5, 20, -3, 30, -1}

	fmt.Println("有効データの処理を開始:")
	for _, value := range data {
		if value <= 0 {
			// 異常データ(汚れたデータ)に遭遇、continue で簡単にスキップ
			continue 
		}

		// クリーンなデータのみがここへ到達
		fmt.Printf("有効な値を処理中: %d\n", value)
	}
}