メインコンテンツまでスキップ

Go言語の基礎となる文法や操作を、20のコード例に分けて詳しく解説

コード例 1: "Hello, World!"プログラム**

package main

import "fmt"

func main() {
fmt.Println("Hello, World!")
}

解説:

この最初の例では、Go言語の基本的なプログラムである「Hello, World!」を作成します。このプログラムを通じて、Goの基本的な構造と文法について説明します。

  1. package main

    • パッケージ宣言: Goのすべてのファイルはパッケージから始まります。
    • mainパッケージ: 特別な名前で、実行可能なプログラムを示します。他の名前を使うと、そのパッケージはライブラリとして機能します。
  2. import "fmt"

    • パッケージのインポート: 必要な標準ライブラリや外部パッケージを取り込むためにimportを使用します。
    • "fmt"パッケージ: 文字列のフォーマットやコンソールへの出力を行うための標準ライブラリです。
  3. func main() { ... }

    • 関数定義: funcキーワードで関数を定義します。
    • main関数: プログラムのエントリーポイントとなる特別な関数です。
  4. fmt.Println("Hello, World!")

    • 関数呼び出し: fmtパッケージ内のPrintln関数を呼び出しています。
    • 出力: "Hello, World!"という文字列をコンソールに出力し、最後に改行します。

ポイント解説:

  • パッケージとモジュールシステム

    Goではコードをパッケージ単位で管理します。package mainと宣言することで、そのファイルが実行可能なプログラムの一部であることを示します。

  • インポート文

    必要な機能を持つパッケージをimport文で取り込みます。複数のパッケージをインポートする場合は、括弧を使って以下のように書きます:

    import (
    "fmt"
    "os"
    )
  • 関数の基本構造

    関数はfuncキーワードで定義し、名前、パラメータ、戻り値を指定します。main関数は特別なもので、パラメータや戻り値を持ちません。

  • 標準ライブラリの活用

    fmtパッケージはフォーマット済みの入出力を提供します。Println関数は引数を文字列として表示し、最後に改行します。


実行方法:

  1. 上記のコードをhello.goというファイル名で保存します。

  2. ターミナル(コマンドプロンプト)でファイルのあるディレクトリに移動します。

  3. 以下のコマンドを実行してプログラムを実行します:

    go run hello.go
  4. コンソールにHello, World!と表示されれば成功です。


まとめ:

この例では、Go言語の基本的なプログラム構造と、コンソールへの文字列出力方法を学びました。これらはGoプログラミングの基礎となる重要な概念です。次の例では、変数の宣言やデータ型について詳しく見ていきます。

コード例 2: 変数の宣言と基本データ型

package main

import "fmt"

func main() {
// 明示的な型指定による変数宣言
var age int = 30
var name string = "太郎"
var height float64 = 170.5
var isStudent bool = true

// 型推論による変数宣言
var weight = 65.0

// 短縮宣言(:=)による変数宣言
city := "東京"

fmt.Println("名前:", name)
fmt.Println("年齢:", age)
fmt.Println("身長:", height)
fmt.Println("体重:", weight)
fmt.Println("学生ですか?", isStudent)
fmt.Println("都市:", city)
}

解説:

このコード例では、Go言語における変数の宣言方法と基本的なデータ型の使い方を学びます。変数の宣言にはいくつかの方法があり、それぞれの特徴を理解することが重要です。

  1. 明示的な型指定による変数宣言

    var age int = 30
    var name string = "太郎"
    var height float64 = 170.5
    var isStudent bool = true
    • varキーワード: 変数を宣言するために使用します。
    • データ型の指定: intstringfloat64boolなどの基本データ型を明示的に指定します。
    • 初期化: =を使って変数に値を代入します。
  2. 型推論による変数宣言

    var weight = 65.0
    • 型の省略: 値を代入する際にデータ型を省略すると、Goが自動的に型を推論します。
    • この例では: 65.0は小数点を含むため、float64型として推論されます。
  3. 短縮宣言(:=)による変数宣言

    city := "東京"
    • :=演算子: 変数の宣言と初期化を同時に行います。
    • 注意点: 関数内でのみ使用可能です。関数外ではvarを使う必要があります。
    • 型推論: 値からデータ型を自動的に推論します。
  4. 値の出力

    fmt.Println("名前:", name)
    // 他の変数も同様に出力
    • fmt.Println関数: 複数の引数を受け取り、スペースで区切って表示します。
    • 文字列と変数の組み合わせ: 文字列リテラルと変数をカンマで区切って渡します。

ポイント解説:

  • 基本データ型

    • 整数型: int, int8, int16, int32, int64
    • 浮動小数点型: float32, float64
    • 文字列型: string
    • ブール型: bool
  • 変数宣言の方法

    1. 明示的な型指定と初期化

      var 変数名 型 = 初期値
    2. 型推論による宣言

      var 変数名 = 初期値
      • データ型を省略すると、初期値から型が推論されます。
    3. 短縮宣言(関数内のみ)

      変数名 := 初期値
      • varキーワードやデータ型を省略できます。
      • 関数内でのみ使用可能。
  • 複数の変数を同時に宣言

    var a, b, c int = 1, 2, 3
    x, y := 10.5, "文字列"
  • デフォルト値

    • 変数を宣言しただけで初期化しない場合、自動的にデフォルト値が設定されます。
      • 数値型: 0または0.0
      • 文字列型: ""(空文字)
      • ブール型: false

実行方法:

  1. 上記のコードをvariables.goというファイル名で保存します。

  2. ターミナルでファイルのあるディレクトリに移動します。

  3. 以下のコマンドを実行してプログラムを実行します:

    go run variables.go
  4. 以下のような出力が得られます:

    名前: 太郎
    年齢: 30
    身長: 170.5
    体重: 65
    学生ですか? true
    都市: 東京

まとめ:

この例では、Go言語における変数の宣言方法と基本データ型について学びました。変数の宣言には複数の方法があり、状況に応じて使い分けることが重要です。また、データ型を理解することで、プログラム内で適切な操作を行うことができます。次の例では、定数の宣言と使用方法について詳しく見ていきます。

コード例 3: 定数の宣言と使用

package main

import "fmt"

func main() {
// 定数の宣言
const pi float64 = 3.14159
const name = "ゴーランゲージ"

// 複数の定数をまとめて宣言
const (
a = 1
b = 2
c = 3
)

// iotaを使用した列挙型のような定数
const (
Sunday = iota
Monday
Tuesday
Wednesday
Thursday
Friday
Saturday
)

fmt.Println("円周率:", pi)
fmt.Println("言語名:", name)
fmt.Println("定数a, b, c:", a, b, c)
fmt.Println("曜日:", Sunday, Monday, Tuesday, Wednesday, Thursday, Friday, Saturday)
}

解説:

このコード例では、Go言語における定数の宣言方法と、その使用法について学びます。定数はプログラム内で変更されない値を表し、データの不変性を保証するために使用します。

  1. 基本的な定数の宣言

    const pi float64 = 3.14159
    const name = "ゴーランゲージ"
    • constキーワード: 定数を宣言するために使用します。
    • データ型の指定: 必要に応じてデータ型を明示できますが、省略も可能です。
    • 初期化: 定数は宣言時に必ず初期化が必要です。
  2. 複数の定数をまとめて宣言

    const (
    a = 1
    b = 2
    c = 3
    )
    • 括弧()を使用: 複数の定数をグループ化して宣言できます。
    • コードの整理: 関連する定数をまとめることで、コードの可読性が向上します。
  3. iotaを使用した連続値の定数

    const (
    Sunday = iota
    Monday
    Tuesday
    Wednesday
    Thursday
    Friday
    Saturday
    )
    • iota: 定数宣言内で使われる特殊な識別子で、行番号を0から自動的に割り当てます。
    • 連続した値の割り当て: 列挙型のように連続した整数値を定数に割り当てるのに便利です。
    • 自動インクリメント: iotaは各行で自動的にインクリメントされます。
  4. 値の出力

    fmt.Println("円周率:", pi)
    // 他の定数も同様に出力
    • fmt.Println関数: 変数と同様に、定数の値も出力できます。

ポイント解説:

  • 定数の特徴

    • 変更不可: 定数は一度宣言すると、その値を変更することはできません。
    • 型推論: 変数と同様に、初期値からデータ型を推論できます。
  • iotaの活用

    • 連番の生成: 列挙型やビットフラグの定義に便利です。

    • 計算可能: iotaを使って計算した値を定数に割り当てることもできます。

      const (
      _ = iota // 0(使わない値はアンダースコアに割り当て)
      KB = 1 << (10 * iota) // 1 << 10
      MB // 1 << 20
      GB // 1 << 30
      )
  • 定数の型

    • 明示的な型指定: 必要に応じてデータ型を明示できます。
    • 暗黙的な型指定: 型を省略すると、使用される場面で適切な型に解釈されます。
  • 定数と変数の違い

    • 再代入の可否: 変数は値を変更できますが、定数は変更できません。
    • 宣言時の初期化: 定数は宣言と同時に初期化が必要ですが、変数は初期化を省略可能です(その場合デフォルト値が入ります)。

実行方法:

  1. 上記のコードをconstants.goというファイル名で保存します。

  2. ターミナルでファイルのあるディレクトリに移動します。

  3. 以下のコマンドを実行してプログラムを実行します:

    go run constants.go
  4. 以下のような出力が得られます:

    円周率: 3.14159
    言語名: ゴーランゲージ
    定数a, b, c: 1 2 3
    曜日: 0 1 2 3 4 5 6

まとめ:

この例では、Go言語における定数の宣言方法とiotaの使い方について学びました。定数はプログラム内で不変の値を扱う際に重要であり、iotaを活用することで連続した値を簡潔に定義できます。次の例では、基本的な演算子と式の使い方について詳しく見ていきます。

コード例 4: 基本的な演算子と式の使い方

package main

import "fmt"

func main() {
// 算術演算子
a := 10
b := 3
sum := a + b // 加算
diff := a - b // 減算
prod := a * b // 乗算
quot := a / b // 除算(整数の場合、商のみ)
rem := a % b // 剰余

// 浮動小数点数での除算
x := 10.0
y := 3.0
floatQuot := x / y // 浮動小数点数の除算

// 関係演算子(比較演算子)
isEqual := (a == b)
isNotEqual := (a != b)
isGreater := (a > b)
isLessOrEqual := (a <= b)

// 論理演算子
boolA := true
boolB := false
andResult := boolA && boolB // AND演算
orResult := boolA || boolB // OR演算
notResult := !boolA // NOT演算

// 出力
fmt.Println("算術演算子の結果:")
fmt.Println("sum:", sum)
fmt.Println("diff:", diff)
fmt.Println("prod:", prod)
fmt.Println("quot:", quot)
fmt.Println("rem:", rem)
fmt.Println("floatQuot:", floatQuot)

fmt.Println("\n関係演算子の結果:")
fmt.Println("isEqual:", isEqual)
fmt.Println("isNotEqual:", isNotEqual)
fmt.Println("isGreater:", isGreater)
fmt.Println("isLessOrEqual:", isLessOrEqual)

fmt.Println("\n論理演算子の結果:")
fmt.Println("andResult:", andResult)
fmt.Println("orResult:", orResult)
fmt.Println("notResult:", notResult)
}

解説:

