制御構造

編集

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はその他の型です")
    }
}

このコードでは、xstring型であるため、"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にはdowhileはなく、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は計算に使用される変数です。resultiで累積的に掛け算していきます。
  • 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です。okfalseであればイテレータが終了したことを意味し、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言語の制御構造は、プログラムのフローを柔軟に制御するための強力なツールです。条件分岐やループ、並行処理を適切に使い分けることで、効率的で読みやすいコードを書くことができます。

脚註

編集