1. Home
  2. Golang
  3. GuideToBecomingGoDeveloper
  4. LearnTheBasics
  5. for 文(For Loop)- Golang learning step 1-10

for 文(For Loop)- Golang learning step 1-10

  • 公開日
  • カテゴリ:LearnTheBasics
  • タグ:Golang,roadmap.sh,学習メモ
for 文(For Loop)- Golang learning step 1-10

roadmap.sh > Go > Learn the Basics > For Loop の学習を進めていきます。

※ 学習メモとしての記録ですが、後にこのセクションを学ぶ道しるべとなるよう、ですます調で記載しています。

contents

  1. 開発環境
  2. for 文(For Loop)
  3. 基本的な for 文
  4. 条件付きループ
  5. ループの制御文: break, continue, return
    1. break - ループの終了
    2. continue - 次のループへのスキップ
    3. return - 関数の終了
  6. 無限ループ
    1. 実用的な使用例
    2. 無限ループの危険性と注意点

開発環境

  • チップ: Apple M2 Pro
  • OS: macOS Sonoma
  • go version: go1.23.2 darwin/arm64

for 文(For Loop)

Go には、ループ構文として for 文のみが存在します。基本的な for 文は、セミコロンで区切られた 3 つの要素で構成されています。

  • 初期化文:最初のループ実行前に一度だけ実行されます
  • 条件式:各ループの実行前に評価されます
  • 後処理式:各ループの最後に実行されます

参考:

基本的な for 文

基本的な for 文は、Go のループ処理で最も一般的な形です。この文法は、初期化式、条件式、後処理式の 3 つの部分で構成されます。

for 初期化式; 条件式; 後処理式 {
  // ループの本体
}

それぞれの要素の意味は以下の通りです。

  • 初期化式:ループの開始時に一度だけ実行される部分です。変数の初期化などが行われます。
  • 条件式:毎回ループの開始時に評価される部分です。この式が true の間、ループが続行され、false になるとループが終了します。
  • 後処理式:ループの本体が実行された後に毎回実行される部分です。変数のインクリメントやカウンタの更新が行われます。

例えば、1 から 5 までの数字を出力する for 文を以下に示します。

package main

import "fmt"

func main() {
  for i := 1; i <= 5; i++ {
    fmt.Println(i)
  }
}

このプログラムは次のように動作します。

  1. 初期化式 i := 1 が最初に実行され、変数 i に 1 が代入されます。
  2. 条件式 i <= 5 が評価されます。i が 5 以下であればループが続きます。1 回目の評価では i は 1 なので条件は true です。
  3. ループの本体で fmt.Println(i) が実行され、1 が出力されます。
  4. 後処理式 i++ が実行され、i の値が 1 増加します。次のループで i は 2 になります。
  5. これを繰り返し、i が 6 になったときに条件式が false となり、ループが終了します。

実行結果

1
2
3
4
5

条件付きループ

for 文に条件だけを指定することも可能です。この場合、while ループと同じ動作になります。Go には while ループが存在しませんが、条件式のみを使用した for 文を使うことで同様の動作を実現できます。

i := 1
for i <= 5 {
  fmt.Println(i)
  i++
}

// 出力:
// 1
// 2
// 3
// 4
// 5

これも先ほどと同様、i が 5 になるまでループが続きます。

ループの制御文: break, continue, return

Go の for ループでは、break、continue、return の 3 つの制御文を使って、ループの動作を制御できます。これらの制御文を適切に使うことで、ループの実行フローを柔軟に変更することができます。

break - ループの終了

break 文は、ループを途中で終了させたい場合に使用します。break が実行されると、ループは直ちに終了し、次の処理に進みます。

for i := 1; i <= 5; i++ {
  if i == 3 {
    break // i が 3 になったらループを抜ける
  }
  fmt.Println(i)
}
// 出力:
// 1
// 2

このプログラムは、1 から 2 までの数字を出力し、i が 3 になった時点でループを終了します。

なお、ネストしたループの場合(ループが入れ子になっている場合)、break は最も内側のループのみを終了します。外側のループはそのまま続行されます。つまり、break は現在実行中の一番内側のループだけを終了させます。

for i := 1; i <= 3; i++ {
  fmt.Printf("外側のループ: i = %d\n", i)
  for j := 1; j <= 3; j++ {
    if j == 3 {
      break // j が 3 になったら内側のループのみを抜ける
    }
    fmt.Printf("  内側のループ: j = %d\n", j)
  }
}
// 出力:
// 外側のループ: i = 1
//   内側のループ: j = 1
//   内側のループ: j = 2
// 外側のループ: i = 2
//   内側のループ: j = 1
//   内側のループ: j = 2
// 外側のループ: i = 3
//   内側のループ: j = 1
//   内側のループ: j = 2