このコード例では、Go言語における基本的な演算子の使い方と、それらを用いた式の評価方法について学びます。算術演算子、関係演算子、論理演算子の3種類を中心に解説します。

  1. 算術演算子

    a := 10
    b := 3
    sum := a + b // 加算
    diff := a - b // 減算
    prod := a * b // 乗算
    quot := a / b // 除算
    rem := a % b // 剰余
    • +(加算): 2つの値を足し合わせます。
    • -(減算): 左の値から右の値を引きます。
    • *(乗算): 2つの値を掛け合わせます。
    • /(除算): 左の値を右の値で割ります。整数同士の除算では商のみが得られます。
    • %(剰余): 左の値を右の値で割った余りを求めます。

    浮動小数点数での除算

    x := 10.0
    y := 3.0
    floatQuot := x / y
    • 浮動小数点数同士の除算では、結果も浮動小数点数となり、小数部分も得られます。
  2. 関係演算子(比較演算子)

    isEqual := (a == b)        // 等しいか
    isNotEqual := (a != b) // 等しくないか
    isGreater := (a > b) // 大きいか
    isLessOrEqual := (a <= b) // 小さいかまたは等しいか
    • ==(等しい): 左右の値が等しければtrue
    • !=(等しくない): 左右の値が等しくなければtrue
    • >(大きい): 左の値が右の値より大きければtrue
    • <(小さい): 左の値が右の値より小さければtrue
    • >=(大きいか等しい): 左の値が右の値以上であればtrue
    • <=(小さいか等しい): 左の値が右の値以下であればtrue
  3. 論理演算子

    boolA := true
    boolB := false
    andResult := boolA && boolB // 論理積(AND)
    orResult := boolA || boolB // 論理和(OR)
    notResult := !boolA // 否定(NOT)
    • &&(AND): 両方の値がtrueの場合にtrue
    • ||(OR): どちらかの値がtrueの場合にtrue
    • !(NOT): 値を反転します。truefalseに、falsetrueに。
  4. 結果の出力

    fmt.Println("算術演算子の結果:")
    fmt.Println("sum:", sum)
    // 他の結果も同様に出力
    • 各演算の結果をコンソールに出力します。

ポイント解説:

  • 整数と浮動小数点数の違い

    • 整数同士の除算では、結果も整数となり、小数部分は切り捨てられます。
    • 浮動小数点数を用いることで、小数部分を含む精度の高い計算が可能です。
  • 型の混在に注意

    • 異なるデータ型同士(例えばintfloat64)で演算を行うとエラーになります。

    • 必要に応じて型変換を行います。

      var a int = 10
      var b float64 = 3.0
      result := float64(a) / b
  • 優先順位

    • 演算子には優先順位があります。必要に応じて()を使って明示的に順序を指定します。

      result := (a + b) * c
  • ブール型

    • 論理演算子はブール値(trueまたはfalse)に対して使用します。
    • 条件分岐やループでの条件判定に利用されます。

実行方法:

  1. 上記のコードをoperators.goというファイル名で保存します。

  2. ターミナルでファイルのあるディレクトリに移動します。

  3. 以下のコマンドを実行してプログラムを実行します:

    go run operators.go
  4. 以下のような出力が得られます:

    算術演算子の結果:
    sum: 13
    diff: 7
    prod: 30
    quot: 3
    rem: 1
    floatQuot: 3.3333333333333335

    関係演算子の結果:
    isEqual: false
    isNotEqual: true
    isGreater: true
    isLessOrEqual: false

    論理演算子の結果:
    andResult: false
    orResult: true
    notResult: false

まとめ:

この例では、Go言語における基本的な演算子の使い方と、それらを組み合わせた式の評価方法について学びました。算術演算子で数値の計算を行い、関係演算子で値を比較し、論理演算子でブール値を操作します。これらの演算子は、条件分岐やループなど、プログラムの制御構造で頻繁に使用されます。次の例では、条件分岐(if文とelse文)について詳しく見ていきます。

コード例 5: 関数の定義と呼び出し

package main

import "fmt"

// 引数と戻り値がある関数の定義
func add(a int, b int) int {
return a + b
}

// 複数の戻り値を持つ関数の定義
func divide(a int, b int) (int, int) {
quotient := a / b
remainder := a % b
return quotient, remainder
}

func main() {
sum := add(5, 3)
fmt.Println("5 + 3 =", sum)

q, r := divide(10, 3)
fmt.Println("10 ÷ 3 の商:", q, "余り:", r)
}

解説:

このコード例では、Go言語における関数の定義と呼び出しについて学びます。関数はコードを再利用可能なブロックにまとめ、プログラムをより整理された形で構築するのに役立ちます。

  1. 関数の定義

    func 関数名(引数リスト) 戻り値の型 {
    // 関数の処理内容
    }
    • funcキーワード: 関数を定義するために使用します。
    • 関数名: 関数の名前。小文字で始まるとパッケージ内でのみ公開され、大文字で始まると他のパッケージからもアクセス可能になります。
    • 引数リスト: 関数が受け取るパラメータ。引数名 データ型の形式で記述します。
    • 戻り値の型: 関数が返す値のデータ型。
  2. 引数と戻り値がある関数

    func add(a int, b int) int {
    return a + b
    }
    • 引数: abの2つの整数型の引数を受け取ります。
    • 戻り値: int型の値を返します。
    • 処理内容: a + bの結果を返します。
  3. 複数の戻り値を持つ関数

    func divide(a int, b int) (int, int) {
    quotient := a / b
    remainder := a % b
    return quotient, remainder
    }
    • 戻り値の型の括弧: 複数の戻り値がある場合、型を()で囲みます。
    • 戻り値: int型の商と余りの2つの値を返します。
  4. 関数の呼び出し

    sum := add(5, 3)
    q, r := divide(10, 3)
    • add関数の呼び出し: 53を引数として渡し、結果をsumに代入します。
    • divide関数の呼び出し: 103を引数として渡し、商と余りをそれぞれqrに代入します。
  5. 結果の出力

    fmt.Println("5 + 3 =", sum)
    fmt.Println("10 ÷ 3 の商:", q, "余り:", r)
    • 計算結果をコンソールに出力します。

ポイント解説:

  • 引数のデータ型の省略

    • 複数の引数が同じデータ型の場合、以下のように省略できます。

      func add(a, b int) int {
      return a + b
      }
  • 名前付き戻り値

    • 戻り値に名前を付けることで、return文で明示的に変数を指定せずに値を返すことができます。

      func multiply(a, b int) (result int) {
      result = a * b
      return
      }
  • 無名関数(匿名関数)

    • 関数に名前を付けずに、その場で定義して使用できます。

      func main() {
      func(msg string) {
      fmt.Println(msg)
      }("こんにちは")
      }
  • 可変長引数

    • 引数の数が変動する関数を定義できます。

      func sum(numbers ...int) int {
      total := 0
      for _, n := range numbers {
      total += n
      }
      return total
      }

実行方法:

  1. 上記のコードをfunctions.goというファイル名で保存します。

  2. ターミナルでファイルのあるディレクトリに移動します。

  3. 以下のコマンドを実行してプログラムを実行します:

    go run functions.go
  4. 以下のような出力が得られます:

    5 + 3 = 8
    10 ÷ 3 の商: 3 余り: 1

まとめ:

この例では、Go言語における関数の定義方法と呼び出し方について学びました。関数はコードの再利用性を高め、プログラムを整理するのに役立ちます。引数や戻り値の型、複数の戻り値、名前付き戻り値、無名関数など、Goの関数には多くの特徴があります。次の例では、配列とスライスについて詳しく見ていきます。


コード例 6: 条件分岐(if文とelse文)

package main

import "fmt"

func main() {
score := 85

if score >= 90 {
fmt.Println("評価: A")
} else if score >= 80 {
fmt.Println("評価: B")
} else if score >= 70 {
fmt.Println("評価: C")
} else if score >= 60 {
fmt.Println("評価: D")
} else {
fmt.Println("評価: F")
}
}

解説:

このコード例では、Go言語における条件分岐の基本であるif文とelse文の使い方を学びます。変数scoreの値に応じて、適切な評価を出力します。

  1. if文の基本構造

    if 条件式 {
    // 条件が真のときに実行されるコード
    }
    • 条件式: ブール値(trueまたはfalse)を返す式。
    • ブロック: 条件がtrueのときに実行されるコードを{}で囲みます。
  2. else ifelseの使用

    else if 条件式 {
    // 前の条件が偽で、この条件が真のときに実行
    } else {
    // すべての条件が偽のときに実行
    }
    • else if: 前のifまたはelse ifの条件が偽で、新たな条件をチェックしたい場合に使用します。
    • else: すべてのifおよびelse ifの条件が偽のときに実行されます。
  3. コードの流れ

    • scoreの値が90以上の場合、"評価: A"を出力。
    • scoreが80以上90未満の場合、"評価: B"を出力。
    • 以下同様に条件をチェックし、どれにも当てはまらない場合は"評価: F"を出力します。

ポイント解説:

  • 条件式

    • 条件式には、比較演算子や論理演算子を使用できます。
    • 条件式は必ずブール値を返す必要があります。
  • if文での短縮宣言

    • if文内で変数を宣言し、同時に条件判定に使用できます。

      if score := 85; score >= 90 {
      fmt.Println("評価: A")
      }
    • この場合、scoreif文内でのみ有効な変数となります。

  • ネストされた条件分岐

    • 必要に応じて、if文の中にさらにif文を記述することも可能です。

      if score >= 60 {
      fmt.Println("合格")
      if score >= 80 {
      fmt.Println("優秀です")
      }
      } else {
      fmt.Println("不合格")
      }
  • switch文の利用

    • 複数の条件を判定する場合、switch文を使用するとコードが見やすくなることがあります。

実行方法:

  1. 上記のコードをif_else.goというファイル名で保存します。

  2. ターミナルでファイルのあるディレクトリに移動します。

  3. 以下のコマンドを実行してプログラムを実行します:

    go run if_else.go
  4. 以下のような出力が得られます:

    評価: B

まとめ:

この例では、Go言語における条件分岐の基本であるif文とelse文の使い方を学びました。条件式の評価により、プログラムの流れを制御することができます。if文はプログラムのロジックを構築する上で非常に重要な要素です。次の例では、switch文を使った条件分岐について詳しく見ていきます。

コード例 7: switch文による条件分岐

package main

import "fmt"

func main() {
day := 3

switch day {
case 1:
fmt.Println("月曜日")
case 2:
fmt.Println("火曜日")
case 3:
fmt.Println("水曜日")
case 4:
fmt.Println("木曜日")
case 5:
fmt.Println("金曜日")
case 6:
fmt.Println("土曜日")
case 7:
fmt.Println("日曜日")
default:
fmt.Println("無効な値です")
}
}

解説:

