if
ifは条件式がtrueのとき、続くブロックを実行します。else ifやelseで条件分岐を継続できます。
if x < 0 {
return -x
} else if x == 0 {
return 0
}
return x条件式の前にセミコロンで区切った初期化文を記述できます。初期化文で宣言した変数のスコープは、続くelse ifやelseを含むif文全体です。ブロックの外からは参照できません。
if n, err := strconv.Atoi(s); err != nil {
return err
} else {
fmt.Println(n) // nはここまで有効
}
// fmt.Println(n) // コンパイルエラーfor
繰り返し処理はfor文で記述します。条件式のみ、初期化と後処理を含む3文形式、range節の3通りの書き方があります。
条件式
条件式を指定すると、評価結果がtrueの間ブロックを繰り返します。条件式を省略した場合は無限ループとなり、breakやreturnで終了します。
for n < 100 {
n *= 2
}for {
if done {
break
}
}初期化・条件・後処理
初期化文、条件式、後処理をセミコロンで区切って並べます。
for i := 0; i < 10; i++ {
fmt.Println(i)
}Go言語1.22以降、初期化文で宣言したループ変数は反復ごとに新しく作られます。ループ内で定義した関数から参照しても変数は共有されず、各反復の値がそのまま保持されます。
var prints []func()
for i := 0; i < 3; i++ {
prints = append(prints, func() { fmt.Println(i) })
}
for _, p := range prints {
p() // 0 1 2
}range
配列、配列へのポインタ、スライス、文字列、マップ、チャネル、整数、イテレータ関数を走査します。rangeで宣言したループ変数は、Go言語1.22以降は反復ごとに新しく作られます。
配列、配列へのポインタ、スライス、文字列では添字と値、マップではキーと値を受け取れます。マップの走査順序は決まっていません。チャネルでは受信した値の1つのみを受け取ります。
// スライスは添字と値を受け取る
s := []string{"a", "b"}
for i, v := range s {
fmt.Println(i, v)
}
// マップはキーと値を受け取る。走査順序は決まっていない
m := map[string]int{"x": 1, "y": 2}
for k, v := range m {
fmt.Println(k, v)
}
// チャネルは受信した値のみを受け取る
ch := make(chan int, 2)
ch <- 10
ch <- 20
close(ch)
for v := range ch {
fmt.Println(v) // 10 20
}Go言語1.22以降では、指定した回数だけ繰り返す用途として整数nも走査できます。for i := range nと記述すると0からn-1まで昇順に取り出します。nが0以下の場合は反復しません。
for i := range 3 {
fmt.Println(i) // 0 1 2
}Go言語1.23以降では、関数をrangeで走査できます。イテレータ関数の中でyieldを呼び出すたびに、走査する側のforループ本体が1回実行されます。yieldは通常trueを返しますが、走査側のループがbreakなどで終了するとfalseを返します。この戻り値を判定することで、呼び出し側でループが終了したことを検知し、不要な処理が続くのを防ぐことができます。イテレータ関数がリターンすると走査全体が終了します。
// 呼び出し側に値を渡すイテレータ関数
seq := func(yield func(int) bool) {
for n := 0; n < 3; n++ {
if !yield(n) {
return // 呼び出し側のループ終了を検知し、無駄な処理の継続を防ぐ
}
}
}
// イテレータ関数をrangeで走査
for n := range seq {
fmt.Println(n) // 0 1 2
}イテレータ関数のシグネチャには、値を渡さない形式、1つの値を渡す形式、2つの値を渡す形式の3パターンがあります。
// 値なし
noVal := func(yield func() bool) {
if !yield() {
return
}
}
for range noVal {
// 反復のみ行う
}
// 1値
singleVal := func(yield func(string) bool) {
if !yield("hello") {
return
}
}
for v := range singleVal {
fmt.Println(v) // hello
}
// 2値
pairVal := func(yield func(string, int) bool) {
if !yield("a", 1) {
return
}
}
for k, v := range pairVal {
fmt.Println(k, v) // a 1
}breakとcontinue
breakは最も内側のfor、switch、selectを終了し、continueは最も内側のforの次の反復へ進みます。入れ子のループで外側を終了したり外側の反復を進めたりするには、対象のfor文にラベルを付けてbreak ラベル名やcontinue ラベル名と指定します。
Outer:
for i := 0; i < 3; i++ {
for j := 0; j < 3; j++ {
if j == 1 {
continue // 内側ループの次の反復へ進む
}
if j == 2 {
break // 内側ループだけを終了して外側の次のiへ進む
}
if i == 1 {
break Outer // 外側ループごと終了する
}
fmt.Println(i, j)
}
}
// 0 0 を出力して終了switch
switchは式の値に一致したcaseの本体を実行します。一致するcaseがなくdefault節があるときは、その処理を実行します。default節を省略し、どのcaseにも当てはまらないときは何もしません。1つのcaseにカンマ区切りで複数の値を並べると、いずれかに一致したときに実行されます。
switch day {
case "Mon":
fmt.Println("月曜")
case "Tue", "Wed":
fmt.Println("火〜水")
default:
fmt.Println("その他")
}各節の処理が終わると自動的にswitch文を抜けます。後続の節へ処理を継続したい場合のみ、節の末尾にfallthroughを記述します。fallthroughは次のcaseの条件判定を行わずに直後の本体を実行します。最後の節には記述できません。
switch rank {
case 1:
fmt.Println("金")
fallthrough
case 2:
fmt.Println("銀")
case 3:
fmt.Println("銅")
}
// rankが1のとき: 金と銀を出力
// rankが2のとき: 銀を出力
// rankが3のとき: 銅を出力判定式を省略したswitchは、各caseの条件式を上から順に評価し、最初にtrueとなった節を実行します。
switch {
case x < 0:
fmt.Println("負")
case x == 0:
fmt.Println("ゼロ")
default:
fmt.Println("正")
}式の前にセミコロンで区切った初期化文を記述できます。宣言した変数のスコープはswitch文全体です。
switch n, err := strconv.Atoi(input); {
case err != nil:
fmt.Println("invalid")
case n == 0:
fmt.Println("zero")
default:
fmt.Println(n) // nはswitch全体で有効
}
// fmt.Println(n) // コンパイルエラー型スイッチ
インターフェース値の動的型で分岐します。switch v := x.(type)で導入した変数vは、節ごとに暗黙のブロック内で別々に宣言されます。単一の型を指定した節ではvはその型として扱われ、複数の型を並べた節やdefault節では元の値xと同じ型になります。型スイッチではfallthroughを使用できません。
func describe(x any) {
switch v := x.(type) {
case int:
fmt.Println("int", v) // vはint型
case string:
fmt.Println("string", v) // vはstring型
default:
fmt.Println("other", v) // vは元のxと同じany型
}
}