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
パッケージ(外部パッケージまたは自作のパッケージとして想定)をインポートしています。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言語の制御構造は、プログラムのフローを柔軟に制御するための強力なツールです。条件分岐やループ、並行処理を適切に使い分けることで、効率的で読みやすいコードを書くことができます。