このコード例では、Go言語におけるswitch文の使い方を学びます。switch文は、複数の条件を効率的に処理するための構文であり、コードの可読性を高めます。

  1. switch文の基本構造

    switch 変数または式 {
    case1:
    // 値1の場合に実行されるコード
    case2:
    // 値2の場合に実行されるコード
    default:
    // どのケースにも一致しない場合に実行されるコード
    }
    • switchキーワード: 条件分岐の開始を示します。
    • caseキーワード: 比較対象の値を指定します。
    • defaultキーワード: どのcaseにも一致しない場合に実行されます。
  2. コードの流れ

    • 変数dayの値に応じて、対応する曜日を出力します。
    • day3の場合、case 3が一致し、"水曜日"が出力されます。
    • defaultケースは、すべてのcaseに一致しない場合に実行されます。
  3. switch文の特徴

    • 自動的なbreak: Goのswitch文では、各caseの末尾にbreakを記述する必要はありません。マッチしたケースのコードを実行した後、自動的にswitch文から抜け出します。

    • 複数の値を持つcase:

      switch day {
      case 1, 2, 3, 4, 5:
      fmt.Println("平日")
      case 6, 7:
      fmt.Println("週末")
      default:
      fmt.Println("無効な値です")
      }
      • カンマで区切って、複数の値を一つのcaseで処理できます。
  4. 条件付きswitch

    • switch文に条件式を記述せず、各caseに条件式を書くことも可能です。

      score := 85

      switch {
      case score >= 90:
      fmt.Println("評価: A")
      case score >= 80:
      fmt.Println("評価: B")
      case score >= 70:
      fmt.Println("評価: C")
      case score >= 60:
      fmt.Println("評価: D")
      default:
      fmt.Println("評価: F")
      }
      • この場合、最初にtrueとなるcaseが実行されます。
  5. fallthroughキーワード

    • fallthroughを使用すると、次のcaseのコードも続けて実行されます。

      num := 1

      switch num {
      case 1:
      fmt.Println("One")
      fallthrough
      case 2:
      fmt.Println("Two")
      default:
      fmt.Println("Default")
      }
      • この例では、"One""Two"の両方が出力されます。

ポイント解説:

  • switch文の利点

    • 複数のif-else文を置き換えることで、コードがシンプルで読みやすくなります。
  • switch文での型

    • switch文で評価する変数や式の型は、caseで指定する値の型と一致している必要があります。
  • defaultケース

    • defaultケースは任意ですが、予期しない値が来たときの処理を記述するのに有用です。

実行方法:

  1. 上記のコードをswitch.goというファイル名で保存します。

  2. ターミナルでファイルのあるディレクトリに移動します。

  3. 以下のコマンドを実行してプログラムを実行します:

    go run switch.go
  4. 以下のような出力が得られます:

    水曜日

まとめ:

この例では、Go言語におけるswitch文の使い方と、そのさまざまなパターンについて学びました。switch文を使うことで、複数の条件分岐を簡潔に記述できます。また、fallthroughや条件付きswitchなどの機能を理解することで、より柔軟な条件分岐が可能になります。次の例では、ループ処理(for文)について詳しく見ていきます。

コード例 8: ループ処理(for文)

package main

import "fmt"

func main() {
// 1から5までの数字を順番に出力する基本的なfor文
for i := 1; i <= 5; i++ {
fmt.Println("カウント:", i)
}

// 条件のみのfor文(whileループのような使い方)
count := 0
for count < 5 {
fmt.Println("条件付きループ:", count)
count++
}

// 無限ループ
sum := 0
for {
sum++
if sum >= 5 {
break
}
}
fmt.Println("無限ループでの合計:", sum)

// 配列やスライスの要素をループで処理する(rangeを使用)
numbers := []int{10, 20, 30, 40, 50}
for index, value := range numbers {
fmt.Printf("インデックス: %d, 値: %d\n", index, value)
}

// 文字列のループ処理
text := "Go言語"
for index, runeValue := range text {
fmt.Printf("文字位置: %d, 文字: %c\n", index, runeValue)
}
}

解説:

このコード例では、Go言語におけるfor文を使ったループ処理について学びます。Goのforループは、他の言語のforwhiledo-whileループをすべて包含ほうがん⓪ する柔軟な構造を持っています。

  1. 基本的なfor

    for i := 1; i <= 5; i++ {
    fmt.Println("カウント:", i)
    }
    • 初期化文: i := 1 ループ開始時に一度だけ実行されます。
    • 条件式: i <= 5 ループの継続条件です。この条件がtrueである限りループが続きます。
    • 後処理: i++ 各ループの最後に実行されます。
    • 動作: iが1から5までインクリメントされ、その都度値が出力されます。
  2. 条件のみのfor

    count := 0
    for count < 5 {
    fmt.Println("条件付きループ:", count)
    count++
    }
    • 条件式のみ: 初期化文と後処理を省略しています。
    • 動作: countが5未満の間ループし、各回でcountをインクリメントします。
    • 類似性: 他の言語のwhileループと同様の動作をします。
  3. 無限ループ

    sum := 0
    for {
    sum++
    if sum >= 5 {
    break
    }
    }
    • 条件なしのfor: for { ... }と書くと無限ループになります。
    • break: 特定の条件を満たしたときにループを抜けます。
    • 動作: sumが5以上になるとループを終了します。
  4. rangeを使ったループ

    numbers := []int{10, 20, 30, 40, 50}
    for index, value := range numbers {
    fmt.Printf("インデックス: %d, 値: %d\n", index, value)
    }
    • rangeキーワード: 配列やスライス、マップ、チャネル、文字列などをループ処理する際に使用します。
    • indexvalue: それぞれ要素のインデックスと値を取得します。
    • 動作: numbersスライスの各要素を順番に処理します。
  5. 文字列のループ処理

    text := "Go言語"
    for index, runeValue := range text {
    fmt.Printf("文字位置: %d, 文字: %c\n", index, runeValue)
    }
    • Unicode対応: Goの文字列はUTF-8でエンコードされており、rangeを使うとUnicodeコードポイント(ルーン)単位で処理できます。
    • rune: Unicodeコードポイントを表す エイリアス(alias) で、実体はint32型です。

ポイント解説:

  • forループの構文

    • 完全な形式

      for 初期化文; 条件式; 後処理 {
      // 処理内容
      }
    • 省略可能

      • 初期化文、条件式、後処理のいずれも省略可能です。
      • 条件式を省略すると、条件が常にtrueとなり無限ループになります。
  • ループ制御文

    • break

      • ループを即座に終了します。
      • 多重ループの場合、内側のループのみを抜けます。
    • continue

      • ループの現在の反復をスキップし、次の反復に進みます。
  • rangeを使ったループ

    • 配列やスライス

      • インデックスと値を取得できます。
    • マップ

      • キーと値を取得できます。
    • 文字列

      • インデックスとUnicodeコードポイントを取得します。
    • インデックスまたは値のみの取得

      • 片方かたほう②が不要な場合はアンダースコア_を使って無視します。
      for _, value := range numbers {
      // インデックスを無視
      }

実行方法:

  1. 上記のコードをfor_loop.goというファイル名で保存します。

  2. ターミナルでファイルのあるディレクトリに移動します。

  3. 以下のコマンドを実行してプログラムを実行します:

    go run for_loop.go
  4. 以下のような出力が得られます:

    カウント: 1
    カウント: 2
    カウント: 3
    カウント: 4
    カウント: 5
    条件付きループ: 0
    条件付きループ: 1
    条件付きループ: 2
    条件付きループ: 3
    条件付きループ: 4
    無限ループでの合計: 5
    インデックス: 0, 値: 10
    インデックス: 1, 値: 20
    インデックス: 2, 値: 30
    インデックス: 3, 値: 40
    インデックス: 4, 値: 50
    文字位置: 0, 文字: G
    文字位置: 1, 文字: o
    文字位置: 2, 文字: 言
    文字位置: 5, 文字: 語

    "Go言語"という文字列は、UTF-8エンコードによりマルチバイト文字が含まれるため、文字位置のインデックスが期待と異なる場合があります。


まとめ:

この例では、Go言語におけるfor文を使ったループ処理について学びました。for文はGoにおける唯一のループ構造であり、さまざまな形で使用できます。基本的なカウンタループ、条件付きループ、無限ループ、そしてrangeを使ったコレクションcollectionの反復処理など、多岐にわたる使い方が可能です。これらの知識を活用して、より複雑なループ処理やデータ操作を行うことができます。次の例では、配列とスライスについて詳しく見ていきます。

コード例 9: 配列とスライスの基本

package main

import "fmt"

func main() {
// 配列の宣言と初期化
var arr [5]int = [5]int{10, 20, 30, 40, 50}
fmt.Println("配列:", arr)

// 配列の要素にアクセス
fmt.Println("配列の要素[0]:", arr[0])
fmt.Println("配列の要素[4]:", arr[4])

// スライスの宣言と初期化
slice := []int{100, 200, 300}
fmt.Println("スライス:", slice)

// スライスの要素にアクセス
fmt.Println("スライスの要素[0]:", slice[0])

// スライスへの要素の追加(append関数)
slice = append(slice, 400)
fmt.Println("スライス(要素追加後):", slice)

// 配列からスライスを作成
subSlice := arr[1:4] // インデックス1から3の要素を含む
fmt.Println("配列から作成したスライス:", subSlice)
}

解説:

このコード例では、Go言語における配列スライスの基本的な使い方について学びます。配列とスライスは、複数の同じ型のデータを格納するためのデータ構造です。

  1. 配列の宣言と初期化

    var arr [5]int = [5]int{10, 20, 30, 40, 50}
    • 配列の宣言: var 配列名 [要素数]データ型 の形式で宣言します。
    • 初期化: {}内にカンマ区切りで要素を指定します。
    • 固定長: 配列の要素数は固定で、変更できません。
  2. 配列の要素へのアクセス

    fmt.Println("配列の要素[0]:", arr[0])
    fmt.Println("配列の要素[4]:", arr[4])
    • インデックス指定: 配列名とインデックスを使って要素にアクセスします。インデックスは0から始まります。
    • 範囲外アクセスに注意: 配列の範囲外のインデックスを指定すると、実行時エラーになります。
  3. スライスの宣言と初期化

    slice := []int{100, 200, 300}
    • スライスの宣言: []の中に要素数を指定しません。
    • 動的サイズ: スライスは要素の追加や削除が可能で、サイズが動的に変化します。
  4. スライスの要素へのアクセス

    fmt.Println("スライスの要素[0]:", slice[0])
    • 配列と同様: インデックスを使って要素にアクセスします。
  5. スライスへの要素の追加

    slice = append(slice, 400)
    fmt.Println("スライス(要素追加後):", slice)
    • append関数: スライスに新しい要素を追加します。元のスライスに要素を追加した新しいスライスが返されます。
    • 再代入が必要: appendの結果を元のスライス変数に再代入する必要があります。
  6. 配列からスライスを作成

    subSlice := arr[1:4] // インデックス1から3の要素を含む
    fmt.Println("配列から作成したスライス:", subSlice)
    • スライス式: 配列名[開始インデックス:終了インデックス] の形式で、配列やスライスから部分的なスライスを作成できます。
    • 開始インデックス: 含まれます。
    • 終了インデックス: 含まれません。

ポイント解説:

  • 配列とスライスの違い

    • 配列

      • 要素数が固定。
      • メモリ上で連続した領域に格納される。
      • 値型であり、関数に渡すとコピーされます。
    • スライス

      • 要素数が可変。
      • 配列への参照を内部に持つ。
      • 参照型であり、関数に渡すと参照が渡されます。
  • スライスの内部構造

    • ポインタ: 元の配列を指すポインタ。
    • 長さ(length): スライス内の要素数。
    • 容量(capacity): スライスが指す配列の要素数。
  • append関数

    • 必要に応じて内部で新しい配列を割り当てます。
    • 元のスライスの容量が不足している場合、新しい配列が作成されます。
  • スライスのコピー

    • copy関数: スライス間の要素をコピーします。

      src := []int{1, 2, 3}
      dst := make([]int, len(src))
      copy(dst, src)

実行方法:

  1. 上記のコードをarrays_slices.goというファイル名で保存します。

  2. ターミナルでファイルのあるディレクトリに移動します。

  3. 以下のコマンドを実行してプログラムを実行します:

    go run arrays_slices.go
  4. 以下のような出力が得られます:

    配列: [10 20 30 40 50]
    配列の要素[0]: 10
    配列の要素[4]: 50
    スライス: [100 200 300]
    スライスの要素[0]: 100
    スライス(要素追加後): [100 200 300 400]
    配列から作成したスライス: [20 30 40]

