Golang 入門

Go 循環(ループ)ステートメント

for ループは、Go言語において極めて基礎的かつ核心的な制御フロー(Control Flow)ステートメントであり、特定のコードブロックを繰り返し実行するために使用されます。

Go言語のループ設計は非常に精緻でミニマリストです。ループ構造は for 一種類しか存在しません(whiledo-while はありません)。しかし、そのシンプルさに惑わされてはいけません。この for ループは極めて柔軟で、単純な数値のインクリメントから複雑な条件ループまで、あらゆるシナリオに対応可能です。

本章では、for ループの様々な形態、機能、および実際の開発におけるアプリケーションについて深く掘り下げていきます。

1. for ループの解剖学的構造

Go言語の標準的な for ループの基礎構造は、セミコロン ; で区切られた3つのオプションのコンポーネントで構成されます。

for 初期化ステートメント; 条件判断; 後置ステートメント {
    // 繰り返し実行されるコード
}

これら3つのコンポーネントを一つずつ分解してみましょう:

  • 初期化ステートメント (Initialization): ループが始まる前に一度だけ実行されます。通常、ループカウンター変数の宣言と初期化に使用されます(例:i := 0)。
  • 条件判断 (Condition): 各イテレーション(繰り返し)の開始前に評価されるブール式です。条件が true であればループは継続し、false であればループは直ちに終了します。
  • 後置ステートメント (Post): 各イテレーションの終了直後に実行されます。通常、ループカウンターのインクリメントまたはデクリメント操作に使用されます(例:i++)。

1.1 基礎的な for ループの例

package main

import "fmt"

func main() {
    // 初期化: i := 0; 条件: i < 5; 後置処理: i++
    for i := 0; i < 5; i++ {
        fmt.Println("現在のイテレーション回数:", i)
    }
    fmt.Println("ループ終了")
}

この例では:

  1. i := 0 でループカウンター i を 0 に初期化します。
  2. i < 5 がチェック条件となり、i が 5 未満である限りループは走り続けます。
  3. i++ は、波括弧内のコードが実行された後、i の値を 1 加算します。

最終的に、このコードは 0 から 4 までのイテレーション情報をプリントし、最後に「ループ終了」と表示します。

2. 柔軟な構造コンポーネントの省略

Go言語の for ループの魅力はその柔軟性にあります。これら3つのコンポーネントはいずれも省略可能であり、すべてを省略することもできます。

2.1 初期化と後置ステートメントの省略 (while ループの代替)

初期化と後置ステートメントを省略すると、for ループは他言語における while ループと同じ挙動になります。

package main

import "fmt"

func main() {
    i := 0
    for i < 5 { // 条件判断のみを保持
        fmt.Println("現在のイテレーション回数:", i)
        i++ // ループ体内部で手動で i をインクリメント
    }
    fmt.Println("ループ終了")
}

このシナリオでは、初期化 (i := 0) がループの外部に移動され、後置処理 (i++) がループ体(Loop Body)の内部に配置されています。

2.2 無限ループ (The Infinite Loop)

条件判断さえも省略すると、無限ループ(死のループ)を作成できます。

package main

import "fmt"

func main() {
    for { // 初期化、条件、後置処理すべてなし
        fmt.Println("この行は永久にプリントされ続けます(強制中断しない限り)")
    }
}

このループは、ループ内部で break ステートメントを使用するか、プログラムを強制終了しない限り、無限に実行され続けます。無限ループは、サーバーのバックグラウンドリスニングタスクなどを記述する際に非常に一般的です。

3. for...range ループ:コレクション走査の切り札

Goは、配列(Array)、スライス(Slice)、文字列(String)、マップ(Map)などのコレクションを走査するための特殊な for ループ形態を提供しています:for...range ループです。これは日常の開発で最も頻繁に使用されるループ方式です。

for index, value := range collection {
    // コレクション内の各要素に対して実行
}
  • index: コレクション内の現在の要素のインデックス(0から始まる位置番号)。
  • value: コレクション内の現在の要素の値。
  • collection: 走査したい配列、スライス、文字列、またはマップ。

3.1 スライス (Slice) の走査

package main

import "fmt"

