Go制御構文
この章では、Goの制御構文について概要を解説し、ハンズオン形式で実際に動作させながら学習します。これにより、ifとswitchによる条件分岐・forによる繰り返しを使いこなす基本が身につきます。
1. 本章の概要
1.1 本章の目的
条件分岐と繰り返しは、あらゆるプログラムの土台になる要素です。Goでは条件分岐を if / switch、繰り返しを for で表現します。基本の書き方を最初に押さえておくと、以降のハンズオンで戸惑わずに済みます。本章では、if 文と switch 文による条件分岐、for 文による繰り返しに加えて、for 文の中で使う break / continue、for-range による反復も扱います。
1.2 ハンズオンの流れ
分岐・繰り返しをそれぞれの構文で書き分けながら、標準出力に結果を出して挙動を確かめます。使い分けを意識しながら、Goらしい書き方に慣れます。
1.3 事前準備
必要なツール
この章では、以下のツールを使用します。まだインストールしていない場合は、リンク先の手順に沿って準備をお願いします。
| ツール名 | 関連箇所 | 理由 |
|---|---|---|
| Visual Studio Code | Visual Studio Codeのインストール | Goのコードを記述するエディタとして使用する |
| Go | Goのインストール | 本章のサンプルコードを実行する言語処理系として使用する |
2. 条件分岐
プログラムは、値の状態や計算結果によって処理を切り替える場面が数多くあります。例えば「点数が 60 以上なら合格と表示する」「曜日によって表示を変える」といった判断を、Goでは if 文 と switch 文 で表現します。ここではまず if 文で基本の書き方を押さえたあと、多分岐を見やすく整理する switch 文を扱います。
2.1 if
if 文は、条件分岐のもっとも基本となる構文です。条件式が true になるとき、{ ... } の中の処理が実行されます。書き方は、条件式にカッコを付けず、続く処理を波カッコでくくるのが特徴です。
基本の書き方
if 条件式 {
// 条件を満たすときの処理
}
実際に試してみましょう。main.go を以下の内容に書き換えて保存し、go run main.go で実行します。これは、score が 60 以上かを判定し、条件を満たしたときだけメッセージを表示するコードです。
package main
import "fmt"
func main() {
score := 85
if score >= 60 {
fmt.Println("合格")
}
}
以下のような実行結果が表示されます。
合格
score が 85 で score >= 60 が満たされたため、合格 が出力されました。
コードを解説します。
score := 85
変数 score に 85 を代入しています。
if score >= 60 {
fmt.Println("合格")
}
score >= 60 が true なら(つまり score が 60 以上なら)、続く波カッコ内の fmt.Println("合格") が実行されます。条件を満たさないときは波カッコ内の処理はスキップされ、何も表示されません。
else
条件を満たさないときの処理は、else で書けます。書き方は以下のとおりです。
if 条件式 {
// 条件を満たすときの処理
} else {
// 条件を満たさないときの処理
}
次のコードは、score が 60 以上なら「合格」、そうでなければ「不合格」を表示します。
package main
import "fmt"
func main() {
score := 40
if score >= 60 {
fmt.Println("合格")
} else {
fmt.Println("不合格")
}
}
以下のような実行結果が表示されます。
不合格
score が 40 で score >= 60 が false になったため、else のブロックが実行されて 不合格 が出力されました。
コードを解説します。
if score >= 60 {
fmt.Println("合格")
} else {
fmt.Println("不合格")
}
if の条件が false のときに、続く else のブロックが実行されます。if と else のうち、どちらか一方だけが必ず実行される仕組みです。
else if
もう1つ条件を追加したいときは、else if で条件を並べます。書き方は以下のとおりです。
if 条件式1 {
// 条件式1を満たすときの処理
} else if 条件式2 {
// 条件式2を満たすときの処理
} else {
// どれも満たさないときの処理
}
次のコードは、点数によって「合格/あと一息!/不合格」を表示し分けます。
package main
import "fmt"
func main() {
score := 50
if score >= 60 {
fmt.Println("合格")
} else if score >= 40 {
fmt.Println("あと一息!")
} else {
fmt.Println("不合格")
}
}
以下のような実行結果が表示されます。
あと一息!
score が 50 で score >= 60 は false、次の score >= 40 が最初に true になったため、あと一息! が出力されました。
コードを解説します。
if score >= 60 {
fmt.Println("合格")
} else if score >= 40 {
fmt.Println("あと一息!")
} else {
fmt.Println("不合格")
}
if の条件から順にチェックしていき、最初に true になったブロックだけが実行されます。以降の判定はスキップされます。どの条件にも当てはまらなかったときは、最後の else のブロック(不合格)が実行されます。
初期化文
Go独特の書き方として、if 文の条件式の前に初期化文を書けます。この方法で宣言した変数は、そのifブロックの中でだけ有効です。
if 初期化文; 条件式 {
// 処理
}
具体例で確認します。これは、n を宣言して 2 で割った余りをその場で判定し、偶数か奇数かを表示するコードです。
package main
import "fmt"
func main() {
if n := 42; n%2 == 0 {
fmt.Printf("%dは偶数\n", n)
} else {
fmt.Printf("%dは奇数\n", n)
}
}
以下のような実行結果が表示されます。
42は偶数
n に 42 を代入した直後に n%2 == 0 を判定し、偶数だったため 42は偶数 が出力されました。
コードを解説します。
if n := 42; n%2 == 0 {
; の前の n := 42 が初期化文で、その場で n を宣言・初期化しています。; のうしろの n%2 == 0 が条件式で、n を 2 で割った余りが 0 かを判定します。
} else {
fmt.Printf("%dは奇数\n", n)
}
else ブロックの中でも、初期化した n を参照できます。n の有効範囲はこの if / else 全体で、ブロックの外では参照できません。
この書き方は、エラーハンドリング(Go関数と型定義で扱う if err := ...; err != nil のパターン)で頻繁に登場します。
2.2 switch
if / else if を並べる代わりに、switch を使うと分岐を見やすく整理できます。Goの switch は柔軟で、値の一致だけでなく、条件式による分岐にも使えます。
値による分岐
switch に評価したい値を書き、各 case にマッチさせたい値を並べます。どの case にもマッチしなかったときの処理は default に書きます。書き方は以下のとおりです。
switch 値 {
case 値1:
// 値1にマッチしたときの処理
case 値2:
// 値2にマッチしたときの処理
default:
// どれにもマッチしなかったときの処理
}
これは、変数 day の値によって対応する曜日名を表示するコードです。
package main
import "fmt"
func main() {
day := "Wed"
switch day {
case "Mon":
fmt.Println("月曜日")
case "Tue":
fmt.Println("火曜日")
case "Wed":
fmt.Println("水曜日")
default:
fmt.Println("その他の曜日")
}
}
以下のような実行結果が表示されます。
水曜日
day が "Wed" なので case "Wed": のブロックがマッチし、水曜日 が出力されました。
コードを解説します。
switch day {
case "Mon":
fmt.Println("月曜日")
case "Tue":
fmt.Println("火曜日")
case "Wed":
fmt.Println("水曜日")
default:
fmt.Println("その他の曜日")
}
switch のあとに書いた day の値と、各 case の値を上から順に照合します。どの case にもマッチしなかった場合は default: のブロックが実行されます。
case にマッチしたら、そのブロックだけが実行されます。次の case へ流れる(フォールスルー)ことはなく、break を書かなくてもマッチしたブロックの処理だけで終わります。
複数の値を1つの case にまとめたい場合はカンマで区切ります。次のコードは、day が土曜または日曜のときに「週末」、それ以外なら「平日」を表示します。
switch day {
case "Sat", "Sun":
fmt.Println("週末")
default:
fmt.Println("平日")
}
以下のような実行結果が表示されます。
平日
day は "Wed" なので "Sat", "Sun" の case にはマッチせず、default: の 平日 が出力されました。
コードを解説します。
case "Sat", "Sun":
fmt.Println("週末")
カンマで区切ることで、day が "Sat" または "Sun" のときにこのブロックが実行されるようになります。
条件式による分岐
switch の値を省略すると、各 case に真偽値を返す式を書けます。if / else if の代替として使えます。書き方は以下のとおりです。
switch {
case 条件式1:
// 条件式1がtrueのときの処理
case 条件式2:
// 条件式2がtrueのときの処理
default:
// どれもfalseのときの処理
}
次のコードは、点数によって「合格/あと一息!/不合格」を表示し分けます。
package main
import "fmt"
func main() {
score := 50
switch {
case score >= 60:
fmt.Println("合格")
case score >= 40:
fmt.Println("あと一息!")
default:
fmt.Println("不合格")
}
}
以下のような実行結果が表示されます。
あと一息!
score = 50 で score >= 60 は false、次の score >= 40 が最初に true になったため あと一息! が出力されました。
コードを解説します。
switch {
case score >= 60:
fmt.Println("合格")
case score >= 40:
fmt.Println("あと一息!")
default:
fmt.Println("不合格")
}
switch のあとに値を書いていないので、各 case は真偽値を返す条件式として評価されます。上から順に評価し、最初に true になったブロックだけが実行されます。
if / else if の連なりよりも見通しが良くなるので、条件が3つ以上ある場合はこちらを選ぶことも多いです。
なお、switch には受け取った値の実際の型に応じて分岐する型スイッチという書き方もあります。詳細はGoインタフェースで扱います。
3. 繰り返し
同じ処理を何度も実行したいとき、繰り返し(ループ) を使います。「1から10まで数える」「文字列を1文字ずつ処理する」といった場面で登場します。Goでは繰り返しを for 文 で書きます。ここでは条件の書き方に応じた for 文の使い分けと、ループの制御に使う break / continue、そして要素を順に取り出す for-range を扱います。
3.1 for
for は「初期化・条件・更新」の3つの要素の組み合わせを変えることで、複数のスタイルを表現します。
従来型(3要素すべて)
for のあとに 初期化文・条件式・更新文 の3つを ; で区切って書きます。書き方は以下のとおりです。
for 初期化文; 条件式; 更新文 {
// 繰り返す処理
}
これは、0 から 4 までの数字を順に表示するコードです。
package main
import "fmt"
func main() {
for i := 0; i < 5; i++ {
fmt.Println(i)
}
}
以下のような実行結果が表示されます。
0
1
2
3
4
i が 0 から始まり、i < 5 を満たす間、1ずつ増やしながら出力されました。
コードを解説します。
for i := 0; i < 5; i++ {
fmt.Println(i)
}
for のあとに 初期化文(i := 0); 条件式(i < 5); 更新文(i++) の3つを ; で区切って書きます。実行の流れは次のとおりです。
- 初期化文で
iを0に設定する - 条件式
i < 5を評価し、trueなら中身を実行する - 中身の
fmt.Println(i)で現在のiを出力する - 更新文
i++でiを1増やす - 手順2に戻る
i が 5 になったとき条件が false になり、ループを抜けます。もっとも一般的な繰り返しの書き方で、指定回数の処理を行うときに使います。
条件のみ
for に条件式だけを書くと、条件を満たすあいだ処理を続けるループになります。書き方は以下のとおりです。
for 条件式 {
// 条件式がtrueの間、繰り返す処理
}
次のコードは、n が 3 未満の間、n を表示しながら 1 ずつ増やします。
package main
import "fmt"
func main() {
n := 0
for n < 3 {
fmt.Println(n)
n++
}
}
以下のような実行結果が表示されます。
0
1
2
n < 3 が true である間、0 から 2 までが順に出力され、n が 3 になった時点でループが終了しました。
コードを解説します。
n := 0
for n < 3 {
fmt.Println(n)
n++
}
for のあとに条件式 n < 3 だけを書いています。中身で n++ を呼んで n を1ずつ増やすことで、いずれ条件が false になりループが終わります。
| 💡 ポイント |
|---|
ループの中で n++ を書き忘れると、n は 0 のままで n < 3 が常に true になり、0 を出力し続けてプログラムが止まらなくなります(無限ループ)。条件式だけの for を使うときは、条件がいずれ false になるように、ループの中で必ず値を更新するよう注意しましょう。 |
3.2 break
break は、ループを完全に抜けるための構文です。ループの中で条件付きに使うのが一般的な形です。書き方は以下のとおりです。
for ... {
if 条件式 {
break
}
// 通常の処理
}
次のコードは、0 から 4 までを順に表示しますが、i が 3 に達した時点でループを止めます。
package main
import "fmt"
func main() {
for i := 0; i < 5; i++ {
if i == 3 {
break
}
fmt.Println(i)
}
}
以下のような実行結果が表示されます。
0
1
2
i が 0、1、2 のときは表示され、3 に達した時点で break によりループを抜けたため、それ以降は実行されませんでした。
コードを解説します。
if i == 3 {
break
}
i が 3 に達した時点で break によりループを完全に抜けます。したがって、i が 3 以降の繰り返しは実行されません。
3.3 continue
continue は、その回の残りの処理をスキップして次の繰り返しに進むための構文です。書き方は以下のとおりです。
for ... {
if 条件式 {
continue
}
// continue が実行されると、以降の処理はスキップされる
}
次のコードは、0 から 4 までのうち奇数だけを表示します。
package main
import "fmt"
func main() {
for i := 0; i < 5; i++ {
if i%2 == 0 {
continue
}
fmt.Println(i)
}
}
以下のような実行結果が表示されます。
1
3
偶数(0、2、4)のときは continue により fmt.Println(i) がスキップされ、奇数の 1 と 3 だけが出力されました。
コードを解説します。
if i%2 == 0 {
continue
}
i を 2 で割った余りが 0(つまり i が偶数)のとき、continue によりその回の残りの処理をスキップして次の繰り返しに進みます。したがって、その先の fmt.Println(i) は実行されません。
3.4 for-range
ここまで見てきた for 文は、ループの回数や条件を自分で管理する書き方でした。ただ実際には「先頭から末尾まで順に取り出したいだけ」という場面が多く、その用途に特化した for-range という書き方が用意されています。ここでは文字列を対象に、通常の for 文で書いた場合と for-range で書いた場合を並べて見ていきます。
まずは、通常の for 文で文字列 "Golang" の1文字ずつを表示するコードです。
package main
import "fmt"
func main() {
s := "Golang"
for i := 0; i < len(s); i++ {
fmt.Printf("[%d] %c\n", i, s[i])
}
}
以下のような実行結果が表示されます。
[0] G
[1] o
[2] l
[3] a
[4] n
[5] g
len で長さを取り、i を 0 から順に進めながら、s[i] で個別の文字を取り出しています。動きますが、i := 0; i < len(s); i++ の管理や s[i] のインデックス指定を毎回書く必要があり、書き方が少し冗長です。
ここで、for-range という書き方を使うと、同じ処理を短く書けます。書き方は以下のとおりです。
for インデックス, 要素 := range コレクション {
// 繰り返す処理
}
次のコードは、for-range で "Golang" を1文字ずつ表示する例です。
package main
import "fmt"
func main() {
s := "Golang"
for i, r := range s {
fmt.Printf("[%d] %c\n", i, r)
}
}
以下のような実行結果が表示されます。
[0] G
[1] o
[2] l
[3] a
[4] n
[5] g
同じ結果が得られました。len の呼び出しやインデックス指定を書かずに、インデックスと要素を一度に受け取れるので短く読めます。
コードを解説します。
for i, r := range s {
fmt.Printf("[%d] %c\n", i, r)
}
range s で s の文字を先頭から順に取り出します。取り出すたびに、左辺の i にバイト位置、r に取り出した文字(Unicodeコードポイント、Goでは rune 型と呼びます)が入り、続く fmt.Printf で表示されます。
さらに、for-range は日本語などマルチバイト文字を含む文字列も、1文字ずつ正しく扱えます。次のコードは、"Go言語" を1文字ずつ、バイト位置と文字のペアで表示する例です。
for i, r := range "Go言語" {
fmt.Printf("[%d] %c\n", i, r)
}
以下のような実行結果が表示されます。
[0] G
[1] o
[2] 言
[5] 語
G と o は1バイトずつなのでインデックスが連続していますが、日本語の 言(3バイト)を挟むと 語 のインデックスが 5 に飛んでいます。UTF-8 ではマルチバイト文字が複数のバイトにわたって格納されるため、通常の for 文で s[i] のようにバイト単位で取り出すと文字が壊れてしまいますが、for-range を使えば1文字(rune)単位で安全に取り出せます。
なお、for-range は文字列だけでなく、後の章で扱うスライス・マップの反復にも同じ形で使えます。詳細はGoデータ構造で扱います。
4. まとめ
この章では、Goの制御構文を学びつつ、実際に条件分岐と繰り返しを体験しました。
- 条件分岐は、
if文とswitch文の2種類で表現できる if文は条件式にカッコが不要で、if 初期化文; 条件式の書き方でスコープ限定の変数を扱えるswitchはデフォルトでフォールスルーせず、caseを並べるだけで安全に分岐できる- 値を省略した
switchは、if / else ifの見通しが良い代替になる - 繰り返しは
for文で書き、「初期化・条件・更新」の3要素の組み合わせを変えることで多様なループを表現できる break/continueにより、ループの中断・スキップができるfor-rangeを使うと、対象の要素を先頭から順に取り出せる(文字列のほか、次章で扱うスライスやマップにも同じ形で使える)
次の章では、Goのデータ構造を学びつつ、実際に配列・スライス・マップの扱いを体験します。