まとめ:

この例では、Go言語における配列とスライスの基本的な使い方について学びました。配列は固定長のデータ構造であり、スライスは可変長で柔軟に要素を操作できます。スライスは配列の一部を参照することができ、効率的なデータ操作が可能です。次の例では、マップ(連想配列)について詳しく見ていきます。

コード例 10: マップ(連想配列)の基本

package main

import "fmt"

func main() {
// マップの宣言と初期化
var scores map[string]int = map[string]int{
"太郎": 85,
"花子": 92,
"次郎": 78,
}
fmt.Println("マップ:", scores)

// マップへの要素の追加
scores["三郎"] = 88
fmt.Println("要素追加後のマップ:", scores)

// マップから要素を取得
hanakoScore := scores["花子"]
fmt.Println("花子のスコア:", hanakoScore)

// キーの存在確認
score, exists := scores["四郎"]
if exists {
fmt.Println("四郎のスコア:", score)
} else {
fmt.Println("四郎のスコアは存在しません")
}

// マップの要素の削除
delete(scores, "次郎")
fmt.Println("要素削除後のマップ:", scores)

// マップのループ処理
fmt.Println("マップのループ処理:")
for name, score := range scores {
fmt.Printf("名前: %s, スコア: %d\n", name, score)
}
}

解説:

このコード例では、Go言語における**マップ(連想配列)**の基本的な使い方について学びます。マップはキーと値のペアでデータを管理するデータ構造です。

  1. マップの宣言と初期化

    var scores map[string]int = map[string]int{
    "太郎": 85,
    "花子": 92,
    "次郎": 78,
    }
    • マップの宣言: var マップ名 map[キーの型]値の型 の形式で宣言します。
    • 初期化: map[キーの型]値の型{キー: 値, ...} の形式で初期化します。
    • キーの型: この例ではstring型を使用しています。
    • 値の型: この例ではint型を使用しています。
  2. マップへの要素の追加

    scores["三郎"] = 88
    • 新しいキーと値のペアを追加: マップ名にキーを指定して値を代入します。
    • 存在しないキー: キーが存在しない場合、新たに追加されます。
  3. マップから要素を取得

    hanakoScore := scores["花子"]
    fmt.Println("花子のスコア:", hanakoScore)
    • キーを指定して値を取得: マップ名とキーで値を取得します。
    • 存在するキー: キーが存在する場合、その値が返されます。
  4. キーの存在確認

    score, exists := scores["四郎"]
    if exists {
    fmt.Println("四郎のスコア:", score)
    } else {
    fmt.Println("四郎のスコアは存在しません")
    }
    • 2つの戻り値: マップから値を取得する際、2つ目の戻り値でキーの存在を確認できます。
    • 存在しないキー: キーが存在しない場合、値はデータ型のゼロ値(この場合は0)、existsfalseになります。
  5. マップの要素の削除

    delete(scores, "次郎")
    • delete関数: マップから指定したキーの要素を削除します。
    • 存在しないキーを削除: 存在しないキーを指定してもエラーにはなりません。
  6. マップのループ処理

    for name, score := range scores {
    fmt.Printf("名前: %s, スコア: %d\n", name, score)
    }
    • rangeキーワード: マップのキーと値を順に取得します。
    • 順序が保証されない: マップのループ処理では、キーの順序は保証されません。

ポイント解説:

  • マップの初期化

    • make関数を使用

      scores := make(map[string]int)
      • 空のマップを作成します。
    • リテラル (literal) で初期化

      scores := map[string]int{
      "太郎": 85,
      "花子": 92,
      }
      • 初期値を持つマップを作成します。
  • キーの存在確認

    • 戻り値の使い方

      value, exists := scores["キー"]
      • existstrueの場合、valueには対応する値が入っています。
      • existsfalseの場合、valueは型のゼロ値になります。
  • マップの注意点

    • nilマップ

      • 宣言だけして初期化しないマップはnilとなり、要素の追加ができません。
      var m map[string]int
      // m["key"] = 1 // これはエラーになります
    • マップの比較

      • マップ同士を直接比較することはできません(==!=は使用不可)。
      • マップがnilかどうかは比較できます。
  • ループ処理

    • キーまたは値のみの取得

      • 不要な変数はアンダースコア_で無視できます。
      for _, score := range scores {
      fmt.Println("スコア:", score)
      }

実行方法:

  1. 上記のコードをmaps.goというファイル名で保存します。

  2. ターミナルでファイルのあるディレクトリに移動します。

  3. 次のコマンドを実行してプログラムを実行します:

    go run maps.go
  4. 以下のような出力が得られます(順序は実行ごとに異なる場合があります):

    マップ: map[太郎:85 花子:92 次郎:78]
    要素追加後のマップ: map[三郎:88 太郎:85 花子:92 次郎:78]
    花子のスコア: 92
    四郎のスコアは存在しません
    要素削除後のマップ: map[三郎:88 太郎:85 花子:92]
    マップのループ処理:
    名前: 三郎, スコア: 88
    名前: 太郎, スコア: 85
    名前: 花子, スコア: 92

まとめ:

この例では、Go言語におけるマップ(連想配列)の基本的な使い方について学びました。マップはキーと値のペアでデータを管理し、効率的なデータアクセスが可能です。キーの存在確認や要素の追加・削除、ループ処理など、マップを操作するための基本的な方法を理解しました。次のコード例では、構造体(Struct)について詳しく見ていきます。

コード例 11: 構造体(Struct)の基本

package main

import "fmt"

// Person構造体の定義
type Person struct {
Name string
Age int
Gender string
}

func main() {
// 構造体のインスタンスを作成(フィールド名を指定)
person1 := Person{
Name: "田中太郎",
Age: 30,
Gender: "男性",
}
fmt.Println("Person 1:", person1)

// 構造体のインスタンスを作成(フィールド順に指定)
person2 := Person{"山田花子", 25, "女性"}
fmt.Println("Person 2:", person2)

// フィールドへのアクセスと更新
fmt.Println("Person 1の名前:", person1.Name)
person1.Age = 31
fmt.Println("Person 1の年齢(更新後):", person1.Age)

// ポインタを使った構造体の操作
personPointer := &person1
personPointer.Gender = "その他"
fmt.Println("Person 1の性別(更新後):", person1.Gender)
}

解説:

このコード例では、Go言語における**構造体(Struct)**の基本的な使い方について学びます。構造体は、複数のフィールド(データ)をまとめて一つの新しい型として定義できるデータ構造です。

  1. 構造体の定義

    type Person struct {
    Name string
    Age int
    Gender string
    }
    • typeキーワード: 新しい型を定義するために使用します。
    • Person構造体: NameAgeGenderという3つのフィールドを持つ構造体です。
    • フィールドの定義: 各フィールドはフィールド名 データ型の形式で定義します。
  2. 構造体のインスタンスを作成(フィールド名を指定)

    person1 := Person{
    Name: "田中太郎",
    Age: 30,
    Gender: "男性",
    }
    • 初期化方法: フィールド名を指定して初期値を割り当てます。
    • 順不同での指定: フィールド名を明示する場合、順番は任意です。
    • 可読性の向上: フィールド名を明示することでコードの可読性が高まります。
  3. 構造体のインスタンスを作成(フィールド順に指定)

    person2 := Person{"山田花子", 25, "女性"}
    • フィールド名を省略: 定義した順番で値を渡します。
    • 注意点: フィールドの順序やデータ型を正確に一致させる必要があります。
  4. フィールドへのアクセスと更新

    fmt.Println("Person 1の名前:", person1.Name)
    person1.Age = 31
    fmt.Println("Person 1の年齢(更新後):", person1.Age)
    • アクセス: 構造体インスタンス.フィールド名でフィールドにアクセスします。
    • 更新: フィールドに新しい値を代入して更新します。
  5. ポインタを使った構造体の操作

    personPointer := &person1
    personPointer.Gender = "その他"
    fmt.Println("Person 1の性別(更新後):", person1.Gender)
    • 構造体のアドレス取得: &演算子を使って構造体のポインタを取得します。
    • ポインタ経由でのアクセス: ポインタからでもドット演算子でフィールドにアクセスできます。
    • 自動デリファレンス: ポインタを通じてフィールドにアクセスする際、Goが自動的にデリファレンスします。

ポイント解説:

  • 構造体の基本

    • カスタム型の作成: 構造体を使うことで複雑なデータ型を定義できます。
    • エクスポート: フィールド名が大文字で始まると、他のパッケージからもアクセス可能になります。
  • 構造体の初期化方法

    • ゼロ値での初期化

      var person3 Person
      fmt.Println("Person 3:", person3)
      • フィールドはそれぞれのデータ型のゼロ値で初期化されます。
    • 新しいインスタンスの作成

      person4 := new(Person)
      • new関数を使うと、構造体のポインタを取得します。
  • メソッドの定義

    • メソッドの追加

      func (p Person) Greet() {
      fmt.Printf("こんにちは、%sです。\n", p.Name)
      }
      • レシーバー: func (レシーバー 型) メソッド名()の形式で定義します。
      • 値レシーバーとポインタレシーバー: メソッドがレシーバーを値として受け取るか、ポインタとして受け取るかで挙動が変わります。
  • 構造体の比較

    • 比較可能な構造体

      • すべてのフィールドが比較可能な場合、==!=で比較できます。
      if person1 == person2 {
      fmt.Println("同じ人物です")
      } else {
      fmt.Println("異なる人物です")
      }
  • 匿名フィールドと埋め込み

    • 継承のような機能

      type Employee struct {
      Person
      Position string
      }
      • Employee構造体はPersonのフィールドを継承します。

実行方法:

  1. 上記のコードをstructs.goというファイル名で保存します。

  2. ターミナルで保存したディレクトリに移動します。

  3. 以下のコマンドを実行します:

    go run structs.go
  4. 以下の出力が表示されます:

    Person 1: {田中太郎 30 男性}
    Person 2: {山田花子 25 女性}
    Person 1の名前: 田中太郎
    Person 1の年齢(更新後): 31
    Person 1の性別(更新後): その他

まとめ:

この例では、Go言語における構造体の定義方法や基本的な操作について学びました。構造体を使うことで、関連するデータを一つのまとまりとして扱うことができます。フィールドの定義、インスタンスの作成、フィールドへのアクセスと更新、ポインタの使用方法など、構造体の基本的な使い方を理解しました。次のコード例では、メソッドとインターフェースについて詳しく見ていきます。

コード例 12: メソッドとインターフェースの基本

package main

import "fmt"

// Animalインターフェースの定義
type Animal interface {
Speak() string
}

// Dog構造体の定義
type Dog struct {
Name string
}

// Dog構造体に対するメソッドの定義
func (d Dog) Speak() string {
return d.Name + "はワンと鳴く"
}

// Cat構造体の定義
type Cat struct {
Name string
}

// Cat構造体に対するメソッドの定義
func (c Cat) Speak() string {
return c.Name + "はニャーと鳴く"
}

func main() {
// Animal型のスライスを作成
animals := []Animal{
Dog{"ポチ"},
Cat{"タマ"},
}

// すべての動物に対してSpeakメソッドを呼び出す
for _, animal := range animals {
fmt.Println(animal.Speak())
}
}

解説:

