制御フロー
制御フロー
あなたがキッチンで料理をしているシェフだと想像してください:フライパンが熱くなったら油を入れる(if);食材に応じて調理法を選ぶ(switch);完成するまでかき混ぜ続ける(for);皿に盛りつける前に火を消すのを忘れない(defer)。プログラムも同じように動作します — 制御フローはコードの実行パスを決定し、コンピュータに「判断する」方法と「タスクを繰り返す」方法を教えます。
1. コアコンセプト
Goの制御フロー文は簡潔でありながら強力で、主に以下のカテゴリに分かれます:
1.1 if / else 条件分岐
Goのif文には独特な特徴があります:条件の前に初期化文を追加できることで、セミコロン;で区切ります。変数のスコープはif/elseブロックに制限されます。
// 基本形
if condition {
// 条件が真の場合に実行
} else if otherCondition {
// 他の条件が真の場合に実行
} else {
// 上記のいずれにも該当しない場合に実行
}
// 初期化文付き
if initStatement; condition {
// 初期化された変数はif/elseブロック内でのみ可視
}
1.2 switch文
Goのswitchには他の言語との2つの重要な違いがあります:
- 自動break:各
caseは自動的に終了し、次のcaseに「フォールスルー」しません。 - 型switchサポート:変数の型をチェックできます。
switch variable {
case value1:
// value1を処理
case value2, value3: // カンマ区切りで複数の値
// value2またはvalue3を処理
default:
// デフォルト処理
}
1.3 forループ
forはGoの唯一のループ構造です。他の言語のwhile、do-while、その他のループを置き換えます。
| 形式 | 構文 | ユースケース |
|---|---|---|
| 従来のfor | for init; cond; post {} |
反復回数が既知 |
| Whileスタイル | for cond {} |
条件ループ |
| 無限ループ | for {} |
継続的な実行 |
| Rangeイテレーション | for i, v := range collection {} |
コレクションのイテレーション |
1.4 breakとcontinue
break:現在のループを直ちに終了します。continue:現在のイテレーションの残りのコードをスキップし、次のイテレーションに進みます。
1.5 defer(遅延実行)
defer文は関数呼び出しを周囲の関数が返るまで延期します。複数のdefer呼び出しは後入れ先出し(LIFO)スタック順で実行されます。
func example() {
defer fmt.Println("最初に登録、最後に実行")
defer fmt.Println("2番目に登録、最後から2番目に実行")
fmt.Println("通常の実行")
}
// 出力順序:通常の実行 → 2番目に登録 → 最初に登録
2. 基本構文/使い方
if / else の使い方
// 標準的なif/else
score := 85
if score >= 90 {
fmt.Println("優秀")
} else if score >= 80 {
fmt.Println("良好")
} else if score >= 60 {
fmt.Println("可")
} else {
fmt.Println("不可")
}
if条件に括弧は不要ですが、波括弧{}は必須で、elseはifの閉じ括弧と同じ行に記述する必要があります。これはGoの構文ルールで、従わないとコンパイルエラーになります。
// 初期化文付きif
// errのスコープはif/elseブロックに制限され、外側からはアクセスできません
if err := doSomething(); err != nil {
fmt.Println("エラー:", err)
}
ifはGoで非常に一般的で、特にエラー処理で使われます。変数のスコープを最小化し、外側のスコープを汚染しません。
switchの使い方
// 基本的な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("無効な日")
}
switchでは各caseの後にbreakは不要です。本当に次のcaseに「フォールスルー」する必要がある場合は、fallthroughキーワードを使用しますが、これは稀です。
// タグなし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ループの使い方
// 従来の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の使い方
func readFile(filename string) {
file, err := os.Open(filename)
if err != nil {
fmt.Println("ファイルを開けませんでした:", err)
return
}
defer file.Close() // 関数終了時にファイルを閉じる
// ... ファイル内容の読み取り ...
}
deferの引数はdefer文が出現した時点で即座に評価され、関数が実行される時点ではありません。この詳細に注意して、予期しない動作を避けてください。
例1:基本的な使い方(難易度⭐)
この例ではif/elseとswitchの基本的な使い方を示します。
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("夕方")
}
}
出力:
点数 78、成績:可
月 8 は夏です
午後
要点:
rank := score / 10は初期化文で、rankのスコープはif/elseブロックに制限されますswitchのcase 3, 4, 5は3、4、5のいずれかにマッチすることを意味します- タグなし
switchは複雑なif-elseチェーンの置き換えに適しています
例2:中級的な使い方(難易度⭐⭐)
この例ではforループの様々な形式とbreak / continueの使い方を示します。
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 // 外側のループから抜ける
}
}
}
}
出力:
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
要点:
for i := 1; i <= 100; i++は標準的な3部構成のループですrangeでスライスをイテレーションするとインデックスと値が返され、文字列ではUnicode文字(rune)単位でイテレーションしますbreak foundはラベルを使って指定された外側のループから抜けます。ネストされたループで非常に便利です
例3:総合応用(難易度⭐⭐⭐)
この例では全ての制御フロー文を組み合わせて、シンプルな学生の成績管理システムを実装します。
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)
}
出力:
========================================
学生の成績レポート ========================================
学生 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)
要点:
- 複数の
defer登録はLIFO(後入れ先出し)順序で実行されます:Diana → Charlie → Bob → Alice defer fmt.Println("レポート終了")は最初に登録されましたが最後に実行されます(最早に登録されたため、最後にポップされます)- タグなし
switchは成績判定の複雑なif-elseチェーンを置き換えます for rangeは構造体のスライスをイテレーションし、for i, s := range students[1:]は2番目の要素から比較を開始します- 匿名関数内の
deferはパラメータ値を即座にキャプチャします(student.Nameとiのコピーを渡します)
3. よくあるユースケース
ケース1:エラー処理チェーン(if + 初期化文)
Goでは、初期化文付きifの最も一般的な使い方はエラー処理です:
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)
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キーワードを明示的に使用できます。
// 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の値変数は要素のコピーで、変更しても元のコレクションには影響しません。元のコレクションの要素を変更する必要がある場合は、インデックスでアクセスしてください。
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文が出現した時点で評価され、関数が返る時点ではありません。
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 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)
}
📖 まとめ
if/elseは初期化文をサポートし(if init; cond {})、変数のスコープはブロックに制限されますswitchはデフォルトでbreakが不要で、各caseは自動的に終了し、タグなしswitchはif-elseチェーンの代替として使用できますforはGoの唯一のループで、4つの形式があります:従来のfor、Whileスタイル、無限ループ、rangeイテレーションbreakはラベルと一緒に使用して特定のループレベルから抜けられ、continueは現在のイテレーションをスキップしますdeferは後入れ先出し(LIFO)順序で実行され、引数はdefer文で即座に評価されますrangeはコピーを返し、コピーの変更は元のコレクションに影響しません
📝 演習
演習1(⭐)
年を入力として受け取り、うるう年かどうかを判定するプログラムを書いてください。ルール:
- 年が4で割り切れて100で割り切れないか、400で割り切れる場合はうるう年です。
- 初期化文付き
ifの形式を使用してください。
package main
import "fmt"
func main() {
year := 2024 // この値を変更してテスト
// ここにコードを書いてください
// ヒント:if init; cond の形式を使用
}
演習2(⭐⭐)
forループを使って九九の表を出力するプログラムを書いてください。フォーマット要件:
- 1行に1つの被乗数
- タブ
\tでアライメント forループとフォーマット出力を組み合わせる
package main
import "fmt"
func main() {
// ここにコードを書いてください
// ヒント:2つのネストされたforループを使用
// 外側ループ i を1から9まで
// 内側ループ j を1からiまで
}
演習3(⭐⭐⭐)
シンプルな数当てゲームを実装するプログラムを書いてください:
- ループを使ってユーザーが正解するまで繰り返し推測させる
- 各推測の後にヒントを与える:「大きすぎ」または「小さすぎ」
deferを使ってゲームの開始と終了時刻を記録するswitchを使って推測回数に基づいて異なる評価を与える(1-3:天才、4-6:良い、7+:頑張ろう)
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の関数定義、複数戻り値、可変長パラメータ、匿名関数、クロージャ、その他のコアコンセプトを学びます。



