制御構造

Goの制御構造はCのものと関連していますが、重要な点で異なります。dowhileループはなく、わずかに一般化されたforのみがあります。switchはより柔軟です。ifswitchforのような初期化ステートメントのオプションを受け入れます。そして、タイプスイッチや多方向通信多重化器であるselectを含む新しい制御構造があります。構文もわずかに異なります:括弧は必要なく、ボディは常に波括弧で区切られる必要があります。

If

Goでは単純なifは次のようになります:

if x > 0 {
    return y
}

必須の波括弧は、複数行に単純なifステートメントを書くことを推奨します。とにかくそうすることは良いスタイルです。特にreturnbreakなどの制御ステートメントを含むボディの場合は特にです。

ifswitchは初期化ステートメントを受け入れるため、ローカル変数を設定するために使用されるのを見ることがよくあります。

if err := file.Chmod(0664); err != nil {
    log.Print(err)
    return err
}

Goライブラリでは、ifステートメントが次のステートメントに流れない場合、つまり、ボディがbreakcontinuegotoreturnで終わる場合、不要なelseが省略されているのを見つけるでしょう。

f, err := os.Open(name)
if err != nil {
    return err
}
codeUsing(f)

これは、コードが一連のエラー条件を防御しなければならない一般的な状況の例です。成功した制御フローがページを下って実行される場合、エラーケースが発生したときにそれらを除去するとコードはよく読めます。エラーケースはreturnステートメントで終わる傾向があるため、結果のコードにはelseステートメントは必要ありません。

f, err := os.Open(name)
if err != nil {
    return err
}
d, err := f.Stat()
if err != nil {
    f.Close()
    return err
}
codeUsing(f, d)

再宣言と再代入

余談:前のセクションの最後の例は、:=短縮宣言形式の動作の詳細を示しています。os.Openを呼び出す宣言は:

f, err := os.Open(name)

このステートメントは、ferrの2つの変数を宣言します。数行後、f.Statへの呼び出しは次のようになります:

d, err := f.Stat()

これはderrを宣言しているように見えます。しかし、errは両方のステートメントに現れることに注意してください。この重複は合法です:errは最初のステートメントで宣言されますが、2番目のステートメントでは再代入されるだけです。これは、f.Statへの呼び出しが上で宣言された既存のerr変数を使用し、それに新しい値を与えるだけであることを意味します。

:=宣言では、変数vは既に宣言されていても現れる可能性があります。ただし、次の場合に限ります:

  • この宣言がvの既存の宣言と同じスコープにある場合(vが外部スコープで既に宣言されている場合、宣言は新しい変数を作成します)
  • 初期化の対応する値がvに代入可能である場合
  • 宣言によって作成される他の変数が少なくとも1つある場合

この珍しい性質は純粋な実用主義であり、長いif-elseチェーンで単一のerr値を使用することなどを簡単にします。よく使用されているのを見るでしょう。

§ここで注目すべき点は、Goでは関数パラメータと戻り値のスコープが、波括弧に囲まれたボディの外側に字句的に現れるにもかかわらず、関数ボディと同じであることです。

For

Go forループはCのものと似ていますが、同じではありません。これはforwhileを統合し、do-whileはありません。3つの形式があり、そのうち1つだけがセミコロンを持ちます。

// C forのような
for init; condition; post { }

// C whileのような
for condition { }

// C for(;;)のような
for { }

短縮宣言により、ループ内でインデックス変数を宣言するのが簡単になります。

sum := 0
for i := 0; i < 10; i++ {
    sum += i
}

配列、スライス、文字列、マップをループしている場合、またはチャネルから読み取っている場合、range節でループを管理できます。

for key, value := range oldMap {
    newMap[key] = value
}

範囲の最初の項目(キーまたはインデックス)のみが必要な場合は、2番目を削除します:

for key := range m {
    if key.expired() {
        delete(m, key)
    }
}

範囲の2番目の項目(値)のみが必要な場合は、ブランク識別子、アンダースコアを使用して最初を破棄します:

sum := 0
for _, value := range array {
    sum += value
}

ブランク識別子は、後のセクションで説明するように、多くの用途があります。