このコード例では、Go言語におけるメソッドインターフェースの基本的な使い方について学びます。インターフェースを使用することで、異なる型でも共通のメソッドを持つ場合に、それらを統一的に扱うことができます。

  1. インターフェースの定義

    type Animal interface {
    Speak() string
    }
    • typeキーワード: 新しい型を定義します。
    • Animalインターフェース: Speakというメソッドを持つことを要求するインターフェースです。
    • メソッドのシグネチャ: Speak() stringは、引数なしでstring型の値を返すメソッドを定義しています。
  2. 構造体の定義とメソッドの実装

    Dog構造体とそのメソッド

    type Dog struct {
    Name string
    }

    func (d Dog) Speak() string {
    return d.Name + "はワンと鳴く"
    }
    • Dog構造体: Nameというフィールドを持つ構造体です。
    • メソッドの定義: func (d Dog) Speak() stringの形式で、Dog型の値に対するメソッドを定義しています。
    • レシーバー: (d Dog)がレシーバーで、メソッドがどの型に属するかを示します。

    Cat構造体とそのメソッド

    type Cat struct {
    Name string
    }

    func (c Cat) Speak() string {
    return c.Name + "はニャーと鳴く"
    }
    • Cat構造体: Nameというフィールドを持つ構造体です。
    • メソッドの定義: Cat型の値に対するSpeakメソッドを定義しています。
  3. メイン関数での利用

    func main() {
    animals := []Animal{
    Dog{"ポチ"},
    Cat{"タマ"},
    }

    for _, animal := range animals {
    fmt.Println(animal.Speak())
    }
    }
    • Animal型のスライス: DogCatAnimalインターフェースを実装しているため、Animal型として扱うことができます。
    • メソッドの呼び出し: スライス内の各要素に対してSpeakメソッドを呼び出します。

ポイント解説:

  • インターフェース

    • 定義方法: type インターフェース名 interface { メソッド一覧 }
    • 実装: 明示的な宣言なしに、必要なメソッドを実装するだけでインターフェースを実装したことになります。
    • ポリモーフィズム: インターフェースを使うことで、異なる型でも共通のメソッドを持つ場合に統一的に扱えます。
  • メソッド

    • レシーバー: メソッドがどの型に属するかを示します。
      • 値レシーバー: (d Dog)のように値を受け取る。値のコピーが渡されます。
      • ポインタレシーバー: (d *Dog)のようにポインタを受け取る。元の値を直接操作できます。
  • メソッドの選択

    • 値レシーバーとポインタレシーバーの違い:
      • 値レシーバーはレシーバーのコピーに対して操作を行います。
      • ポインタレシーバーは元のオブジェクトを直接操作できます。
  • インターフェースの組み合わせ

    • インターフェースは他のインターフェースを含めることができます。

      type Walker interface {
      Walk()
      }

      type Runner interface {
      Walker
      Run()
      }
  • 空のインターフェース

    • interface{}: すべての型を満たす特殊なインターフェース。

      var x interface{}
      x = 10
      x = "文字列"

実行方法:

  1. 上記のコードをinterfaces.goというファイル名で保存します。

  2. ターミナルで保存したディレクトリに移動します。

  3. 以下のコマンドを実行します:

    go run interfaces.go
  4. 以下の出力が表示されます:

    ポチはワンと鳴く
    タマはニャーと鳴く

まとめ:

この例では、Go言語におけるメソッドとインターフェースの基本的な使い方について学びました。インターフェースを使用することで、異なる型でも共通のメソッドを持つ場合に、それらを統一的に扱うことができます。メソッドの定義方法、レシーバーの使い方、インターフェースの実装方法などを理解しました。次のコード例では、エラー処理とカスタムエラーについて詳しく見ていきます。

コード例 13: エラー処理とカスタムエラー

package main

import (
"errors"
"fmt"
"math"
)

// カスタムエラーの定義
type NegativeNumberError struct {
number float64
msg string
}

func (e *NegativeNumberError) Error() string {
return fmt.Sprintf("%s: %f", e.msg, e.number)
}

// 平方根を計算する関数
func sqrt(number float64) (float64, error) {
if number < 0 {
return 0, &NegativeNumberError{
number: number,
msg: "負の数の平方根は計算できません",
}
}
return math.Sqrt(number), nil
}

func main() {
numbers := []float64{16, -4, 9}

for _, num := range numbers {
result, err := sqrt(num)
if err != nil {
fmt.Println("エラー:", err)
} else {
fmt.Printf("√%f = %f\n", num, result)
}
}

// errors.Newを使ったエラーの作成
err := errors.New("これは新しいエラーです")
fmt.Println("カスタムエラー:", err)
}

解説:

このコード例では、Go言語におけるエラー処理カスタムエラーの作成方法について学びます。Goでは、関数からエラーを返すことで、問題が発生したことを呼び出し元に通知します。

  1. 標準ライブラリのインポート

    import (
    "errors"
    "fmt"
    "math"
    )
    • errorsパッケージ: エラーの作成や操作に使用します。
    • fmtパッケージ: フォーマット済みの入出力を行います。
    • mathパッケージ: 数学的な関数を提供します。
  2. カスタムエラーの定義

    type NegativeNumberError struct {
    number float64
    msg string
    }

    func (e *NegativeNumberError) Error() string {
    return fmt.Sprintf("%s: %f", e.msg, e.number)
    }
    • カスタムエラー型の定義: NegativeNumberErrorという構造体を定義します。
    • Error()メソッドの実装: エラー型として機能させるために、Error()メソッドを実装します。これにより、errorインターフェースを満たします。
    • エラーメッセージのフォーマット: fmt.Sprintfを使って、エラーメッセージを整形します。
  3. 平方根を計算する関数

    func sqrt(number float64) (float64, error) {
    if number < 0 {
    return 0, &NegativeNumberError{
    number: number,
    msg: "負の数の平方根は計算できません",
    }
    }
    return math.Sqrt(number), nil
    }
    • 引数と戻り値: float64型の数値を受け取り、結果とエラーを返します。
    • エラーチェック: 入力が負の数の場合、カスタムエラーを生成して返します。
    • 正常時の戻り値: math.Sqrt関数で平方根を計算し、エラーはnilを返します。
  4. main関数でのエラー処理

    func main() {
    numbers := []float64{16, -4, 9}

    for _, num := range numbers {
    result, err := sqrt(num)
    if err != nil {
    fmt.Println("エラー:", err)
    } else {
    fmt.Printf("√%f = %f\n", num, result)
    }
    }

    // errors.Newを使ったエラーの作成
    err := errors.New("これは新しいエラーです")
    fmt.Println("カスタムエラー:", err)
    }
    • 数値のスライス: 複数の数値を持つスライスを定義します。
    • ループ処理: 各数値に対してsqrt関数を呼び出します。
    • エラーチェック: errnilかどうかをチェックし、エラーがあればメッセージを表示します。
    • エラーの作成: errors.New関数を使って、新しいエラーを作成します。

ポイント解説:

  • エラーインターフェース

    • 定義: type error interface { Error() string }
    • エラー型: Error()メソッドを実装することで、カスタムエラー型を作成できます。
  • エラーの生成

    • errors.New関数: シンプルなエラーメッセージを持つエラーを生成します。

      err := errors.New("エラーメッセージ")
    • fmt.Errorf関数: フォーマット済みのエラーメッセージを生成します。

      err := fmt.Errorf("コード%d: %s", 404, "ページが見つかりません")
  • エラーのハンドリング

    • エラーチェック: 関数の戻り値としてエラーを受け取り、nilでない場合に適切な処理を行います。

      result, err := someFunction()
      if err != nil {
      // エラー処理
      }
  • カスタムエラー

    • 詳細なエラー情報: カスタムエラー型を使うことで、追加の情報をエラーに含めることができます。

    • 型アサーション: カスタムエラー型をチェックし、特定のエラーに対する処理を行えます。

      if err != nil {
      if negativeErr, ok := err.(*NegativeNumberError); ok {
      // NegativeNumberErrorに対する特別な処理
      } else {
      // その他のエラー処理
      }
      }

実行方法:

  1. 上記のコードをerror_handling.goというファイル名で保存します。

  2. ターミナルで保存したディレクトリに移動します。

  3. 以下のコマンドを実行します:

    go run error_handling.go
  4. 以下の出力が表示されます:

    √16.000000 = 4.000000
    エラー: 負の数の平方根は計算できません: -4.000000
    √9.000000 = 3.000000
    カスタムエラー: これは新しいエラーです

まとめ:

この例では、Go言語におけるエラー処理とカスタムエラーの作成方法について学びました。エラーはerrorインターフェースを実装することで作成でき、関数の戻り値としてエラーを返すことでエラー状況を呼び出し元に伝えます。カスタムエラーを使うことで、より詳細なエラー情報を提供し、エラーの種類に応じた処理を行うことが可能になります。次のコード例では、deferpanicrecoverによるパニック処理とリカバリについて詳しく見ていきます。

コード例 14: deferpanicrecoverによるパニック処理とリカバリ

package main

import (
"fmt"
)

func main() {
fmt.Println("プログラム開始")

// パニックの発生とリカバリ
defer func() {
if r := recover(); r != nil {
fmt.Println("パニックを検知しました:", r)
}
}()

fmt.Println("安全な計算を実行します")
result := safeDivide(10, 2)
fmt.Println("10 ÷ 2 =", result)

fmt.Println("危険な計算を実行します")
result = safeDivide(10, 0) // ここでパニックが発生します
fmt.Println("10 ÷ 0 =", result)

fmt.Println("プログラム終了") // この行は実行されません
}

// 安全な除算を行う関数
func safeDivide(a, b int) int {
if b == 0 {
panic("ゼロでの除算は許可されていません")
}
return a / b
}

解説:

このコード例では、Go言語における**deferpanicrecover**を使ったパニック処理とリカバリについて学びます。これらはエラー処理の一環として、予期せぬエラー(パニック)が発生した際の挙動を制御するために使用されます。

  1. deferの使用

    defer func() {
    if r := recover(); r != nil {
    fmt.Println("パニックを検知しました:", r)
    }
    }()
    • deferキーワード: 関数の実行を遅延させ、直近のreturnまたはパニックが発生した後に実行されます。
    • 匿名関数の定義と実行: func() { ... }()の形式で匿名関数を定義し、その場で実行します。
    • recover関数: パニックが発生した場合に、その情報を取得し、パニック状態からリカバリします。
    • パニックの検知: if r := recover(); r != nilでパニックが発生したかをチェックします。
  2. panicの発生

    panic("ゼロでの除算は許可されていません")
    • panic関数: 実行を即座に中断し、現在の関数の実行を停止します。
    • エラーメッセージ: panicに渡した引数がエラーメッセージとして表示されます。
    • スタックトレース: パニックが発生すると、スタックトレースが出力されます。
  3. recoverによるリカバリ

    • パニック状態の解除: recover関数を使って、パニック状態を解除し、プログラムの異常終了を防ぎます。
    • 取得したパニック情報: recoverpanicで渡された値を返します。
  4. safeDivide関数

    func safeDivide(a, b int) int {
    if b == 0 {
    panic("ゼロでの除算は許可されていません")
    }
    return a / b
    }
    • 引数のチェック: b0の場合にpanicを発生させます。
    • 正常時の処理: a / bを計算して結果を返します。
  5. プログラムの流れ

    • プログラム開始
    • deferによるリカバリのセットアップ
    • 安全な計算の実行: safeDivide(10, 2)は正常に動作します。
    • 危険な計算の実行: safeDivide(10, 0)panicが発生します。
    • deferによるパニックのキャッチ: パニックが発生すると、deferで設定した関数が実行されます。
    • プログラム終了のメッセージ: パニックがリカバリされても、パニックが発生した関数以降のコードは実行されません。

