制御フロー

制御フロー

あなたがキッチンで料理をしているシェフだと想像してください:フライパンが熱くなったら油を入れる(if);食材に応じて調理法を選ぶ(switch);完成するまでかき混ぜ続ける(for);皿に盛りつける前に火を消すのを忘れない(defer)。プログラムも同じように動作します — 制御フローはコードの実行パスを決定し、コンピュータに「判断する」方法と「タスクを繰り返す」方法を教えます。


1. コアコンセプト

Goの制御フロー文は簡潔でありながら強力で、主に以下のカテゴリに分かれます:

1.1 if / else 条件分岐

Goのif文には独特な特徴があります:条件の前に初期化文を追加できることで、セミコロン;で区切ります。変数のスコープはif/elseブロックに制限されます。

GO
// 基本形
if condition {
    // 条件が真の場合に実行
} else if otherCondition {
    // 他の条件が真の場合に実行
} else {
    // 上記のいずれにも該当しない場合に実行
}

// 初期化文付き
if initStatement; condition {
    // 初期化された変数はif/elseブロック内でのみ可視
}

1.2 switch文

Goのswitchには他の言語との2つの重要な違いがあります:

GO
switch variable {
case value1:
    // value1を処理
case value2, value3: // カンマ区切りで複数の値
    // value2またはvalue3を処理
default:
    // デフォルト処理
}

1.3 forループ

forはGoの唯一のループ構造です。他の言語のwhiledo-while、その他のループを置き換えます。

形式 構文 ユースケース
従来のfor for init; cond; post {} 反復回数が既知
Whileスタイル for cond {} 条件ループ
無限ループ for {} 継続的な実行
Rangeイテレーション for i, v := range collection {} コレクションのイテレーション

1.4 breakとcontinue

1.5 defer(遅延実行)

defer文は関数呼び出しを周囲の関数が返るまで延期します。複数のdefer呼び出しは後入れ先出し(LIFO)スタック順で実行されます。

GO
func example() {
    defer fmt.Println("最初に登録、最後に実行")
    defer fmt.Println("2番目に登録、最後から2番目に実行")
    fmt.Println("通常の実行")
}
// 出力順序:通常の実行 → 2番目に登録 → 最初に登録

2. 基本構文/使い方

if / else の使い方

GO
// 標準的なif/else
score := 85
if score >= 90 {
    fmt.Println("優秀")
} else if score >= 80 {
    fmt.Println("良好")
} else if score >= 60 {
    fmt.Println("可")
} else {
    fmt.Println("不可")
}
💡 ヒント: Goでは、if条件に括弧は不要ですが、波括弧{}は必須で、elseifの閉じ括弧と同じ行に記述する必要があります。これはGoの構文ルールで、従わないとコンパイルエラーになります。

GO
// 初期化文付きif
// errのスコープはif/elseブロックに制限され、外側からはアクセスできません
if err := doSomething(); err != nil {
    fmt.Println("エラー:", err)
}
💡 ヒント: 初期化文付きifはGoで非常に一般的で、特にエラー処理で使われます。変数のスコープを最小化し、外側のスコープを汚染しません。

switchの使い方

GO
// 基本的なswitch
day := "Wednesday"
switch day {
case "Monday":
    fmt.Println("新しい週の始まり")
case "Tuesday", "Wednesday", "Thursday":
    fmt.Println("平日")
case "Friday":
    fmt.Println("もうすぐ週末")
case "Saturday", "Sunday":
    fmt.Println("楽しい週末")
default:
    fmt.Println("無効な日")
}
💡 ヒント: Goのswitchでは各caseの後にbreakは不要です。本当に次のcaseに「フォールスルー」する必要がある場合は、fallthroughキーワードを使用しますが、これは稀です。

GO
// タグなしswitch(長いif-elseチェーンの置き換え)
score := 85
switch {
case score >= 90:
    fmt.Println("優秀")
case score >= 80:
    fmt.Println("良好")
case score >= 60:
    fmt.Println("可")
default:
    fmt.Println("不可")
}
💡 ヒント: switchの後に変数がない場合、switch trueと等価で、長いif-elseチェーンを置き換えてコードをよりクリーンにできます。

