Go基礎 #3 制御フロー — if, for, switch

読了 6分

#2 変数、型、定数 で値を扱う道具を見ました。今回は — その値で分岐と繰り返しを作る場面。

if / else if / else #

条件分岐。他の言語とほぼ似ていますが、丸括弧なしが特徴です。

if 基本
score := 85

if score >= 90 {
	fmt.Println("A")
} else if score >= 80 {
	fmt.Println("B")
} else {
	fmt.Println("C")
}

if (条件) ではなく if 条件 です。C/Javaスタイルから来ると最初は違和感がありますが、すぐ慣れます。

短い宣言 (short statement) #

条件の中で変数を一つ宣言してすぐ使うことができます。Goで非常によく出会うパターン。

if + 短い宣言
if v, ok := lookup(key); ok {
	fmt.Println("見つかった:", v)
} else {
	fmt.Println("なし")
}

v, ok := lookup(key) がifの中で起こり、vok はif/elseブロック内でのみ見えます。ブロックを抜けると消えます。

このパターンがGoコードのいたるところに登場します。エラー検査(#4)の標準的な形でもあります。

条件は bool のみ #

bool のみ受ける
x := 5

// if x { ... }     ✗ x は int
if x > 0 { ... }     // OK

// if v := lookup(); v { ... }   ✗ v が bool でないとダメ

JavaScriptのtruthy/falsyのような自動変換がありません。明示的な条件のみ受け付けます。

for — 唯一の繰り返し文 #

Goには while がありませんfor 一つですべての繰り返しを表現します。

1) 古典的なfor — 三部分 #

古典的なfor
for i := 0; i < 5; i++ {
	fmt.Println(i)
}
// 0, 1, 2, 3, 4

C/Javaと同じ形。初期化 ; 条件 ; 各反復後。

2) whileのように — 条件のみ #

whileのように
n := 0
for n < 5 {
	fmt.Println(n)
	n++
}

三部分のうち条件だけがある形。他の言語のwhileと同じ意味です。

3) 無限ループ — 条件もなし #

無限 for
for {
	// ...
	if shouldStop() {
		break
	}
}

for { } が無限ループ。while (true) と同じ意味です。

4) for range — コレクション巡回 #

for range 配列/スライス
fruits := []string{"りんご", "バナナ", "ぶどう"}

for i, fruit := range fruits {
	fmt.Println(i, fruit)
}
// 0 りんご
// 1 バナナ
// 2 ぶどう
for range マップ
ages := map[string]int{
	"カーティス": 30,
	"アリス": 25,
}

for name, age := range ages {
	fmt.Println(name, age)
}
for range 文字列 (rune単位)
for i, r := range "あいう" {
	fmt.Printf("%d: %c\n", i, r)
}
// 0: あ
// 3: い  (UTF-8 バイトインデックス)
// 6: う

for range は非常に強力です。インデックス/キーだけ必要なら2番目の変数を省略し、値だけ必要なら最初を _ で無視します。

一部だけ受ける
for i := range fruits {        // インデックスのみ
	fmt.Println(i)
}

for _, fruit := range fruits {  // 値のみ
	fmt.Println(fruit)
}

breakcontinue #

ループの流れを制御。

break / continue
for i := 0; i < 10; i++ {
	if i == 5 {
		break       // ループから抜ける
	}
	if i%2 == 0 {
		continue    // 次の反復へ
	}
	fmt.Println(i)
}
// 1, 3

ラベルで入れ子ループ制御 #

何重もの入れ子ループから一度に抜けたいとき。

ラベル
outer:
for i := 0; i < 5; i++ {
	for j := 0; j < 5; j++ {
		if i*j > 6 {
			break outer    // outerラベルまで一気にbreak
		}
	}
}

頻繁には使いませんが、深い入れ子では綺麗です。

switch — Goの最大の違い #

Goのswitchは自動的にfallthroughしません。C/Javaと真逆です。

switch — break 自動
day := "mon"