ポイント解説:

  • deferの特徴

    • 実行タイミング: deferに指定した関数は、現在の関数が終了する直前に実行されます。
    • 複数のdefer: 複数のdeferがある場合、後に定義されたものから先に実行されます(LIFO順)。
    • リソースの解放: ファイルのクローズやロックの解放など、後処理に使われます。
  • panicの特徴

    • 異常終了: panicが発生すると、現在の関数が即座に終了し、呼び出し元にパニックが伝播します。
    • スタックの巻き戻し: パニックが伝播する際、各関数で定義されたdeferが実行されます。
    • 使いどころ: 深刻なエラーが発生し、プログラムを続行できない場合に使用します。
  • recoverの特徴

    • パニックの捕捉: recoverdefer内の関数でのみ効果を発揮します。
    • プログラムの継続: recoverでパニックを捕捉すると、パニック状態が解除され、プログラムの異常終了を防ぎます。
    • 戻り値: recoverpanicで渡された値を返します。パニックが発生していない場合はnilを返します。
  • 注意点

    • パニック後のコード実行: パニックが発生した関数内の、パニック後のコードは実行されません。
    • リカバリ後の挙動: パニックをリカバリしても、パニックが発生した箇所以降のコードは実行されないため、適切な処理が必要です。

実行方法:

  1. 上記のコードをpanic_recover.goというファイル名で保存します。

  2. ターミナルで保存したディレクトリに移動します。

  3. 以下のコマンドを実行します:

    go run panic_recover.go
  4. 以下の出力が表示されます:

    プログラム開始
    安全な計算を実行します
    10 ÷ 2 = 5
    危険な計算を実行します
    パニックを検知しました: ゼロでの除算は許可されていません
    • : "プログラム終了"のメッセージは表示されません。パニックが発生した関数内の、パニック後のコードは実行されないためです。

まとめ:

この例では、Go言語におけるdeferpanicrecoverを使ったパニック処理とリカバリの方法について学びました。deferは関数の終了時に実行されるコードを登録し、panicは深刻なエラーを発生させ、recoverはパニック状態を回復させるために使用します。これらを適切に組み合わせることで、予期せぬエラーが発生した際にもプログラムの異常終了を防ぎ、リソースの解放やログの記録などの後処理を行うことができます。

次のコード例では、ゴルーチンを使った並行処理の基本について詳しく見ていきます。

コード例 15: ゴルーチンによる並行処理の基本

package main

import (
"fmt"
"time"
)

func main() {
// 通常の関数呼び出し
say("同期的な呼び出し")

// ゴルーチンによる並行処理
go say("ゴルーチン1")
go say("ゴルーチン2")

// 無名関数を使ったゴルーチン
go func(message string) {
for i := 0; i < 3; i++ {
fmt.Println(message, i)
time.Sleep(100 * time.Millisecond)
}
}("無名関数のゴルーチン")

// ゴルーチンが完了するのを待つための時間待ち
time.Sleep(1 * time.Second)
fmt.Println("メイン関数終了")
}

func say(message string) {
for i := 0; i < 5; i++ {
fmt.Println(message, i)
time.Sleep(100 * time.Millisecond)
}
}

解説:

このコード例では、Go言語のゴルーチンを使った並行処理の基本について学びます。ゴルーチンは、Goの並行処理を実現する軽量なスレッドのようなものです。ゴルーチンを使用することで、複数の処理を同時に実行できます。

  1. 通常の関数呼び出し

    say("同期的な呼び出し")
    • 同期処理: この関数呼び出しは、say関数が完了するまで次のコードは実行されません。
    • 出力: "同期的な呼び出し"と数字が順番に出力されます。
  2. ゴルーチンによる並行処理

    go say("ゴルーチン1")
    go say("ゴルーチン2")
    • goキーワード: 関数呼び出しの前にgoを付けることで、その関数を新しいゴルーチンとして実行します。
    • 非同期処理: ゴルーチンは非同期に実行され、メインのプログラムはすぐに次のコードに進みます。
  3. 無名関数を使ったゴルーチン

    go func(message string) {
    for i := 0; i < 3; i++ {
    fmt.Println(message, i)
    time.Sleep(100 * time.Millisecond)
    }
    }("無名関数のゴルーチン")
    • 無名関数: 関数名を持たない関数をその場で定義します。
    • 即時実行: 定義と同時に引数を渡して実行します。
    • ゴルーチン化: goキーワードを使って、この無名関数をゴルーチンとして実行します。
  4. ゴルーチンが完了するのを待つ

    time.Sleep(1 * time.Second)
    • time.Sleep関数: 指定した時間だけプログラムを一時停止します。
    • 理由: メイン関数が終了すると、他のゴルーチンも強制的に終了するため、ゴルーチンが完了するまで待機します。
  5. say関数の定義

    func say(message string) {
    for i := 0; i < 5; i++ {
    fmt.Println(message, i)
    time.Sleep(100 * time.Millisecond)
    }
    }
    • 繰り返し処理: 引数で受け取ったメッセージとカウンタを出力します。
    • ウェイト: time.Sleepで100ミリ秒の待機を入れることで、出力のタイミングを調整します。

ポイント解説:

  • ゴルーチン

    • 軽量スレッド: ゴルーチンは非常に軽量で、数万のゴルーチンを同時に実行することも可能です。
    • 並行処理: goキーワードを使って関数をゴルーチンとして実行すると、並行して処理が進みます。
  • ゴルーチンの終了

    • メイン関数の終了: メイン関数が終了すると、プログラム全体が終了します。実行中のゴルーチンも強制的に終了します。
    • 同期の必要性: ゴルーチンが完了するのを待つために、適切な同期処理が必要です。
  • time.Sleepによる同期

    • この例では簡易的にtime.Sleepを使って待機していますが、実際のアプリケーションではチャネルや**sync.WaitGroup**を使って同期を取るべきです。
  • 無名関数とゴルーチン

    • 無名関数を使うことで、簡潔にゴルーチンを定義できます。
    • 引数を渡すことも可能です。

実行方法:

  1. 上記のコードをgoroutines.goというファイル名で保存します。

  2. ターミナルで保存したディレクトリに移動します。

  3. 以下のコマンドを実行します:

    go run goroutines.go
  4. 以下のような出力が表示されます(出力順序は実行ごとに異なる場合があります):

    同期的な呼び出し 0
    同期的な呼び出し 1
    同期的な呼び出し 2
    同期的な呼び出し 3
    同期的な呼び出し 4
    ゴルーチン1 0
    ゴルーチン2 0
    無名関数のゴルーチン 0
    ゴルーチン1 1
    ゴルーチン2 1
    無名関数のゴルーチン 1
    ゴルーチン1 2
    ゴルーチン2 2
    無名関数のゴルーチン 2
    ゴルーチン1 3
    ゴルーチン2 3
    ゴルーチン1 4
    ゴルーチン2 4
    メイン関数終了
    • : ゴルーチンの実行順序は保証されません。同時に実行されるため、出力が混在します。

まとめ:

この例では、Go言語のゴルーチンを使った並行処理の基本を学びました。goキーワードを使うことで、関数をゴルーチンとして実行し、複数の処理を同時に進めることができます。また、無名関数を使ってゴルーチンを定義する方法も紹介しました。ゴルーチンは軽量で強力な並行処理の手段ですが、適切な同期を取らないと予期しない動作をする可能性があります。次のコード例では、チャネルを使ったゴルーチン間の通信について詳しく見ていきます。

コード例 16: チャネルによるゴルーチン間の通信

package main

import (
"fmt"
"time"
)

func main() {
// 文字列型のチャネルを作成
messages := make(chan string)

// ゴルーチンでメッセージを送信
go func() {
time.Sleep(1 * time.Second)
messages <- "こんにちは、ゴルーチンからのメッセージです"
}()

// メインゴルーチンでメッセージを受信
msg := <-messages
fmt.Println("受信したメッセージ:", msg)
}

解説:

このコード例では、Go言語におけるチャネルを使ったゴルーチン間の通信について学びます。チャネルはゴルーチン間でデータを送受信するための同期的なデータ構造で、ゴルーチン同士の連携に重要な役割を果たします。

  1. チャネルの作成

    messages := make(chan string)
    • make関数: チャネルを作成するために使用します。
    • chanキーワード: チャネルの型を指定します。この場合はstring型のデータを送受信するチャネルです。
    • 無バッファチャネル: バッファサイズを指定しない場合、無バッファチャネルとなり、送信と受信が同期的に行われます。
  2. ゴルーチンでのメッセージ送信

    go func() {
    time.Sleep(1 * time.Second)
    messages <- "こんにちは、ゴルーチンからのメッセージです"
    }()
    • 無名関数のゴルーチン: 無名関数をゴルーチンとして実行します。
    • time.Sleep: メッセージの送信を1秒遅らせます。
    • チャネルへの送信: messages <- "メッセージ"の形式で、チャネルにデータを送信します。
    • 送信のブロック: 受信側がデータを受け取るまで、送信はブロックされます(無バッファチャネルの場合)。
  3. メインゴルーチンでのメッセージ受信

    msg := <-messages
    fmt.Println("受信したメッセージ:", msg)
    • チャネルからの受信: msg := <-messagesの形式で、チャネルからデータを受信します。
    • 受信のブロック: データがチャネルに送信されるまで、受信はブロックされます。
  4. プログラムの流れ

    • チャネルの作成: メインゴルーチンでチャネルを作成します。
    • ゴルーチンの開始: メッセージを送信するゴルーチンを開始します。
    • メッセージの受信: メインゴルーチンでチャネルからのメッセージを受信します。送信されるまでブロックされます。
    • メッセージの表示: 受信したメッセージをコンソールに表示します。

ポイント解説:

  • チャネルの基本

    • 宣言方法: var チャネル名 chan データ型 または チャネル名 := make(chan データ型)
    • 送信: チャネル名 <- 値 の形式でデータを送信します。
    • 受信: 変数 := <-チャネル名 の形式でデータを受信します。
  • 無バッファチャネルとバッファ付きチャネル

    • 無バッファチャネル: 送信と受信が同期的に行われ、送信側と受信側が揃うまでブロックされます。

    • バッファ付きチャネル: make(chan データ型, バッファサイズ)の形式で作成します。バッファに空きがあれば送信側はブロックされずにデータを送信できます。

      messages := make(chan string, 2) // バッファサイズ2のチャネル
  • チャネルの閉鎖

    • close関数: チャネルを閉じて、これ以上の送信を行わないことを示します。

      close(messages)
    • チャネルの受信ループ

      for msg := range messages {
      fmt.Println(msg)
      }
      • チャネルが閉じられるまで、データを受信し続けます。
  • チャネルとゴルーチンの連携

    • チャネルを使うことで、ゴルーチン間で安全かつ同期的にデータをやり取りできます。
    • ゴルーチンの同期やデータ共有の手段として重要です。

実行方法:

  1. 上記のコードをchannels.goというファイル名で保存します。

  2. ターミナルで保存したディレクトリに移動します。

  3. 以下のコマンドを実行します:

    go run channels.go
  4. 以下の出力が表示されます:

    受信したメッセージ: こんにちは、ゴルーチンからのメッセージです

まとめ:

この例では、Go言語のチャネルを使ったゴルーチン間の通信方法について学びました。チャネルを使用することで、ゴルーチン間でデータを安全かつ同期的にやり取りできます。チャネルの基本的な使い方、無バッファチャネルとバッファ付きチャネルの違い、チャネルの送受信方法などを理解しました。

