実践Go言語(Effective Go)の翻訳、5回目です。
前回までの訳は実践Go言語[日本語訳]にまとめてあります。
制御構造
Go言語の制御構造は、C言語の制御構造と似通っていますが、大きく異なる点があります。ループにはdo
やwhile
はなく、若干改良されたfor
ループだけです。switch
はより柔軟になっています。if
とswitch
はオプションとしてfor
のように初期化ステートメントを受け入れます。また、新しい制御構造として、型switch
、および多重通信を取り扱えるselect
があります。文法も若干異なっており、丸括弧()は不要ですが、本体部は波括弧で区切られていなければなりません。
if
下はGo言語における単純なif
ステートメントです。
if x > 0 { return y }
波括弧{}を必須にしたことにより、複数行に渡るif
ステートメントの記述が見やすくなりました。これは特に、return
やbreak
のような制御ステートメントを含むときには優れた書き方です。
if
とswitch
には初期化ステートメントを記述できるため、そこでローカル変数の準備を行うのが一般的です。
if err := file.Chmod(0664); err != nil { log.Stderr(err) return err }
Go言語のライブラリ内で良く見られる書き方ですが、if
ステートメントから次のステートメントへ制御が移らないとき(すなわち、break
、continue
、goto
、return
のいずれかでif
の本体から抜けるとき)は、不要なelse
は省略します。
f, err := os.Open(name, os.O_RDONLY, 0) if err != nil { return err } codeUsing(f)
下の例は、一連のエラー判定を必要とするよく出会う状況です。処理が成功と判断されたときは、エラー処理はスキップし、処理が下方へと流れていくので読みやすいコードとなっています。エラーのときはreturn
ステートメントで抜けてしまうため、else
ステートメントは必要ありません。
f, err := os.Open(name, os.O_RDONLY, 0) if err != nil { return err } d, err := f.Stat() if err != nil { return err } codeUsing(f, d)
for
Go言語のfor
ループは、C言語のfor
と似てはいますが、同じではありません。Go言語のfor
ループは、C言語のfor
とwhile
ループを兼ねていますが、do-while
ループに相当するものはありません。for
ループには3つの形式がありますが、セミコロンを使うのはそのうちひとつだけです。
// Cのforに相当する形式 for init; condition; post { } // Cのwhileに相当する形式 for condition { } // Cのfor(;;)に相当する形式 for { }
省略形式による変数の宣言(:=
)を使うと、インデックス変数の宣言が容易です。
sum := 0 for i := 0; i < 10; i++ { sum += i }
配列、スライス、文字列、マップの内容、もしくはチャネルから読み込んだデータをループさせるときは、range
節でループを制御することができます。
var m map[string]int sum := 0 for _, value := range m { // キーは使われません sum += value }
文字列を扱うときのrange
はより高機能で、UTF-8エンコードでユニコードの各文字を取り出します。このとき不正なエンコーディングあると、1バイトスキップした上で置換ルーン(Unicode replacement character U+FFFD)として扱います。下はループの例です。
for pos, char := range "日本語" { fmt.Printf("character %c starts at byte position %d\n", char, pos) }
次が出力されます。
character 日 starts at byte position 0 character 本 starts at byte position 3 character 語 starts at byte position 6
最後になりますが、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
の式は、定数や整数である必要はありません。一致するものが見つかるまで、case
を上から下まで評価していきます。switch
が式を伴わないときは、式の値がtrue
となるcase
にマッチします。これを利用してswitch
を使ってif
–else
–if
–else
チェーンを書くことができます。これは慣用的な書き方です。
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 }
case
から直下のcase
へと処理が自動的に移ることはありませんが、case
にはカンマで区切ったリストを指定できます。
func shouldEscape(c byte) bool { switch c { case ' ', '?', '&', '=', '#', '+', '%': return true } return false }
下は、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 }
switch
は、インタフェース変数の動的な型を見つけるために用いることもできます。その型switch
には、型アサーションの構文を使って丸括弧()の中にキーワード"type"
と書きます。switch
の式で変数を宣言したとき、その変数は各case
節において適切な型となります。
switch t := interfaceValue.(type) { default: fmt.Printf("unexpected type %T", t) // %T は型を出力する case bool: fmt.Printf("boolean %t\n", t) case int: fmt.Printf("integer %d\n", t) case *bool: fmt.Printf("pointer to boolean %t\n", *t) case *int: fmt.Printf("pointer to integer %d\n", *t) }
Comments