Go言語入門:効果的なGo -制御構造:For文-

スポンサーリンク
Go言語入門:効果的なGo -制御構造:For文- ノウハウ
Go言語入門:効果的なGo -制御構造:For文-
この記事は約11分で読めます。
よっしー
よっしー

こんにちは。よっしーです(^^)

本日は、Go言語を効果的に使うためのガイドラインについて解説しています。

スポンサーリンク

背景

Go言語を学び始めて、より良いコードを書きたいと思い、Go言語の公式ドキュメント「Effective Go」を知りました。これは、いわば「Goらしいコードの書き方指南書」になります。単に動くコードではなく、効率的で保守性の高いコードを書くためのベストプラクティスが詰まっているので、これを読んだ時の内容を備忘として残しました。

Go言語の制御構造

Go言語の制御構造はC言語のものと関連していますが、重要な違いがあります。

C言語との主な違い

  • dowhileループはありません。わずかに一般化されたforのみ
  • switchはより柔軟
  • ifswitchforのような初期化文をオプションで受け入れる
  • breakcontinue文は、何を中断または継続するかを識別するためのオプションのラベルを取る
  • 新しい制御構造:型スイッチと多方向通信マルチプレクサであるselect

構文の違い

  • 括弧はありません
  • 本体は常に波括弧で区切られなければなりません

Go言語のforループ

Go言語のforループはC言語のものと似ていますが、同じではありません。 forwhileを統一し、do-whileはありません。3つの形式があり、そのうち1つだけがセミコロンを使用します。

1. C言語のfor風

// C言語のforのような形式
for init; condition; post { }

// 具体例
for i := 0; i < 10; i++ {
    fmt.Println(i)
}

2. C言語のwhile風

// C言語のwhileのような形式
for condition { }

// 具体例
i := 0
for i < 10 {
    fmt.Println(i)
    i++
}

3. 無限ループ(C言語のfor(;;)風)

// C言語のfor(;;)のような形式
for { }

// 具体例
for {
    // 無限ループ
    if someCondition {
        break
    }
}

短縮宣言の活用

短縮宣言により、インデックス変数をループ内で直接宣言することが簡単になります。

sum := 0
for i := 0; i < 10; i++ {
    sum += i
}
// i はここでは使用できない(スコープがループ内のみ)

range句を使った反復

配列、スライス、文字列、マップをループしたり、チャネルから読み取る場合、range句がループを管理できます。

基本的なrange使用法

// マップの反復
for key, value := range oldMap {
    newMap[key] = value
}

// スライスの反復
numbers := []int{1, 2, 3, 4, 5}
for index, value := range numbers {
    fmt.Printf("インデックス %d: 値 %d\n", index, value)
}

キーまたはインデックスのみが必要な場合

範囲の最初の項目(キーまたはインデックス)のみが必要な場合、2番目を省略します:

for key := range m {
    if key.expired() {
        delete(m, key)
    }
}

// スライスでのインデックスのみ使用
for i := range slice {
    slice[i] = slice[i] * 2
}

値のみが必要な場合

範囲の2番目の項目(値)のみが必要な場合、空白識別子(アンダースコア)を使用して最初を破棄します:

sum := 0
for _, value := range array {
    sum += value
}

// マップでの値のみ使用
for _, value := range myMap {
    fmt.Println(value)
}

空白識別子には多くの用途があり、後のセクションで説明されます。

文字列でのrange

文字列の場合、rangeはUTF-8を解析して個々のUnicodeコードポイントを抽出するという、より多くの作業を行います。 エラーのあるエンコーディングは1バイトを消費し、置換ルーン U+FFFD を生成します。

Unicode処理の例

for pos, char := range "日本\x80語" { // \x80 は不正なUTF-8エンコーディング
    fmt.Printf("character %#U starts at byte position %d\n", char, pos)
}

出力:

character U+65E5 '日' starts at byte position 0
character U+672C '本' starts at byte position 3
character U+FFFD '�' starts at byte position 6
character U+8A9E '語' starts at byte position 7

rune型について

(関連する組み込み型を持つ)名前runeは、単一のUnicodeコードポイントのGo言語用語です。詳細は言語仕様を参照してください。

// rune型の使用例
var r rune = '日'
fmt.Printf("rune: %c, Unicode: %U\n", r, r)

forループでの制限事項

カンマ演算子と++/–の制限

最後に、Go言語にはカンマ演算子がなく、++--は式ではなく文です。 したがって、forで複数の変数を実行したい場合は、並列代入を使用する必要があります(ただし、これは++--を排除します)。

並列代入の例

// 配列 a を反転
for i, j := 0, len(a)-1; i < j; i, j = i+1, j-1 {
    a[i], a[j] = a[j], a[i]
}

この例では:

  • i, j := 0, len(a)-1 で複数変数を初期化
  • i, j = i+1, j-1 で並列代入を使用
  • i++, j-- は使用できない