次のコード例では、select文を使ったチャネルの多重処理について詳しく見ていきます。

コード例 17: select文を使ったチャネルの多重処理

package main

import (
"fmt"
"time"
)

func main() {
// 整数型のチャネルを2つ作成
ch1 := make(chan int)
ch2 := make(chan int)

// ゴルーチン1: 500ミリ秒ごとにデータを送信
go func() {
for i := 1; i <= 5; i++ {
time.Sleep(500 * time.Millisecond)
ch1 <- i
}
close(ch1)
}()

// ゴルーチン2: 800ミリ秒ごとにデータを送信
go func() {
for i := 1; i <= 3; i++ {
time.Sleep(800 * time.Millisecond)
ch2 <- i * 10
}
close(ch2)
}()

// `select`文を使って複数のチャネルを待ち受け
for {
select {
case val1, ok1 := <-ch1:
if ok1 {
fmt.Println("ch1から受信:", val1)
} else {
ch1 = nil // チャネルを`nil`に設定してブロック
}
case val2, ok2 := <-ch2:
if ok2 {
fmt.Println("ch2から受信:", val2)
} else {
ch2 = nil
}
default:
if ch1 == nil && ch2 == nil {
fmt.Println("すべてのチャネルが閉じられました")
return
}
// どのチャネルからもデータがない場合の処理
fmt.Println("待機中...")
time.Sleep(300 * time.Millisecond)
}
}
}

解説:

このコード例では、Go言語の**select文**を使って、複数のチャネルを同時に待ち受ける方法について学びます。select文は、複数のチャネル操作を監視し、いずれかのチャネルが準備できたときにその操作を実行します。

  1. 複数のチャネルの作成

    ch1 := make(chan int)
    ch2 := make(chan int)
    • ch1ch2: 整数型のチャネルを2つ作成します。
    • 無バッファチャネル: バッファサイズを指定していないため、無バッファチャネルとなります。
  2. ゴルーチン1の定義

    go func() {
    for i := 1; i <= 5; i++ {
    time.Sleep(500 * time.Millisecond)
    ch1 <- i
    }
    close(ch1)
    }()
    • データ送信: 500ミリ秒ごとにch1にデータを送信します。
    • ループ: iを1から5までインクリメントします。
    • チャネルの閉鎖: データの送信が終わったらclose(ch1)でチャネルを閉じます。
  3. ゴルーチン2の定義

    go func() {
    for i := 1; i <= 3; i++ {
    time.Sleep(800 * time.Millisecond)
    ch2 <- i * 10
    }
    close(ch2)
    }()
    • データ送信: 800ミリ秒ごとにch2にデータを送信します。
    • ループ: iを1から3までインクリメントします。
    • チャネルの閉鎖: データの送信が終わったらclose(ch2)でチャネルを閉じます。
  4. select文を使ったチャネルの待ち受け

    for {
    select {
    case val1, ok1 := <-ch1:
    if ok1 {
    fmt.Println("ch1から受信:", val1)
    } else {
    ch1 = nil // チャネルが閉じられたら`nil`に設定
    }
    case val2, ok2 := <-ch2:
    if ok2 {
    fmt.Println("ch2から受信:", val2)
    } else {
    ch2 = nil
    }
    default:
    if ch1 == nil && ch2 == nil {
    fmt.Println("すべてのチャネルが閉じられました")
    return
    }
    // データがない場合の処理
    fmt.Println("待機中...")
    time.Sleep(300 * time.Millisecond)
    }
    }
    • forループ: 無限ループでselect文を繰り返し実行します。
    • select: 複数のチャネル操作を待ち受けます。
      • case: それぞれのチャネルからの受信操作を定義します。
      • val, ok := <-ch: 受信した値と、チャネルが閉じられていないかを確認します。
      • チャネルの閉鎖確認:
        • okfalseの場合、チャネルは閉じられています。
        • チャネルをnilに設定することで、select文での待ち受けから除外します。
    • default:
      • どのcaseも準備ができていない場合に実行されます。
      • チャネルがすべてnilになったらループを終了します。
      • データがまだ来ていない場合は「待機中...」と表示します。

ポイント解説:

  • select文の基本

    • 構文:

      select {
      case1:
      // 式1が準備できたときの処理
      case2:
      // 式2が準備できたときの処理
      default:
      // どのケースも準備できていないときの処理
      }
    • 動作: 複数のチャネル操作の中から、準備ができているものを選択して実行します。

  • チャネルの閉鎖とnil設定

    • チャネルを閉じる: close(チャネル名)でチャネルを閉じます。
    • 閉じたチャネルの受信: チャネルが閉じられると、受信操作は即座にゼロ値とfalseを返します。
    • nilチャネル:
      • チャネルにnilを代入すると、そのチャネルへの送受信操作はブロックされます。
      • select文でチャネルを動的に有効/無効にする際に便利です。
  • defaultケース

    • 非ブロッキングなselect:
      • defaultケースを定義すると、すべてのcaseがブロックされている場合でも即座にdefaultの処理が実行されます。
      • defaultを定義しない場合、すべてのcaseがブロックされているとselect文自体がブロックされます。
  • タイムアウト処理

    • time.After関数を使って、特定の時間が経過したらタイムアウトするselect文を作成できます。

      select {
      case val := <-ch:
      // チャネルからの受信処理
      case <-time.After(1 * time.Second):
      // タイムアウト時の処理
      }

実行方法:

  1. 上記のコードをselect_statement.goというファイル名で保存します。

  2. ターミナルで保存したディレクトリに移動します。

  3. 以下のコマンドを実行します:

    go run select_statement.go
  4. 以下のような出力が表示されます(タイミングにより出力順が変わる可能性があります):

    待機中...
    ch1から受信: 1
    待機中...
    ch2から受信: 10
    ch1から受信: 2
    待機中...
    ch1から受信: 3
    ch2から受信: 20
    待機中...
    ch1から受信: 4
    待機中...
    ch2から受信: 30
    ch1から受信: 5
    待機中...
    待機中...
    すべてのチャネルが閉じられました

まとめ:

この例では、Go言語のselect文を使って複数のチャネルを同時に待ち受ける方法を学びました。select文を利用することで、ゴルーチン間の通信を効率的に管理できます。チャネルの閉鎖、nilチャネルの扱い、defaultケースによる非ブロッキングな処理など、select文の重要なポイントを理解しました。

次のコード例では、sync.WaitGroupを使ったゴルーチンの同期について詳しく見ていきます。

コード例 18: sync.WaitGroupを使ったゴルーチンの同期

package main

import (
"fmt"
"sync"
"time"
)

func worker(id int, wg *sync.WaitGroup) {
defer wg.Done() // 処理が完了したことを通知

fmt.Printf("ワーカー%d: 開始\n", id)
time.Sleep(time.Duration(id) * time.Second) // ワーカーごとに異なる処理時間
fmt.Printf("ワーカー%d: 終了\n", id)
}

func main() {
var wg sync.WaitGroup

// ゴルーチンを起動
for i := 1; i <= 5; i++ {
wg.Add(1) // ワーカーの数を追加
go worker(i, &wg)
}

wg.Wait() // すべてのワーカーが完了するのを待つ
fmt.Println("すべてのワーカーが終了しました")
}

解説:

このコード例では、Go言語のsync.WaitGroupを使って、複数のゴルーチンの完了を待ち合わせる方法について学びます。WaitGroupは複数のゴルーチンの終了を同期するためのシンプルで効果的な方法です。

  1. sync.WaitGroupのインポートと宣言

    import (
    "fmt"
    "sync"
    "time"
    )

    func main() {
    var wg sync.WaitGroup
    // ...
    }
    • syncパッケージ: 同期プリミティブを提供する標準ライブラリ。
    • WaitGroupの宣言: var wg sync.WaitGroupWaitGroupを宣言します。
  2. ワーカー関数の定義

    func worker(id int, wg *sync.WaitGroup) {
    defer wg.Done() // 処理が完了したことを通知

    fmt.Printf("ワーカー%d: 開始\n", id)
    time.Sleep(time.Duration(id) * time.Second) // ワーカーごとに異なる処理時間
    fmt.Printf("ワーカー%d: 終了\n", id)
    }
    • 引数:
      • id int: ワーカーの識別子。
      • wg *sync.WaitGroup: WaitGroupへのポインタを渡します。
    • defer wg.Done():
      • wg.Done()WaitGroupのカウンタを1減らします。
      • deferを使うことで、関数の終了時に必ず実行されます。
    • ワーカーの処理:
      • 開始と終了のメッセージを表示します。
      • time.Sleepで処理時間をシミュレートします。ワーカーのIDに応じて異なる待機時間を設定しています。
  3. ゴルーチンの起動と同期

    for i := 1; i <= 5; i++ {
    wg.Add(1) // ワーカーの数を追加
    go worker(i, &wg)
    }

    wg.Wait() // すべてのワーカーが完了するのを待つ
    fmt.Println("すべてのワーカーが終了しました")
    • wg.Add(1):
      • WaitGroupのカウンタを1増やします。
      • ゴルーチンを起動する前に呼び出す必要があります。
    • ゴルーチンの起動:
      • go worker(i, &wg)でワーカー関数をゴルーチンとして実行します。
      • wgへのポインタを渡します。
    • wg.Wait():
      • WaitGroupのカウンタが0になるまでブロックします。
      • すべてのワーカーが完了するのを待機します。
  4. プログラムの流れ

    • ワーカーの起動:
      • 5つのワーカーがそれぞれ別のゴルーチンとして並行して実行されます。
    • ワーカーの処理:
      • 各ワーカーは開始メッセージを表示し、IDに応じた時間だけ待機します。
      • 待機後、終了メッセージを表示し、wg.Done()で完了を通知します。
    • メインゴルーチンの待機:
      • wg.Wait()で、すべてのワーカーが完了するまで待機します。
    • プログラムの終了:
      • すべてのワーカーが終了すると、「すべてのワーカーが終了しました」と表示されます。

ポイント解説:

  • sync.WaitGroupの基本

    • カウンタの管理:
      • wg.Add(n): カウンタをn増やす。通常はゴルーチンを起動する前に呼び出します。
      • wg.Done(): カウンタを1減らす。ゴルーチンの処理が完了したときに呼び出します。
      • wg.Wait(): カウンタが0になるまでブロックします。
    • 注意点:
      • wg.Done()wg.Add()の呼び出し回数が一致しないと、Wait()が永遠にブロックされたり、負のカウンタになってパニックが発生する可能性があります。
  • deferの活用

    • deferによる確実な処理:
      • defer wg.Done()を使うことで、関数がどのように終了してもwg.Done()が確実に呼び出されます。
  • ゴルーチン間の同期

    • 共有リソースへのアクセス:
      • この例では共有リソースはありませんが、共有データにアクセスする際はsync.Mutexなどの同期プリミティブが必要です。
  • ワーカーの処理時間のシミュレーション

    • time.Sleep:
      • ワーカーごとに異なる処理時間を設定することで、並行処理の動作を観察しやすくしています。

実行方法:

  1. 上記のコードをwaitgroup_example.goというファイル名で保存します。

  2. ターミナルで保存したディレクトリに移動します。

  3. 以下のコマンドを実行します:

    go run waitgroup_example.go
  4. 以下のような出力が表示されます(実行するたびにタイミングが異なる場合があります):

    ワーカー1: 開始
    ワーカー2: 開始
    ワーカー3: 開始
    ワーカー4: 開始
    ワーカー5: 開始
    ワーカー1: 終了
    ワーカー2: 終了
    ワーカー3: 終了
    ワーカー4: 終了
    ワーカー5: 終了
    すべてのワーカーが終了しました
    • : 各ワーカーの開始順序は同じですが、終了順序は処理時間によって異なります。

