for 文(For Loop)- Golang learning step 1-10
- 公開日
- カテゴリ:LearnTheBasics
- タグ:Golang,roadmap.sh,学習メモ
roadmap.sh > Go > Learn the Basics > For Loop の学習を進めていきます。
※ 学習メモとしての記録ですが、後にこのセクションを学ぶ道しるべとなるよう、ですます調で記載しています。
contents
開発環境
- チップ: Apple M2 Pro
- OS: macOS Sonoma
- go version: go1.23.2 darwin/arm64
for 文(For Loop)
Go には、ループ構文として for 文のみが存在します。基本的な for 文は、セミコロンで区切られた 3 つの要素で構成されています。
- 初期化文:最初のループ実行前に一度だけ実行されます
- 条件式:各ループの実行前に評価されます
- 後処理式:各ループの最後に実行されます
参考:
- [official] For Loop in Golang
- [official] Effective Go: For loop
- [article] Go by Example: For loop
- [article] 5 Basic for Loop Patterns
基本的な 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)
}
}
このプログラムは次のように動作します。
- 初期化式 i := 1 が最初に実行され、変数 i に 1 が代入されます。
- 条件式 i <= 5 が評価されます。i が 5 以下であればループが続きます。1 回目の評価では i は 1 なので条件は true です。
- ループの本体で fmt.Println(i) が実行され、1 が出力されます。
- 後処理式 i++ が実行され、i の値が 1 増加します。次のループで i は 2 になります。
- これを繰り返し、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