forループの使い方

GO
// 従来のforループ
for i := 0; i < 5; i++ {
    fmt.Println(i)
}

// Whileスタイル
count := 0
for count < 5 {
    fmt.Println(count)
    count++
}

// 無限ループ(breakと併用)
for {
    fmt.Println("Ctrl+Cで終了")
    break // 本当の無限ループを避けるためここではbreakを使用
}

deferの使い方

GO
func readFile(filename string) {
    file, err := os.Open(filename)
    if err != nil {
        fmt.Println("ファイルを開けませんでした:", err)
        return
    }
    defer file.Close() // 関数終了時にファイルを閉じる
    // ... ファイル内容の読み取り ...
}
💡 ヒント: deferの引数はdefer文が出現した時点で即座に評価され、関数が実行される時点ではありません。この詳細に注意して、予期しない動作を避けてください。


例1:基本的な使い方(難易度⭐)

この例ではif/elseswitchの基本的な使い方を示します。

GO
package main

import "fmt"

func main() {
    // ===== if/elseの例:成績判定 =====
    score := 78

    // 初期化文付きifを使用
    // rank変数はif/elseブロック内でのみ有効
    if rank := score / 10; rank >= 9 {
        fmt.Printf("点数 %d、成績:優秀\n", score)
    } else if rank >= 8 {
        fmt.Printf("点数 %d、成績:良好\n", score)
    } else if rank >= 6 {
        fmt.Printf("点数 %d、成績:可\n", score)
    } else {
        fmt.Printf("点数 %d、成績:不可\n", score)
    }

    // ===== switchの例:月から季節を判定 =====
    month := 8

    switch month {
    case 3, 4, 5:
        fmt.Printf("月 %d は春です\n", month)
    case 6, 7, 8:
        fmt.Printf("月 %d は夏です\n", month)
    case 9, 10, 11:
        fmt.Printf("月 %d は秋です\n", month)
    case 12, 1, 2:
        fmt.Printf("月 %d は冬です\n", month)
    default:
        fmt.Printf("%d は有効な月ではありません\n", month)
    }

    // ===== タグなしswitch:if-elseチェーンの置き換え =====
    hour := 14
    switch {
    case hour < 6:
        fmt.Println("早朝")
    case hour < 12:
        fmt.Println("午前")
    case hour < 18:
        fmt.Println("午後")
    default:
        fmt.Println("夕方")
    }
}
▶ 試してみよう

出力:

TEXT
点数 78、成績:可
月 8 は夏です
午後

要点:


例2:中級的な使い方(難易度⭐⭐)

この例ではforループの様々な形式とbreak / continueの使い方を示します。

GO
package main

import "fmt"

func main() {
    // ===== 従来のforループ:1から100までの合計 =====
    sum := 0
    for i := 1; i <= 100; i++ {
        sum += i
    }
    fmt.Printf("1から100までの合計: %d\n", sum)

    // ===== rangeでスライスをイテレーション =====
    fruits := []string{"Apple", "Banana", "Orange", "Grape"}
    for index, fruit := range fruits {
        fmt.Printf("インデックス %d: %s\n", index, fruit)
    }

    // ===== rangeで文字列をイテレーション(rune単位) =====
    text := "Go言語"
    for i, ch := range text {
        fmt.Printf("位置 %d: %c (Unicode: %U)\n", i, ch, ch)
    }

    // ===== break:7で割り切れる最初の数を見つける =====
    for i := 1; i <= 100; i++ {
        if i%7 == 0 {
            fmt.Printf("7で割り切れる最初の数: %d\n", i)
            break // 見つかったら直ちにループを終了
        }
    }

    // ===== continue:1-20の奇数を全て出力 =====
    fmt.Print("1-20の奇数: ")
    for i := 1; i <= 20; i++ {
        if i%2 == 0 {
            continue // 偶数をスキップし、次のイテレーションへ
        }
        fmt.Printf("%d ", i)
    }
    fmt.Println()

    // ===== ラベル付きbreak:外側のループから抜ける =====
    fmt.Println("2次元配列で数値5を検索:")
    matrix := [][]int{
        {1, 2, 3},
        {4, 5, 6},
        {7, 8, 9},
    }

found: // ラベル名
    for row, cols := range matrix {
        for col, val := range cols {
            if val == 5 {
                fmt.Printf("発見! 位置:行 %d、列 %d\n", row, col)
                break found // 外側のループから抜ける
            }
        }
    }
}
▶ 試してみよう

