フロー制御文:for, if, else, switch and defer

The Go Authors

目次

1. for

基本的に、 for ループはセミコロン ; で3つの部分に分かれています:

初期化ステートメントは、短い変数宣言によく利用します。その変数は for ステートメントのスコープ内でのみ有効です。

ループは、条件式の評価が false となった場合にイテレーションを停止します。

Note: 他の言語、C言語 や Java、JavaScriptの for ループとは異なり、 for ステートメントの3つの部分を括る括弧 ( ) はありません。なお、中括弧 { } は必要です。

package main

import "fmt"

func main() {
    sum := 0
    for i := 0; i < 10; i++ {
        sum += i
    }
    fmt.Println(sum)
}

2. for(続)

初期化と後処理ステートメントの記述は任意です。

package main

import "fmt"

func main() {
    sum := 1
    for ; sum < 1000; {
        sum += sum
    }
    fmt.Println(sum)
}

3. forはGoでの“while”

セミコロン(;)を省略することもできます。つまり、C言語などにある while は、Goでは for だけを使います。

package main

import "fmt"

func main() {
    sum := 1
    for sum < 1000 {
        sum += sum
    }
    fmt.Println(sum)
}

4. 無限ループ

ループ条件を省略すれば、無限ループ( infinite loop )になりますので、無限ループをコンパクトに表現できます。

package main

func main() {
    for {
    }
}

5. If

Go言語の if ステートメントは、先ほどの for ループと同様に、括弧 ( ) は不要で、中括弧 { } は必要です。

(もうおなじみですね!)

package main

import (
    "fmt"
    "math"
)

func sqrt(x float64) string {
    if x < 0 {
        return sqrt(-x) + "i"
    }
    return fmt.Sprint(math.Sqrt(x))
}

func main() {
    fmt.Println(sqrt(2), sqrt(-4))
}

6. 簡単なステートメント付きのif

if ステートメントは、 for のように、条件の前に、評価するための簡単なステートメントを書くことができます。

ここで宣言された変数は、 if のスコープ内だけで有効です。

(ためしに最後の return 文で、 v を使ってみてください。 スコープの外なので使えないですよね?)

package main

import (
    "fmt"
    "math"
)

func pow(x, n, lim float64) float64 {
    if v := math.Pow(x, n); v < lim {
        return v
    }
    return lim
}

func main() {
    fmt.Println(
        pow(3, 2, 10),
        pow(3, 3, 20),
    )
}

7. ifとelse

なお、 if ステートメントで宣言された変数は、 else ブロック内でも使うことができます。

(コードの補足: mainfmt.Println は、2つの pow が先に実行されてから実行されます)

package main

import (
    "fmt"
    "math"
)

func pow(x, n, lim float64) float64 {
    if v := math.Pow(x, n); v < lim {
        return v
    } else {
        fmt.Printf("%g >= %g\n", v, lim)
    }
    // can't use v here, though
    return lim
}

func main() {
    fmt.Println(
        pow(3, 2, 10),
        pow(3, 3, 20),
    )
}

8. 練習:ループと関数

関数とループを使った簡単な練習として、平方根の計算を実装してみましょう: 数値 x が与えられたときに z² が最も x に近い数値 z を求めたいと思います。

コンピュータは通常ループを使って x の平方根を計算します。 いくつかの z を推測することから始めて、z² がどれほど x に近づいているかに応じて z を調整できます。

z -= (z*z - x) / (2*z)

実際の平方根に近い答えになるまでこの調整を繰り返すことによって、推測はより良いものなります。

これを func Sqrt に実装してください。 何が入力されても z の適切な開始推測値は 1 です。 まず計算を 10 回繰り返してそれぞれの z を表示します。 x (1, 2, 3, ...) のさまざまな値に対する答えがどれほど近似し、 推測が速くなるかを確認してください。

Hint: 浮動小数点の変数を初期化して宣言するには、型でキャストするか、浮動小数点を使ってみてください:

z := 1.0
z := float64(1)

次に値が変化しなくなった (もしくはごくわずかな変化しかしなくなった) 場合にループを停止させます。 それが 10 回よりも多いか少ないかを確認してください。 x や x/2 のように他の初期推測の値を z に与えてみてください。 あなたの関数の結果は標準ライブラリの math.Sqrt にどれくらい近づきましたか?

(メモ: アルゴリズムの詳細について興味がある人のために説明すると、 上の z² − xという式は、z² が最終的な期待値 x からどのくらい離れているかを表しています。 除算の 2z は z² の導関数で、z² の変化の大きさに応じて z の調整値を変化させます。 この一般的なアプローチはニュートン法と呼ばれています。 多くの関数で有効に働きますがとくに平方根では殊更有効です。)

package main

import (
    "fmt"
)

func Sqrt(x float64) float64 {
}

func main() {
    fmt.Println(Sqrt(2))
}

9. switch

switch ステートメントは if - else ステートメントのシーケンスを短く書く方法です。

Go の switch は C や C++、Java、JavaScript、PHP の switch と似ていますが、 Go では選択された case だけを実行してそれに続く全ての case は実行されません。 これらの言語の各 case の最後に必要な break ステートメントが Go では自動的に提供されます。 もう一つの重要な違いは Go の switch の case は定数である必要はなく、 関係する値は整数である必要はないということです。

package main

import (
    "fmt"
    "runtime"
)

func main() {
    fmt.Print("Go runs on ")
    switch os := runtime.GOOS; os {
    case "darwin":
        fmt.Println("OS X.")
    case "linux":
        fmt.Println("Linux.")
    default:
        // freebsd, openbsd,
        // plan9, windows...
        fmt.Printf("%s.", os)
    }
}

10. switchの評価順

switch caseは、上から下へcaseを評価します。 caseの条件が一致すれば、そこで停止(自動的にbreak)します。

(例えば、

switch i {
case 0:
case f():
}

では、 i==0 であれば、 case 0 でbreakされるため f は呼び出されません)

package main

import (
    "fmt"
    "time"
)

func main() {
    fmt.Println("When's Saturday?")
    today := time.Now().Weekday()
    switch time.Saturday {
    case today + 0:
        fmt.Println("Today.")
    case today + 1:
        fmt.Println("Tomorrow.")
    case today + 2:
        fmt.Println("In two days.")
    default:
        fmt.Println("Too far away.")
    }
}

11. 条件のないswitch

条件のないswitchは、 switch true と書くことと同じです。

このswitchの構造は、長くなりがちな "if-then-else" のつながりをシンプルに表現できます。

package main

import (
    "fmt"
    "time"
)

func main() {
    t := time.Now()
    switch {
    case t.Hour() < 12:
        fmt.Println("Good morning!")
    case t.Hour() < 17:
        fmt.Println("Good afternoon.")
    default:
        fmt.Println("Good evening.")
    }
}

12. defer

defer ステートメントは、 defer へ渡した関数の実行を、呼び出し元の関数の終わり(returnする)まで遅延させるものです。

defer へ渡した関数の引数は、すぐに評価されますが、その関数自体は呼び出し元の関数がreturnするまで実行されません。

package main

import "fmt"

func main() {
    defer fmt.Println("world")

    fmt.Println("hello")
}

13. deferのスタック

defer へ渡した関数が複数ある場合、その呼び出しはスタック( stack )されます。 呼び出し元の関数がreturnするとき、 defer へ渡した関数は LIFO(last-in-first-out) の順番で実行されます。

defer ステートメントについてさらに学ぶには、 こちら(blog post)を読んでみてください。

package main

import "fmt"

func main() {
    fmt.Println("counting")

    for i := 0; i < 10; i++ {
        defer fmt.Println(i)
    }

    fmt.Println("done")
}

14. Congratulations!

この章はこれで終わりです。

章のリストから学びたいところを見ても良いですし、 > をクリックして次の章へ進みましょう。