配列・スライスの処理

// インデックスと値の両方を使用
fruits := []string{"apple", "banana", "cherry"}
for i, fruit := range fruits {
    fmt.Printf("%d: %s\n", i, fruit)
}

// 値のみを使用
for _, fruit := range fruits {
    fmt.Println(fruit)
}

// インデックスのみを使用
for i := range fruits {
    fmt.Printf("インデックス: %d\n", i)
}

マップの処理

ages := map[string]int{
    "Alice": 25,
    "Bob":   30,
    "Carol": 35,
}

// キーと値の両方を使用
for name, age := range ages {
    fmt.Printf("%s is %d years old\n", name, age)
}

// キーのみを使用
for name := range ages {
    fmt.Printf("Name: %s\n", name)
}

文字列の文字処理

text := "Hello, 世界"

// バイト単位での反復
for i := 0; i < len(text); i++ {
    fmt.Printf("Byte %d: %x\n", i, text[i])
}

// Unicode文字単位での反復
for pos, char := range text {
    fmt.Printf("Position %d: %c (U+%04X)\n", pos, char, char)
}

チャネルでのrange

ch := make(chan int, 3)
ch <- 1
ch <- 2
ch <- 3
close(ch)

// チャネルからの読み取り
for value := range ch {
    fmt.Println("Received:", value)
}

条件付きループ

// while風の使用
scanner := bufio.NewScanner(os.Stdin)
for scanner.Scan() {
    line := scanner.Text()
    if line == "quit" {
        break
    }
    fmt.Println("入力:", line)
}

無限ループでの処理

// サーバーのメインループ例
for {
    conn, err := listener.Accept()
    if err != nil {
        log.Printf("Accept error: %v", err)
        continue
    }
    
    go handleConnection(conn)
}

ラベル付きbreak/continue

outer:
for i := 0; i < 3; i++ {
    for j := 0; j < 3; j++ {
        if i == 1 && j == 1 {
            break outer // 外側のループから抜ける
        }
        fmt.Printf("i=%d, j=%d\n", i, j)
    }
}

複数変数での反復制御

// 両端から中央に向かって処理
data := []int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}
for left, right := 0, len(data)-1; left < right; left, right = left+1, right-1 {
    fmt.Printf("Left: %d, Right: %d\n", data[left], data[right])
    
    // 要素を交換
    data[left], data[right] = data[right], data[left]
}

まとめ

Go言語のforループの特徴

  1. 統一性: forwhile、無限ループをすべてforで表現
  2. range句: 配列、スライス、マップ、文字列、チャネルの簡潔な反復
  3. Unicode対応: 文字列rangeでのUTF-8自動処理
  4. 並列代入: 複数変数の同時更新をサポート
  5. スコープ制御: ループ変数のスコープを適切に管理

設計哲学

  • 簡潔性: 複数のループ構文を1つに統一
  • 安全性: 適切なスコープ管理と型安全性
  • 国際化対応: Unicode文字列処理の自動化
  • 実用性: 実際のプログラミングでよく使われるパターンをサポート

Go言語のforループは、シンプルでありながら強力で、様々な反復処理を効率的に記述できます。

ポイント

1. ループの統一

Go言語は他の言語にある複数のループ構文(forwhiledo-while)を1つのfor文に統一しています:

for init; condition; post { }  // 従来のfor
for condition { }              // while相当
for { }                        // 無限ループ

2. range句の強力さ

range句は単なる反復以上の機能を提供します:

  • 配列・スライス: インデックスと値
  • マップ: キーと値
  • 文字列: バイト位置とUnicodeコードポイント
  • チャネル: 受信した値

3. Unicode対応の自動化

文字列でのrangeUTF-8を自動解析し、個々のUnicodeコードポイントを抽出します。これは国際化対応において非常に重要な機能です。

4. Go言語特有の制限

  • カンマ演算子なし
  • ++/--は文であり式ではない
  • 並列代入を使用して複数変数を制御

設計哲学の反映

統一性

複数のループ構文を1つに統一することで、学習コストを削減し、コードの一貫性を保ちます。

実用性

range句やUnicode自動処理など、実際のプログラミングでよく必要となる機能を組み込んでいます。

安全性

適切なスコープ管理(ループ変数はforブロック内でのみ有効)により、変数の誤用を防ぎます。

国際化重視

UTF-8文字列の自動処理により、国際化対応のプログラムを簡単に書けます。

このforループの設計は、Go言語の「シンプルでありながら実用的」という哲学を体現した好例です。

おわりに 

本日は、Go言語を効果的に使うためのガイドラインについて解説しました。

よっしー
よっしー

何か質問や相談があれば、コメントをお願いします。また、エンジニア案件の相談にも随時対応していますので、お気軽にお問い合わせください。

それでは、また明日お会いしましょう(^^)

コメント

タイトルとURLをコピーしました