文字列の場合、rangeは、UTF-8を解析することで個々のUnicode コード ポイントを分解し、より多くの作業を実行します。誤ったエンコーディングは1バイトを消費し、置換ルーンU+FFFDを生成します。(関連する組み込み型を持つ名前runeは、単一のUnicodeコードポイントのGo用語です。詳細については、言語仕様を参照してください)ループ:

for pos, char := range "日本\x80語" { // \x80は不正なUTF-8エンコーディング
    fmt.Printf("character %#U starts at byte position %d\n", char, pos)
}

は次を出力します:

character U+65E5 '日' starts at byte position 0
character U+672C '本' starts at byte position 3
character U+FFFD '�' starts at byte position 6
character U+8A9E '語' starts at byte position 7

最後に、Goにはカンマ演算子がなく、++--は式ではなくステートメントです。したがって、forで複数の変数を実行したい場合は、並列代入を使用する必要があります(ただし、これは++--を排除します)。

// aを逆順にする
for i, j := 0, len(a)-1; i < j; i, j = i+1, j-1 {
    a[i], a[j] = a[j], a[i]
}

Switch

GoのswitchはCのswitchよりも一般的です。式は定数である必要はなく、整数である必要もありません。ケースは一致が見つかるまで上から下に評価され、switchに式がない場合はtrueでスイッチします。したがって、if-else-if-elseチェーンをswitchとして書くことが可能で、慣用的です。

func unhex(c byte) byte {
    switch {
    case '0' <= c && c <= '9':
        return c - '0'
    case 'a' <= c && c <= 'f':
        return c - 'a' + 10
    case 'A' <= c && c <= 'F':
        return c - 'A' + 10
    }
    return 0
}

自動フォールスルーはありませんが、ケースはカンマ区切りリストで提示できます。

func shouldEscape(c byte) bool {
    switch c {
    case ' ', '?', '&', '=', '#', '+', '%':
        return true
    }
    return false
}

他のC系言語ほど一般的ではありませんが、breakステートメントを使用してswitchを早期に終了できます。時々、しかし、スイッチではなく、周囲のループを抜ける必要があり、Goでは、ループにラベルを付けて、そのラベルに「break」することで実現できます。この例では、両方の使用法を示しています。

Loop:
	for n := 0; n < len(src); n += size {
		switch {
		case src[n] < sizeOne:
			if validateOnly {
				break
			}
			size = 1
			update(src[n])

		case src[n] < sizeTwo:
			if n+1 >= len(src) {
				err = errShortInput
				break Loop
			}
			if validateOnly {
				break
			}
			size = 2
			update(src[n] + src[n+1]<<shift)
		}
	}

もちろん、continueステートメントもオプションのラベルを受け入れますが、ループにのみ適用されます。

このセクションを終了するために、2つのswitchステートメントを使用するバイト スライスの比較ルーチンを次に示します:

// Compare は、2つのバイト配列を辞書順に比較した整数を返します。
// 結果は、a == b の場合は 0、a < b の場合は -1、a > b の場合は +1 になります
func Compare(a, b []byte) int {
    for i := 0; i < len(a) && i < len(b); i++ {
        switch {
        case a[i] > b[i]:
            return 1
        case a[i] < b[i]:
            return -1
        }
    }
    switch {
    case len(a) > len(b):
        return 1
    case len(a) < len(b):
        return -1
    }
    return 0
}

型スイッチ

スイッチは、インターフェース変数の動的型を発見するためにも使用できます。このような型スイッチは、括弧内のtypeキーワードを使用して型アサーションの構文を使用します。スイッチが式で変数を宣言する場合、変数は各節で対応する型を持ちます。また、このような場合は名前を再利用するのが慣用的で、実際には各ケースで同じ名前でも異なる型の新しい変数を宣言します。

var t interface{}
t = functionOfSomeType()
switch t := t.(type) {
default:
    fmt.Printf("unexpected type %T\n", t)     // %T は t が持つ型を出力
case bool:
    fmt.Printf("boolean %t\n", t)             // t は bool 型
case int:
    fmt.Printf("integer %d\n", t)             // t は int 型
case *bool:
    fmt.Printf("pointer to boolean %t\n", *t) // t は *bool 型
case *int:
    fmt.Printf("pointer to integer %d\n", *t) // t は *int 型
}