Goで「これどう書くんやっけ」となった時に見返すためのTips集.まずは for の書き方から.
早見表#
| やりたいこと | 書き方 |
|---|---|
| カウンタを増やす | for i := 0; i < n; i++ { ... } |
| n回繰り返す(Go 1.22以降) | for range n { ... } |
| 0からn−1を使う(Go 1.22以降) | for i := range n { ... } |
| 条件を満たす間繰り返す | for 条件 { ... } |
| 無限ループ | for { ... } |
| スライスの添字と値を使う | for i, v := range items { ... } |
| スライスの値だけ使う | for _, v := range items { ... } |
| スライスの添字だけ使う | for i := range items { ... } |
| 逆順に処理する | for i := len(items) - 1; i >= 0; i-- { ... } |
以下のコードは,特記がなければ main 関数内に書く想定.fmt を使う例では import "fmt" が必要です.
基本形:初期化・条件・更新#
for i := 0; i < 5; i++ {
fmt.Println(i) // 0, 1, 2, 3, 4
}for 初期化; 条件; 更新 の形.2つ飛ばしなら i += 2 のように,刻み幅も変えられます.
回数だけ指定する(Go 1.22以降)#
for range 3 {
fmt.Println("hello") // 3回表示
}
for i := range 3 {
fmt.Println(i) // 0, 1, 2
}整数への range はGo 1.22で追加されました.n 回繰り返し,添字は 0 から n-1.n が0以下なら一度も実行しません.利用するモジュールの go.mod でも go 1.22 以降を指定します.Go 1.22リリースノート
while風:条件だけ書く#
Goには while がないので,for に条件だけ書きます.
n := 1
for n < 10 {
fmt.Println(n) // 1, 2, 4, 8
n *= 2
}無限ループとdo-while風#
条件を省略すると無限ループ.終了条件で break します.
n := 0
for {
fmt.Println(n)
n++
if n >= 3 {
break
}
}do-while もありませんが,このようにループの末尾で終了判定すると,最低1回は処理を実行できます.
スライス・配列をrangeで回す#
names := []string{"Go", "Rust", "Python"}
for i, name := range names {
fmt.Println(i, name)
}
// 値だけ使う
for _, name := range names {
fmt.Println(name)
}
// 添字だけ使う
for i := range names {
fmt.Println(i)
}変数を1つだけ書くと,受け取るのは値ではなく添字です.値だけ欲しい時は _, name と書きます.
元の要素を更新する#
range で受け取る値は要素のコピーなので,元の要素への代入には添字を使います.
nums := []int{1, 2, 3}
for i := range nums {
nums[i] *= 2
}
fmt.Println(nums) // [2 4 6]ポインタなどを含む要素の場合,コピー先から参照先を変更することはできます.「値がコピーされる」と「参照先も複製される」は別の話です.
逆順に回す#
names := []string{"Go", "Rust", "Python"}
for i := len(names) - 1; i >= 0; i-- {
fmt.Println(names[i]) // Python, Rust, Go
}空のスライスなら i の初期値が -1 になり,一度も実行しません.
mapを回す#
scores := map[string]int{"alice": 90, "bob": 80}
for name, score := range scores {
fmt.Println(name, score)
}
// キーだけ使う
for name := range scores {
fmt.Println(name)
}mapの走査順は保証されません.順番が必要なら,キーをスライスに集めてソートしてからアクセスします.
文字列を回す:runeとbyte#
s := "Go言語"
// Unicodeコードポイント単位
for i, r := range s {
fmt.Printf("%d: %c\n", i, r)
}
// 0: G
// 1: o
// 2: 言
// 5: 語
// byte単位
for i := 0; i < len(s); i++ {
fmt.Printf("%d: %02x\n", i, s[i])
}文字列への range は rune 単位ですが,添字はUTF-8のバイト位置です.また,結合文字や絵文字では,1つの見た目の文字が複数の rune になることがあります.
channelから受信する#
ch := make(chan string, 2)
ch <- "Go"
ch <- "Rust"
close(ch)
for name := range ch {
fmt.Println(name)
}送信済みの値を受信し終え,channelが閉じられるとループが終了します.閉じられていないchannelは,値がなければ受信待ちになります.この例ではバッファを2つ用意しているので,受信前に2回送信できます.
continueとbreak#
for i := 0; i < 10; i++ {
if i%2 == 0 {
continue // 偶数はスキップ
}
if i >= 7 {
break // ループ終了
}
fmt.Println(i) // 1, 3, 5
}この形の for では,continue しても更新処理の i++ は実行されます.条件だけのループで自分でカウンタを更新する場合は,更新を飛ばさないように注意.
入れ子のループから抜ける#
Outer:
for i := 0; i < 3; i++ {
for j := 0; j < 3; j++ {
if i+j == 3 {
break Outer
}
fmt.Println(i, j)
}
}break Outer でラベルの付いた外側のループを終了できます.continue Outer なら外側のループの次の反復に進みます.
イテレータを回す(Go 1.23以降)#
Go 1.23以降はイテレータ関数にも range を使えます.例えば,標準ライブラリの slices.Backward で逆順に走査できます.公式ブログの解説
package main
import (
"fmt"
"slices"
)
func main() {
names := []string{"Go", "Rust", "Python"}
for i, name := range slices.Backward(names) {
fmt.Println(i, name) // 2 Python, 1 Rust, 0 Go
}
}ループ変数とGoのバージョン#
Go 1.22以降の言語仕様では,:= でループ内に宣言した変数は反復ごとに新しく作られます.古い記事にある,クロージャのための v := v という退避は,この条件では不要です.ただし,外で宣言した変数へ = で代入する場合は同じ変数を使い回します.Go 1.22リリースノート
基本構文と range の詳しい仕様は,Go言語仕様のFor statementsを参照.