実践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)
}