出力:

TEXT
1から100までの合計: 5050
インデックス 0: Apple
インデックス 1: Banana
インデックス 2: Orange
インデックス 3: Grape
位置 0: G (Unicode: U+0047)
位置 1: o (Unicode: U+006F)
位置 2: 語 (Unicode: U+8A9E)
位置 5: 言 (Unicode: U+8A00)
7で割り切れる最初の数: 7
1-20の奇数: 1 3 5 7 9 11 13 15 17 19
2次元配列で数列で数値5を検索:
発見! 位置:行 1、列 1

要点:


例3:総合応用(難易度⭐⭐⭐)

この例では全ての制御フロー文を組み合わせて、シンプルな学生の成績管理システムを実装します。

GO
package main

import "fmt"

// Student構造体
type Student struct {
    Name   string
    Scores []int
}

// GetAverageは平均点を計算します
func (s Student) GetAverage() float64 {
    if len(s.Scores) == 0 {
        return 0
    }
    total := 0
    for _, score := range s.Scores {
        total += score
    }
    return float64(total) / float64(len(s.Scores))
}

// GetGradeは平均点に基づいて成績を返します
func (s Student) GetGrade() string {
    avg := s.GetAverage()
    switch {
    case avg >= 90:
        return "A (優秀)"
    case avg >= 80:
        return "B (良好)"
    case avg >= 70:
        return "C (平均)"
    case avg >= 60:
        return "D (可)"
    default:
        return "F (不可)"
    }
}

// PrintReportは成績レポートを出力します
func PrintReport(students []Student) {
    fmt.Println("========================================")
    fmt.Println("         学生の成績レポート             ")
    fmt.Println("========================================")

    // deferでレポート終了が全学生データの後に確実に出力されるようにする
    defer fmt.Println("========================================")
    defer fmt.Println("         レポート終了                   ")

    for i, student := range students {
        // deferでLIFO順序をデモンストレーション
        defer func(name string, idx int) {
            fmt.Printf("[クリーンアップ] %sさんのレポート処理完了\n", name)
        }(student.Name, i)

        // 学生情報を出力
        fmt.Printf("\n学生 %d: %s\n", i+1, student.Name)
        fmt.Printf("  点数: %v\n", student.Scores)
        fmt.Printf("  平均: %.1f\n", student.GetAverage())
        fmt.Printf("  成績: %s\n", student.GetGrade())

        // 各科目の点数を分析
        subjects := []string{"国語", "数学", "英語"}
        for j, score := range student.Scores {
            // インデックスが範囲内であることを確認
            subject := "不明"
            if j < len(subjects) {
                subject = subjects[j]
            }

            // 各科目の点数をチェック
            if score < 60 {
                fmt.Printf("  ⚠ %s (%d) 不合格、再試験が必要です\n", subject, score)
            }
        }
    }
}

// FindTopStudentは最高平均点の学生を見つけます
func FindTopStudent(students []Student) (string, float64) {
    if len(students) == 0 {
        return "", 0
    }

    topName := students[0].Name
    topAvg := students[0].GetAverage()

    for _, s := range students[1:] {
        if avg := s.GetAverage(); avg > topAvg {
            topName = s.Name
            topAvg = avg
        }
    }

    return topName, topAvg
}