switch day {
case "mon", "tue", "wed", "thu", "fri":
	fmt.Println("平日")
case "sat", "sun":
	fmt.Println("週末")
default:
	fmt.Println("?")
}

各caseの末尾に自動break。古いCのように break を書く必要がありません。複数の値を一つのcaseにカンマでまとめることもできます。

強制fallthrough #

本当に次のcaseへ流したければ fallthrough キーワード。

fallthrough — 明示的
n := 1

switch n {
case 1:
	fmt.Println("one")
	fallthrough
case 2:
	fmt.Println("two")
}
// one
// two

ほとんど使いません。意図が明確なときだけ。

条件式switch — if/elseの短い形 #

式のないswitchはif/elseの連鎖を短くします。

条件 switch
score := 85

switch {
case score >= 90:
	fmt.Println("A")
case score >= 80:
	fmt.Println("B")
case score >= 70:
	fmt.Println("C")
default:
	fmt.Println("F")
}

各caseがbool式。長くなるif/else ifより見やすいことが多いです。

switch + 短い宣言 #

ifと同様にswitchも短い宣言を許します。

switch 短い宣言
switch v := getValue(); {
case v > 100:
	fmt.Println("大きい")
case v > 0:
	fmt.Println("正の数")
default:
	fmt.Println("0 以下")
}

型switch — interface分岐 #

中級 #1 インターフェース で本格的に扱いますが、switchが型検査にも使われます。

型 switch (プレビュー)
func describe(i interface{}) {
	switch v := i.(type) {
	case int:
		fmt.Println("int:", v)
	case string:
		fmt.Println("string:", v)
	case bool:
		fmt.Println("bool:", v)
	default:
		fmt.Println("unknown")
	}
}

i.(type) という特別な構文。インターフェースがどの具体型を持っているかに応じて分岐します。

goto — ほとんど使わない #

Goにも goto はありますが、ほとんど使いません。本当に特殊な場面(入れ子からの脱出、後始末コード)でだけ出会います。

短いifの罠 — 波括弧必須 #

JavaScriptと違い、Goは波括弧が常に必須です。

波括弧必須
// if condition doSomething()       ✗ コンパイルエラー
if condition {
	doSomething()
}

この設計のおかげで、JavaScriptの「インデントだけの偽if」のような罠がそもそもありません。

よく使うパターン #

1) エラー検査 #

if err != nil パターンがGoコードで最もよく見る形の一つです。

エラー検査
file, err := os.Open("data.txt")
if err != nil {
	return err
}
defer file.Close()
// ... 正常処理

#4 で詳しく扱います。

2) キーの存在確認 #

map のキー確認
ages := map[string]int{"カーティス": 30}

if age, ok := ages["カーティス"]; ok {
	fmt.Println(age)
}

map[キー] は2つの値を返すことができます — 値と存在の有無 ok。この短い宣言パターンが標準です。

3) early return #

深いif/elseより早めにreturnするパターン。

early return
func process(s string) error {
	if s == "" {
		return fmt.Errorf("空文字列")
	}
	if len(s) > 100 {
		return fmt.Errorf("文字列が長すぎる")
	}

	// メインロジック — インデント1段だけ
	return nil
}

JavaScript #4 関数 で見たパターンと同じです。Goでも推奨されます。

まとめ #

今回の記事で整理した内容:

  • if 条件 { } — 丸括弧なし、波括弧必須
  • 短い宣言 — if v, ok := ...; ok { } がよく登場
  • 条件はboolのみ — truthy/falsy自動変換なし
  • for が唯一の繰り返し文 — while/do-whileなし
  • 4つの形 — 古典、条件のみ、無限、range
  • for range でスライス/マップ/文字列を巡回 (文字列はrune)
  • switch は自動break — fallthroughは明示
  • 条件式switchでif/else連鎖を整理
  • i.(type) で型switch (プレビュー)

次の記事(#4 関数、多値返却、error型)ではGoの関数 — 定義方法、多値返却、そして最も重要なエラー処理パターンを扱います。

X