制御構造
Goの制御構造はCのものと関連していますが、重要な点で異なります。do
やwhile
ループはなく、わずかに一般化されたfor
のみがあります。switch
はより柔軟です。if
とswitch
はfor
のような初期化ステートメントのオプションを受け入れます。そして、タイプスイッチや多方向通信多重化器であるselect
を含む新しい制御構造があります。構文もわずかに異なります:括弧は必要なく、ボディは常に波括弧で区切られる必要があります。
If
Goでは単純なif
は次のようになります:
if x > 0 {
return y
}
必須の波括弧は、複数行に単純なif
ステートメントを書くことを推奨します。とにかくそうすることは良いスタイルです。特にreturn
やbreak
などの制御ステートメントを含むボディの場合は特にです。
if
とswitch
は初期化ステートメントを受け入れるため、ローカル変数を設定するために使用されるのを見ることがよくあります。
if err := file.Chmod(0664); err != nil {
log.Print(err)
return err
}
Goライブラリでは、if
ステートメントが次のステートメントに流れない場合、つまり、ボディがbreak
、continue
、goto
、return
で終わる場合、不要な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)
このステートメントは、f
とerr
の2つの変数を宣言します。数行後、f.Stat
への呼び出しは次のようになります:
d, err := f.Stat()
これはd
とerr
を宣言しているように見えます。しかし、err
は両方のステートメントに現れることに注意してください。この重複は合法です:err
は最初のステートメントで宣言されますが、2番目のステートメントでは再代入されるだけです。これは、f.Stat
への呼び出しが上で宣言された既存のerr
変数を使用し、それに新しい値を与えるだけであることを意味します。
:=
宣言では、変数v
は既に宣言されていても現れる可能性があります。ただし、次の場合に限ります:
- この宣言が
v
の既存の宣言と同じスコープにある場合(v
が外部スコープで既に宣言されている場合、宣言は新しい変数を作成します) - 初期化の対応する値が
v
に代入可能である場合 - 宣言によって作成される他の変数が少なくとも1つある場合
この珍しい性質は純粋な実用主義であり、長いif-else
チェーンで単一のerr
値を使用することなどを簡単にします。よく使用されているのを見るでしょう。
§ここで注目すべき点は、Goでは関数パラメータと戻り値のスコープが、波括弧に囲まれたボディの外側に字句的に現れるにもかかわらず、関数ボディと同じであることです。
For
Go for
ループはCのものと似ていますが、同じではありません。これはfor
とwhile
を統合し、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 型
}