まとめ:

この例では、sync.WaitGroupを使って複数のゴルーチンの完了を待ち合わせる方法を学びました。WaitGroupは、並行処理を行う際にゴルーチン間の同期を取るための基本的な手段です。wg.Add()wg.Done()wg.Wait()の使い方を理解し、deferを活用して確実に完了を通知する方法も紹介しました。

次のコード例では、sync.Mutexを使った排他制御について詳しく見ていきます。

コード例 19: sync.Mutexを使った排他制御

package main

import (
"fmt"
"sync"
"time"
)

type Counter struct {
mu sync.Mutex
count int
}

func (c *Counter) Increment() {
c.mu.Lock() // ロックを取得
defer c.mu.Unlock() // 関数終了時にロックを解放

c.count++
fmt.Println("カウント:", c.count)
}

func main() {
var wg sync.WaitGroup
counter := Counter{}

// 5つのゴルーチンを起動してカウンタをインクリメント
for i := 0; i < 5; i++ {
wg.Add(1)
go func(id int) {
defer wg.Done()
for j := 0; j < 3; j++ {
counter.Increment()
time.Sleep(100 * time.Millisecond)
}
}(i)
}

wg.Wait()
fmt.Println("最終的なカウント値:", counter.count)
}

解説:

このコード例では、Go言語のsync.Mutexを使って、複数のゴルーチンから共有データに安全にアクセスする方法について学びます。Mutex(ミューテックス)は、排他制御のための同期プリミティブで、共有リソースへの同時アクセスを防ぎます。

  1. Counter構造体の定義

    type Counter struct {
    mu sync.Mutex
    count int
    }
    • フィールド
      • mu sync.Mutex: ミューテックスを保持します。
      • count int: カウントする整数値です。
  2. Incrementメソッドの定義

    func (c *Counter) Increment() {
    c.mu.Lock() // ロックを取得
    defer c.mu.Unlock() // 関数終了時にロックを解放

    c.count++
    fmt.Println("カウント:", c.count)
    }
    • ロックの取得と解放
      • c.mu.Lock(): ミューテックスをロックし、他のゴルーチンがこのセクションに入らないようにします。
      • defer c.mu.Unlock(): 関数が終了する際にロックを解放します。
    • カウントのインクリメント
      • 共有変数c.countを安全にインクリメントします。
  3. メイン関数でのゴルーチンの起動

    func main() {
    var wg sync.WaitGroup
    counter := Counter{}

    // 5つのゴルーチンを起動してカウンタをインクリメント
    for i := 0; i < 5; i++ {
    wg.Add(1)
    go func(id int) {
    defer wg.Done()
    for j := 0; j < 3; j++ {
    counter.Increment()
    time.Sleep(100 * time.Millisecond)
    }
    }(i)
    }

    wg.Wait()
    fmt.Println("最終的なカウント値:", counter.count)
    }
    • WaitGroupの使用
      • wg.Add(1): ゴルーチンを起動する前にカウンタを増やします。
      • wg.Done(): ゴルーチンが終了したことを通知します。
      • wg.Wait(): すべてのゴルーチンが終了するまで待機します。
    • ゴルーチンの処理
      • 各ゴルーチンはcounter.Increment()を3回呼び出します。
      • time.Sleepで少し待機することで、他のゴルーチンとの競合を発生させます。
  4. プログラムの流れ

    • カウンタの初期化: Counter構造体のインスタンスcounterを作成します。
    • ゴルーチンの起動: 5つのゴルーチンを起動し、それぞれがcounter.Increment()を3回呼び出します。
    • 排他制御の実現: Incrementメソッド内でミューテックスを使って、countのインクリメントを安全に行います。
    • 最終結果の表示: すべてのゴルーチンが終了した後、counter.countの最終的な値を表示します。

ポイント解説:

  • sync.Mutexの基本

    • ロックの取得と解放
      • mu.Lock(): ミューテックスをロックします。すでにロックされている場合、アンロックされるまでブロックされます。
      • mu.Unlock(): ミューテックスをアンロックします。他のゴルーチンがロックを取得できるようになります。
    • 注意点
      • デッドロックの回避: Lock()したら必ず対応するUnlock()を呼び出す必要があります。deferを使うと安全です。
      • 再帰的なロック不可: Goのミューテックスは再帰的なロックをサポートしていません。同じゴルーチンが同じミューテックスを複数回ロックするとデッドロックになります。
  • 共有データの保護

    • 競合状態の防止: 複数のゴルーチンが同時に共有データを読み書きすると、データの一貫性が保てなくなります。ミューテックスを使ってこれを防ぎます。
    • クリティカルセクション: ロックの間に行われる処理は、他のゴルーチンから保護されています。
  • deferの活用

    • ロックの解放を確実にする: deferを使うことで、関数がどのように終了しても(正常終了やパニックなど)、必ずUnlock()が呼ばれます。
  • ゴルーチンの同期

    • sync.WaitGroupの再利用: 前回のコード例と同様に、WaitGroupを使ってゴルーチンの終了を待ち合わせます。

実行方法:

  1. 上記のコードをmutex_example.goというファイル名で保存します。

  2. ターミナルで保存したディレクトリに移動します。

  3. 以下のコマンドを実行します:

    go run mutex_example.go
  4. 以下のような出力が表示されます(実行ごとに順序が異なる場合があります):

    カウント: 1
    カウント: 2
    カウント: 3
    カウント: 4
    カウント: 5
    カウント: 6
    カウント: 7
    カウント: 8
    カウント: 9
    カウント: 10
    カウント: 11
    カウント: 12
    カウント: 13
    カウント: 14
    カウント: 15
    最終的なカウント値: 15
    • : counter.countの最終的な値は15になります(5つのゴルーチン × 3回のインクリメント)。

まとめ:

この例では、Go言語のsync.Mutexを使って、複数のゴルーチンから共有データに安全にアクセスする方法を学びました。ミューテックスを使用することで、同時アクセスによるデータ競合を防ぎ、一貫性を保つことができます。ロックの取得と解放、deferによる安全なロック解放、共有データの保護など、並行プログラミングで重要な概念を理解しました。

次のコード例では、カスタムパッケージの作成とモジュール管理について詳しく見ていきます。

コード例 20: カスタムパッケージの作成とモジュール管理

ファイル構成:

myapp/
├── go.mod
├── main.go
└── mathutil/
└── mathutil.go

main.go

package main

import (
"fmt"
"myapp/mathutil"
)

func main() {
a := 15
b := 27

sum := mathutil.Add(a, b)
diff := mathutil.Subtract(a, b)
fmt.Printf("%d + %d = %d\n", a, b, sum)
fmt.Printf("%d - %d = %d\n", a, b, diff)
}

mathutil/mathutil.go

package mathutil

// Addは2つの整数を加算します
func Add(a, b int) int {
return a + b
}

// Subtractは2つの整数を減算します
func Subtract(a, b int) int {
return a - b
}

go.mod

module myapp

go 1.20

解説:

このコード例では、Go言語におけるカスタムパッケージの作成モジュール管理について学びます。カスタムパッケージを作成することで、コードの再利用性を高め、プロジェクトをより整理された形で構築できます。また、Go Modulesを使って依存関係を管理します。

  1. プロジェクトの初期化

    • ディレクトリ構成

      • プロジェクトルートはmyappディレクトリです。
      • main.goがエントリポイントで、mathutilディレクトリ内にカスタムパッケージを作成します。
    • go mod initコマンド

      go mod init myapp
      • Go Modulesを初期化し、go.modファイルを生成します。
      • module myappと定義され、パッケージのインポートパスのベースとなります。
  2. mathutilパッケージの作成

    • mathutil/mathutil.go
      package mathutil

      // Addは2つの整数を加算します
      func Add(a, b int) int {
      return a + b
      }

      // Subtractは2つの整数を減算します
      func Subtract(a, b int) int {
      return a - b
      }
      • パッケージ宣言: ファイルの先頭でpackage mathutilと宣言します。これにより、このファイルがmathutilパッケージに属することを示します。
      • エクスポート関数: AddSubtract関数は大文字で始まるため、他のパッケージからアクセス可能です。
      • 関数の定義: それぞれ2つの整数を受け取り、加算および減算の結果を返します。
  3. main.goでのパッケージ利用

    • インポート文

      import (
      "fmt"
      "myapp/mathutil"
      )
      • 標準パッケージのインポート: fmtパッケージをインポートします。
      • カスタムパッケージのインポート: myapp/mathutilをインポートします。これはgo.modで定義したモジュール名myappに基づきます。
    • パッケージ内の関数の使用

      sum := mathutil.Add(a, b)
      diff := mathutil.Subtract(a, b)
      • パッケージ名を指定: mathutil.Addのように、パッケージ名をプレフィックスとして関数を呼び出します。
      • 結果の表示: fmt.Printfを使って計算結果を表示します。
  4. Go Modulesによるモジュール管理

    • go.modファイル
      module myapp

      go 1.20
      • モジュール名の指定: module myappで、このプロジェクトのモジュール名をmyappと定義します。
      • Goのバージョン: go 1.20は、このモジュールがGo 1.20で動作することを示します。

ポイント解説:

  • カスタムパッケージの作成

    • パッケージのディレクトリ構造

      • 各パッケージは専用のディレクトリに配置します。
      • パッケージ名とディレクトリ名は一致させるのが一般的です。
    • パッケージの宣言

      • 各ソースファイルの先頭でpackage パッケージ名を宣言します。
    • エクスポートと非エクスポート

      • エクスポートされる識別子: 大文字で始まる関数や変数はパッケージ外からアクセス可能です。
      • 非エクスポートされる識別子: 小文字で始まるものはパッケージ内でのみ有効です。
  • Go Modulesの活用

    • 依存関係の管理

      • Go Modulesを使うことで、外部パッケージのバージョン管理や依存関係の解決が容易になります。
    • モジュール名の指定

      • モジュール名は、インポートパスのベースとなります。
      • 一般的にはリポジトリのURLを使用しますが、ローカル開発では任意の名前を付けられます。
    • go.modgo.sum

      • go.modにはモジュールの情報と依存関係が記述されます。
      • go.sumには依存関係の正確なバージョンとチェックサムが記録されます。
  • パッケージのインポート

    • 相対パスのインポートは不可

      • Goでは相対パスでのパッケージインポートは推奨されておらず、モジュール名からの絶対パスでインポートします。
    • 循環参照の禁止

      • パッケージ間で循環参照が発生しないように設計する必要があります。

実行方法:

  1. プロジェクトのディレクトリに移動

    cd myapp
  2. Go Modulesの初期化(既に行っている場合は不要)

    go mod init myapp
  3. プログラムの実行

    go run main.go
    • 以下のような出力が得られます:
      15 + 27 = 42
      15 - 27 = -12

まとめ:

この例では、Go言語におけるカスタムパッケージの作成方法とGo Modulesを使ったモジュール管理について学びました。カスタムパッケージを作成することで、コードをモジュール化し、再利用性やメンテナンス性を向上させることができます。また、Go Modulesを活用することで、依存関係の管理が容易になり、プロジェクトのビルドや共有がスムーズになります。


総括:

20のコード例を通じて、Go言語の基本的な文法や操作を学びました。これらの知識を基に、Goを使ったさまざまなアプリケーション開発に挑戦してみてください。Goのシンプルさと強力な機能を活用して、効率的なプログラムを作成できることでしょう。