func main() {
    // 学生データを作成
    students := []Student{
        {"Alice", []int{85, 92, 78}},
        {"Bob", []int{90, 95, 88}},
        {"Charlie", []int{72, 58, 65}},
        {"Diana", []int{95, 98, 92}},
    }

    // 成績レポートを出力
    PrintReport(students)

    // 最優秀学生を見つける
    topName, topAvg := FindTopStudent(students)
    fmt.Printf("\n🏆 最優秀学生: %s (平均: %.1f)\n", topName, topAvg)
}
▶ 試してみよう

出力:

TEXT
========================================
         学生の成績レポート             ========================================

学生 1: Alice
  点数: [85 92 78]
  平均: 85.0
  成績: B (良好)

学生 2: Bob
  点数: [90 95 88]
  平均: 91.0
  成績: A (優秀)

学生 3: Charlie
  点数: [72 58 65]
  平均: 65.0
  成績: D (可)
  ⚠ 数学 (58) 不合格、再試験が必要です

学生 4: Diana
  点数: [95 98 92]
  平均: 95.0
  成績: A (優秀)
[クリーンアップ] Dianaさんのレポート処理完了
[クリーンアップ] Charlieさんのレポート処理完了
[クリーンアップ] Bobさんのレポート処理完了
[クリーンアップ] Aliceさんのレポート処理完了
========================================
         レポート終了                   ========================================

🏆 最優秀学生: Diana (平均: 95.0)

要点:


3. よくあるユースケース

ケース1:エラー処理チェーン(if + 初期化文)

Goでは、初期化文付きifの最も一般的な使い方はエラー処理です:

GO
package main

import (
    "fmt"
    "strconv"
)

func main() {
    // 数値をパースしてエラーを処理
    inputs := []string{"42", "abc", "100", "xyz", "0"}

    for _, input := range inputs {
        // 初期化で型変換を行い、条件でエラーをチェック
        if num, err := strconv.Atoi(input); err != nil {
            fmt.Printf("❌ %q をパースできません: %v\n", input, err)
        } else {
            fmt.Printf("✅ パース成功: %q → %d\n", input, num)
        }
    }
}

ケース2:条件フィルタリング付きMapイテレーション(for + switch)

GO
package main

import "fmt"

func main() {
    // 学生の成績テーブル
    students := map[string]int{
        "Alice":   85,
        "Bob":     92,
        "Charlie": 58,
        "Diana":   76,
        "Eve":     45,
    }

    // 各成績レベルの学生をカウント
    excellent, good, pass, fail := 0, 0, 0, 0

    for name, score := range students {
        switch {
        case score >= 90:
            excellent++
            fmt.Printf("⭐ %s: %d (優秀)\n", name, score)
        case score >= 80:
            good++
            fmt.Printf("👍 %s: %d (良好)\n", name, score)
        case score >= 60:
            pass++
            fmt.Printf("✅ %s: %d (可)\n", name, score)
        default:
            fail++
            fmt.Printf("❌ %s: %d (不可)\n", name, score)
        }
    }

    fmt.Printf("\nまとめ: 優秀 %d、良好 %d、可 %d、不可 %d\n",
        excellent, good, pass, fail)
}

❓ よくある質問

質問1:なぜGoのswitchにはbreakが不要なのですか?

Goの設計者たちは、Cのswitchのフォールスルー動作がバグの一般的な原因であると考えました。Goのswitchは各caseの後に自動的に終了し、忘れられたbreak文による意図しないフォールスルーを防ぎます。本当にフォールスルーが必要な場合は、fallthroughキーワードを明示的に使用できます。

GO
// fallthroughの使い方(稀)
x := 1
switch x {
case 1:
    fmt.Println("one")
    fallthrough // 次のcaseのコードを続行して実行
case 2:
    fmt.Println("two") // 実行される
default:
    fmt.Println("other")
}
// 出力:one \n two

質問2:for rangeの変数はコピーですか?