ラベル付きの break

ラベルを使用すると、ネストされた複数のループの中で特定のループを終了させることができます。

outer:
  for i := 1; i <= 3; i++ {
    for j := 1; j <= 3; j++ {
      if i*j > 5 {
        break outer // 外側のループまで一気に抜ける
      }
      fmt.Printf("i: %d, j: %d\n", i, j)
    }
  }
// 出力:
// i: 1, j: 1
// i: 1, j: 2
// i: 1, j: 3
// i: 2, j: 1
// i: 2, j: 2

ラベルを使用すると、複数のループがネストされている場合に、内側のループだけでなく、特定の外側のループまで一気に終了させることができます。これにより、特定の状況下で複雑なループの制御が容易になります。

continue - 次のループへのスキップ

continue 文は、現在のループの反復処理をスキップし、次の反復処理に進みたい場合に使用します。ループ本体の残りの処理を実行せずに、次のイテレーション(繰り返し処理)に移ります。

for i := 1; i <= 5; i++ {
  if i == 3 {
    continue // i が 3 の時はスキップ
  }
  fmt.Println(i)
}
// 出力:
// 1
// 2
// 4
// 5

ラベル付きの continue

ラベルを使用することで、ネストされたループの特定の外側のループに対して、次のイテレーションにスキップさせることができます。通常の continue は最も内側のループでのみ適用されますが、ラベルを使うことで、外側のループまでスキップすることが可能です。

以下の例では、i * j が 4 以上の場合に、内側のループを中断して外側のループの次のイテレーションに進むようにしています。

outer:
  for i := 1; i <= 3; i++ {
    for j := 1; j <= 3; j++ {
      if i*j >= 4 {
        continue outer // 外側のループの次のイテレーションに進む
      }
      fmt.Printf("i: %d, j: %d\n", i, j)
    }
  }

// 出力:
// i: 1, j: 1
// i: 1, j: 2
// i: 1, j: 3
// i: 2, j: 1
// i: 3, j: 1

return - 関数の終了

return 文は、関数の実行を終了して値を返す際に使用されますが、ループの中で使うと、その関数自体の実行が終了します。つまり、return はループの外に出るだけでなく、関数自体も終了します。

package main

import "fmt"

func printNumbers() {
  for i := 1; i <= 5; i++ {
    if i == 3 {
      return // i が 3 になったら関数を終了
    }
    fmt.Println(i)
  }
  fmt.Println("この行は実行されません")
}

func main() {
  printNumbers()
}

// 出力:
// 1
// 2

無限ループ

for 文に条件を指定しない場合、無限ループになります。

for {
  // ループ処理
}

実用的な使用例

無限ループは、以下のような継続的な処理が必要な場合に実用的です。

1. ユーザー入力の待ち受け

package main

import (
  "bufio"
  "fmt"
  "os"
  "strings"
)

func main() {
  scanner := bufio.NewScanner(os.Stdin)

  for {
    fmt.Print("コマンドを入力してください(終了する場合は 'quit'): ")
    scanner.Scan()
    input := strings.TrimSpace(scanner.Text())

    if input == "quit" {
      fmt.Println("プログラムを終了します")
      break
    }

    fmt.Printf("入力されたコマンド: %s\n", input)
  }
}

実行結果:

コマンドを入力してください(終了する場合は 'quit'): A
入力されたコマンド: A
コマンドを入力してください(終了する場合は 'quit'): B
入力されたコマンド: B
コマンドを入力してください(終了する場合は 'quit'): C
入力されたコマンド: C
コマンドを入力してください(終了する場合は 'quit'): quit
プログラムを終了します

2. サーバープログラム

package main

import (
  "fmt"
  "net/http"
  "os"
  "os/signal"
  "syscall"
)

func main() {
  // シグナル待ち受けのチャネル
  sigChan := make(chan os.Signal, 1)
  signal.Notify(sigChan, syscall.SIGINT, syscall.SIGTERM)

  // HTTPサーバーの設定
  http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
    fmt.Fprintf(w, "Hello, World!")
  })

  // サーバーを別のゴルーチンで起動
  go func() {
    fmt.Println("サーバーを起動しました。http://localhost:8080/")
    if err := http.ListenAndServe(":8080", nil); err != nil {
      fmt.Printf("サーバーエラー: %v\n", err)
    }
  }()

  // シグナルを待ち受け
  for {
    sig := <-sigChan
    fmt.Printf("\n%vシグナルを受信しました。シャットダウンを開始します...\n", sig)
    break
  }
}

実行結果:

% curl http://localhost:8080/
Hello, World!

4. 定期的なタスク実行

package main

import (
  "fmt"
  "time"
)

func main() {
  ticker := time.NewTicker(1 * time.Second)
  defer ticker.Stop()

  count := 0
  for {
    <-ticker.C
    count++
    fmt.Printf("タスクを実行しました(%d回目)\n", count)

    if count >= 5 {
      fmt.Println("すべてのタスクが完了しました")
      break
    }
  }
}

実行結果:

タスクを実行しました(1回目)
タスクを実行しました(2回目)
タスクを実行しました(3回目)
タスクを実行しました(4回目)
タスクを実行しました(5回目)
すべてのタスクが完了しました

無限ループの危険性と注意点

無限ループは強力なプログラミング手法ですが、適切に制御されないと重大な問題を引き起こす可能性があります。

1. リソースの枯渇

無限ループが適切に制御されていないと、CPU やメモリなどのリソースを過剰に消費する可能性があります。

package main

import (
  "fmt"
  "runtime"
  "time"
)

func badLoop() {
  count := 0
  for {
    count++ // カウンターが無限に増加
  }
}

func goodLoop() {
  count := 0
  ticker := time.NewTicker(100 * time.Millisecond)
  defer ticker.Stop()

  for {
    select {
    case <-ticker.C:
      count++
      fmt.Printf("現在のカウント: %d\n", count)
    }
  }
}

func main() {
  // CPUの使用率を表示
  go func() {
    for {
      fmt.Printf("CPUの使用率: %d%%\n", runtime.NumGoroutine())
      time.Sleep(time.Second)
    }
  }()

  // badLoop() // このループを実行するとCPU使用率が100%になる
  goodLoop() // このループは適切な間隔で実行される
}

2. 終了条件の重要性

  • 必ず適切な終了条件(break文、return文)を設定する
  • 可能な限りタイムアウト処理を実装する
  • システムシグナル(Ctrl+C等)を適切に処理する
package main

import (
  "fmt"
  "time"
)

func main() {
  // タイムアウト付きの無限ループ
  timeout := time.After(5 * time.Second)
  count := 0

  for {
    select {
    case <-timeout:
      fmt.Println("タイムアウトしました")
      return
    default:
      count++
      fmt.Printf("処理回数: %d\n", count)
      time.Sleep(1 * time.Second)
    }
  }
}
処理回数: 1
処理回数: 2
処理回数: 3
処理回数: 4
処理回数: 5
タイムアウトしました

3. エラー処理の実装

無限ループ内では、エラーが発生した際の適切な処理と回復手段を実装することが重要です

package main

import (
  "fmt"
  "time"
)

func riskyOperation() error {
  // エラーが発生する可能性のある処理
  return fmt.Errorf("エラーが発生しました")
}

func main() {
  maxRetries := 3
  retryCount := 0

  for {
    err := riskyOperation()
    if err != nil {
      retryCount++
      fmt.Printf("エラーが発生しました(%d回目): %v\n", retryCount, err)

      if retryCount >= maxRetries {
        fmt.Println("最大リトライ回数に達しました。プログラムを終了します")
        break
      }

      // 一定時間待機してから再試行
      time.Sleep(time.Second)
      continue
    }

    // 正常終了
    fmt.Println("処理が成功しました")
    break
  }
}
エラーが発生しました(1回目): エラーが発生しました
エラーが発生しました(2回目): エラーが発生しました
エラーが発生しました(3回目): エラーが発生しました
最大リトライ回数に達しました。プログラムを終了します

これらの例のように、無限ループは適切に制御され、明確な目的を持って使用する必要があります。また、リソースの管理、エラー処理、終了条件の設定を適切に行うことで、安全で効果的なプログラムを作成することができます。

まとめ

  • 基本的な for 文は、初期化式、条件式、後処理式で構成され、条件式が true の間、ループが継続します。
  • 条件付きループは、for に条件式だけを指定することで、while ループと同様の動作を実現できます。
  • 制御文
    • break: ループを途中で終了します。
    • continue: 現在のイテレーションをスキップして次のイテレーションに進みます。
    • return: ループを終了させ、関数自体を終了します。
  • 無限ループは、条件式を指定しない for 文で作成でき、ユーザー入力の待ち受けやサーバーの待機処理などに利用されます。
  • 無限ループの注意点として、適切な終了条件を設けること、リソースの管理やエラー処理を実装することが重要です。


[Next] Step 1-11: Range

[Prev] Step 1-9: 条件文(Conditional Statements)

Author

rito

  • Backend Engineer
  • Tokyo, Japan
  • PHP 5 技術者認定上級試験 認定者
  • 統計検定 3 級