Go/条件分岐と繰り返し
制御構造
[編集]Go言語では、プログラムのフローを制御するためにいくつかの制御構造が用意されています。これらの構造は、プログラムのロジックを実行する際に重要な役割を果たします。本章では、Go言語における主な制御構造について説明します。
条件分岐
[編集]Goでは、条件分岐をif文などで行いますが、C言語とは異なります。C言語にもif、else、switchなどがありますが、スコープルールなどに違いがあります。
基本的なif文
[編集]package main import "fmt" func main() { number := 10 if number > 5 { fmt.Println("numberは5より大きい") } }
このコードでは、numberが5より大きいため、"numberは5より大きい"が出力されます。
else文
[編集]package main import "fmt" func main() { number := 3 if number > 5 { fmt.Println("numberは5より大きい") } else { fmt.Println("numberは5以下") } }
このコードでは、numberが3なので、"numberは5以下"が出力されます。
else if文
[編集]package main import "fmt" func main() { number := 7 if number > 10 { fmt.Println("numberは10より大きい") } else if number > 5 { fmt.Println("numberは5より大きく、10以下") } else { fmt.Println("numberは5以下") } }
このコードでは、numberが7なので、"numberは5より大きく、10以下"が出力されます。
- コード例
package main import ( "fmt" "math" ) func main() { if num := math.NaN(); num < 0.0 { fmt.Println("負") } else if num > 0.0 { fmt.Println("正") } else if num == 0.0 { fmt.Println("零") } else { fmt.Println("NaN") } }
- 実行結果
NaN
Goでは、if文の条件式の前に「単純な文」を書くことが可能です。上記の例では、変数numがmath.NaN()で初期化されており、そのスコープはif文の中に限定されます。
switch文
[編集]switch文を使うと、複雑なif文を簡潔に表現できます。
- コード例
package main import ( "fmt" "math" ) func main() { switch num := math.NaN(); { case num < 0.0: fmt.Println("負") case num > 0.0: fmt.Println("正") case num == 0.0: fmt.Println("零") default: fmt.Println("NaN") } }
- 実行結果
NaN
Goのswitch文では、if文と同様に「単純な文」を書くことができます。C言語やJavaのようにbreak文は不要で、fallthrough文を使わない限り、自動的にcase節の終わりで処理が終了します。
型によるswitch
[編集]package main import "fmt" func main() { var x interface{} = "Hello" switch v := x.(type) { case int: fmt.Println("xはint型です") case string: fmt.Println("xはstring型です:", v) default: fmt.Println("xはその他の型です") } }
このコードでは、xがstring型であるため、"xはstring型です: Hello"が出力されます。
select文
[編集]select文は、通信チャンネルを使った並行処理のための構文です。複数のチャンネルからの入力を待機し、その中で最初に準備ができたものを処理します。
- Select文を使ったタイムアウト
package main import ( "fmt" "time" ) func main() { done := make(chan bool) go func(s int) { fmt.Printf("#%d..do\n", s) time.Sleep(time.Duration(s) * time.Second) fmt.Printf("#%d..done\n", s) done <- true }(2) select { case <-done: fmt.Println("Done!") case <-time.After(1 * time.Second): fmt.Println("Timeout!") } }
- 実行結果
#2..do Timeout!
反復:for文
[編集]Goでは、for文が唯一の繰り返し構文です。Goにはdoやwhileはなく、for文のみで繰り返し処理を行います。
Cスタイルの for 文 (初期化、条件式、後処理)
[編集]Goでは、C言語のような構文で for 文を使用できます。この形式では、初期化、条件式、後処理を記述することができます。
package main import "fmt" func main() { for i := 0; i < 5; i++ { fmt.Println(i) } }
- 実行結果
0 1 2 3 4
この例では、i を0から始めて5未満の間、1ずつ増加させながらループを繰り返します。i++ は後処理にあたります。
条件式だけの for 文
[編集]for 文の初期化と後処理を省略し、条件式のみを使うこともできます。C言語のwhile 文に相当します。
package main import "fmt" func main() { i := 0 for i < 5 { fmt.Println(i) i++ } }
- 実行結果
0 1 2 3 4
ここでは、i が5未満である限りループを繰り返します。i++ はループ内で更新されます。
無限ループ
[編集]条件式を省略すると、無限ループを作成することができます。break を使ってループを終了させることができます。
package main import "fmt" func main() { for { fmt.Println("無限ループ") break // ループを終了 } fmt.Println("脱出") }
- 実行結果
無限ループ 脱出
このように、Goの for 文は非常に柔軟で、様々なループの形に対応しています。
range を使ったループ
[編集]Goでは、range を使うことで、配列やスライス、マップ、チャンネルなどを簡単にループ処理できます。
- スライス
package main import "fmt" func main() { s := []int{2, 3, 5, 7, 11} for index, value := range s { fmt.Println(index, value) } }
- 実行結果
0 2 1 3 2 5 3 7 4 11
- マップ
package main import "fmt" func main() { m := map[string]int{"a": 1, "b": 2, "c": 3} for key, value := range m { fmt.Println(key, value) } }
- 実行結果
a 1 b 2 c 3
- チェンネル
package main import "fmt" func main() { ch := make(chan int) go func(ch chan int) { for i := 0; i < 5; i++ { ch <- 2*i + 1 } close(ch) }(ch) for num := range ch { fmt.Println(num) } fmt.Println("done.") }
- 実行結果
1 3 5 7 9 done.
rangeと整数を使ったループ
[編集]Go 1.22 では、for 文の range 構文が拡張され、整数を使ってイテレーションができるようになりました。これにより、配列やスライス、マップ、チェンネルだけでなく、整数回のループ処理を行うことが可能になりました。
- 例
package main import "fmt" func main() { for i := range 10 { fmt.Println(i) } }
- 実行結果
0 1 2 3 4 5 6 7 8 9
rangeと関数を使ったループ
[編集]Go 1.23 では、for 文の range 構文が拡張され、関数を使ってイテレーションができるようになりました。これにより、配列やスライス、マップ、チェンネルだけでなく、関数の結果を使ってループ処理を行うことが可能になりました。
具体的には、関数を引数として受け取り、その yield を使って値を返し、for 文がその値を反復処理します。この仕組みによって、再帰的なデータ構造や動的に生成されるデータを簡単に扱えるようになりました。
- 例
package main import "fmt" func main() { factorial := func(yield func(x int) bool) { for result, i := 1, 1; yield(result); i++ { result *= i } } // print the factorial numbers below 1000: for x := range factorial { if x >= 1000 { break } fmt.Printf("%d ", x) } }
- 実行結果
1 1 2 6 24 120 720
このように、関数内で yield を使って値を返し、for 文でその値を反復処理することができます。
iterパッケージを使った再実装
[編集]package main import ( "fmt" "iter" ) func factorial() iter.Seq[int] { return func(yield func(int) bool) { for result, i := 1, 1; yield(result); i++ { result *= i } } } func main() { // print the factorial numbers below 1000: for x := range factorial() { if x >= 1000 { break } fmt.Printf("%d ", x) } }
iter.Pull() の利用
[編集]package main import ( "fmt" "iter" ) func factorial(n int) iter.Seq[int] { return func(yield func(int) bool) { for result, i := 1, 1; yield(result); i++ { result *= i if result > n { break } } } } func main() { // print the factorial numbers below 1000: next, stop := iter.Pull(factorial(1000)) defer stop() for { if x, ok := next(); !ok { break } else { fmt.Printf("%d ", x) } } }
このGoコードでは、iterパッケージを使って階乗を生成するイテレータを作成しています。以下にコードの解説を行います。
- パッケージとインポート
mainパッケージと、標準ライブラリのfmt、そしてiterパッケージをインポートしています。factorial関数factorial関数は、引数nを受け取り、iter.Seq[int](整数型のイテレータ)を返します。この関数の内部で、yield関数を使用して階乗の数を生成しています。yield(func(int) bool)は、呼び出し側がイテレータの次の値を取得するために使うコールバック関数です。resultは階乗の結果を保持し、iは計算に使用される変数です。resultをiで累積的に掛け算していきます。if result > nでは、計算した結果がnを超えた場合にイテレータを終了させます。
main関数main関数では、factorial(1000)を使って、1000未満の階乗数をイテレータとして取得し、その結果を順番に表示しています。iter.Pull(factorial(1000))は、factorial(1000)から生成されるイテレータのnext関数とstop関数を取得します。next()は次の階乗値を取得する関数、stop()はイテレータの処理を終了させるための関数です。defer stop()によって、main関数の終了時に必ずstop()が呼び出され、リソースが解放されます。next()が返すのは、階乗の値と、イテレータがまだ有効かどうかを示すブール値okです。okがfalseであればイテレータが終了したことを意味し、breakでループを終了します。- 各階乗数は
fmt.Printfで出力されます。
このプログラムは、階乗のイテレータを定義して、指定された制限(1000)以内の階乗値を順次出力するものです。イテレータを使うことで、数値の生成と処理の流れを遅延させ、効率的に階乗の数を扱うことができます。
イテレーションの連鎖
[編集]package main import ( "fmt" "iter" ) func naturals() iter.Seq[int] { return func(yield func(int) bool) { for i := 0; yield(i); i++ { } } } func evens(seq iter.Seq[int]) iter.Seq[int] { return func(yield func(int) bool) { seq(func(v int) bool { if v%2 == 0 { return yield(v) } return true }) } } func take(n int, seq iter.Seq[int]) iter.Seq[int] { return func(yield func(int) bool) { i := 0 seq(func(v int) bool { i += 1 if i <= n { return yield(v) } return false }) } } func main() { for x := range take(10, evens(naturals())) { fmt.Println(x) } }
まとめ
[編集]Go言語の制御構造は、プログラムのフローを柔軟に制御するための強力なツールです。条件分岐やループ、並行処理を適切に使い分けることで、効率的で読みやすいコードを書くことができます。