func main() {
    numbers := []int{10, 20, 30, 40, 50}
    
    for index, value := range numbers {
        fmt.Printf("インデックス: %d, 値: %d\n", index, value)
    }
}

このループは、numbers スライス内の各要素を順番に取り出し、その位置インデックスと具体的な数値をプリントします。

3.2 インデックスまたは値の無視

ループ内で「値」だけに興味があり「インデックス」が必要ない場合(またはその逆)、ブランク識別子 _ (アンダースコア) を使用して無視することができます。これにより、Go コンパイラが「未使用の変数」としてエラーを出すのを防げます。

package main

import "fmt"

func main() {
    numbers := []int{10, 20, 30, 40, 50}
    
    // インデックスを無視 (値のみ取得)
    for _, value := range numbers {
        fmt.Println("値のみ:", value)
    }
    
    // 値を無視 (インデックスのみ取得)
    for index := range numbers {
        fmt.Println("インデックスのみ:", index)
    }
}

3.3 文字列の走査 (日本語と Unicode の処理)

for...range を使用して文字列を走査する場合、Go は非常にスマートに、純粋なバイト単位ではなく Rune (Unicode コードポイント) 単位で走査します。これは、日本語などを含む文字列を処理する際に極めて重要です。

package main

import "fmt"

func main() {
    message := "Hello, 世界" // ASCII 文字と日本語文字の混在
    
    for index, runeValue := range message {
        fmt.Printf("バイトインデックス: %d, 文字: %c, Unicodeエンコーディング: %U\n", index, runeValue, runeValue)
    }
}

Go の文字列は UTF-8 エンコーディングされており、日本語の一文字は通常 3 バイトを占有します。for...range ループは文字の境界を正しく識別し、各イテレーションで完全な一文字(rune)を返すため、多言語テキストを安全に処理できます。

4. 実戦演習と練習

いくつかの実戦的な例を通じて、for ループのスキルを定着させましょう。

4.1 例 1:数値の合計を計算する

スライス内のすべての数値の合計を for ループを使用して計算するプログラムです。

package main

import "fmt"

func main() {
    numbers := []int{1, 2, 3, 4, 5}
    sum := 0
    
    for _, number := range numbers {
        sum += number
    }
    fmt.Println("合計:", sum) // 出力: 15
}

4.2 例 2:最大値を見つける

スライスの中から最大の値を探し出すプログラムです。

package main

import "fmt"

func main() {
    numbers := []int{5, 2, 9, 1, 5, 6}
    largest := numbers[0] // 最初の要素を最大値と仮定
    
    for _, number := range numbers {
        if number > largest {
            largest = number // より大きな値が見つかれば、記録を更新
        }
    }
    fmt.Println("最大値:", largest) // 出力: 9
}

4.3 チャレンジ 1:文字列の反転

for ループを利用して文字列を反転させる関数です(一つの for ループ内で i, j 二つの変数を同時に宣言・更新する高度な記述に注目してください)。

package main

import "fmt"

func reverseString(s string) string {
    runes := []rune(s) // 文字を正しく処理するため、rune スライスに変換
    
    // 二つのポインタを初期化:i は左から右へ、j は右から左へ。i < 長さの半分まで繰り返す。
    for i, j := 0, len(runes)-1; i < len(runes)/2; i, j = i+1, j-1 {
        runes[i], runes[j] = runes[j], runes[i] // 先頭と末尾の対応する文字を入れ替え
    }
    return string(runes) // 文字列型に戻す
}

func main() {
    original := "Go is awesome!"
    reversed := reverseString(original)
    fmt.Println("元の文字列:", original)
    fmt.Println("反転後:", reversed)
}

4.4 チャレンジ 2:母音の数をカウントする

文字列に含まれる母音 (a, e, i, o, u) の数を for ループでカウントするプログラムです。

package main

import (
	"fmt"
	"strings"
)

func countVowels(s string) int {
	vowels := "aeiouAEIOU"
	count := 0
	
	for _, char := range s {
	    // 現在の文字が母音文字列に含まれているか判断
		if strings.ContainsRune(vowels, char) {
			count++
		}
	}
	return count
}

func main() {
	text := "This is a test string with some vowels."
	vowelCount := countVowels(text)
	fmt.Println("母音の数:", vowelCount)
}