はい。for rangeの値変数は要素のコピーで、変更しても元のコレクションには影響しません。元のコレクションの要素を変更する必要がある場合は、インデックスでアクセスしてください。

GO
nums := []int{1, 2, 3, 4, 5}

// ❌ 誤り:コピーの変更
for _, n := range nums {
    n *= 2 // numsには影響しない
}

// ✅ 正しい:インデックスで元のスライスを変更
for i := range nums {
    nums[i] *= 2 // numsが変更される
}

質問3:deferの引数はいつ評価されますか?

deferの引数はdefer文が出現した時点で評価され、関数が返る時点ではありません。

GO
func main() {
    x := 10
    defer fmt.Println("defer内のx:", x) // xはここで10として評価される
    x = 20
    fmt.Println("変更後のx:", x)
}
// 出力:
// 変更後のx: 20
// defer内のx: 10  (20ではない!)

質問4:ループ内でgoroutineを使用する際、ループ変数を正しくキャプチャするには?

Go 1.22以前では、forループ変数は全てのイテレーションで同じアドレスを共有しており、goroutineで問題が発生する可能性がありました。Go 1.22+ではこの動作が修正され、各イテレーションが独自の変数を取得します。古いバージョンを使用している場合は、変数を引数として渡してください:

GO
// Go 1.22+ — 直接使用可能
for i := 0; i < 5; i++ {
    go func() {
        fmt.Println(i) // 各goroutineが独自のiを取得
    }()
}

// 古いバージョン — 明示的に引数として渡す
for i := 0; i < 5; i++ {
    go func(n int) {
        fmt.Println(n) // nはiのコピー
    }(i)
}

📖 まとめ


📝 演習

演習1(⭐)

年を入力として受け取り、うるう年かどうかを判定するプログラムを書いてください。ルール:

GO
package main

import "fmt"

func main() {
    year := 2024 // この値を変更してテスト

    // ここにコードを書いてください
    // ヒント:if init; cond の形式を使用
}

演習2(⭐⭐)

forループを使って九九の表を出力するプログラムを書いてください。フォーマット要件:

GO
package main

import "fmt"

func main() {
    // ここにコードを書いてください
    // ヒント:2つのネストされたforループを使用
    // 外側ループ i を1から9まで
    // 内側ループ j を1からiまで
}

演習3(⭐⭐⭐)

シンプルな数当てゲームを実装するプログラムを書いてください:

  1. ループを使ってユーザーが正解するまで繰り返し推測させる
  2. 各推測の後にヒントを与える:「大きすぎ」または「小さすぎ」
  3. deferを使ってゲームの開始と終了時刻を記録する
  4. switchを使って推測回数に基づいて異なる評価を与える(1-3:天才、4-6:良い、7+:頑張ろう)
GO
package main

import (
    "fmt"
    "math/rand"
    "time"
)

func main() {
    // deferで終了時刻を記録
    start := time.Now()
    defer func() {
        fmt.Printf("\nゲーム時間: %v\n", time.Since(start))
    }()

    // 1-100の乱数を生成
    target := rand.Intn(100) + 1
    attempts := 0

    fmt.Println("=== 数当てゲーム ===")
    fmt.Println("1-100の数字を考えています。推測してみてください!")

    // ここにコードを書いてください
    // ヒント:
    // 1. forループでゲームを続ける
    // 2. fmt.Scanでユーザー入力を読み取る
    // 3. switchで推測結果と評価レベルをチェック
    // 4. 正解したらbreakでループを抜ける
}

次のレッスン

次のレッスン:関数 → Goの関数定義、複数戻り値、可変長パラメータ、匿名関数、クロージャ、その他のコアコンセプトを学びます。

Web-Tutorial.com

Web-Tutorial 技術チーム

複数の開発者によって共同維持されているプログラミングチュートリアルプラットフォーム。各チュートリアルは専門分野の開発者が執筆・レビューしています。正確で信頼性の高いコンテンツを目指しています — 問題を見つけた場合はお知らせください。

100%