goの基本文法 No.2

4 min read読了の目安(約4100字

2020-09-27執筆
A Tour of Goに沿ってGoの文法をまとめます。
これまでにJavaScriptやPythonなどのリッチ言語しか使ったことがなかった自分のためにまとめたものです。
なのでこれまでに上のようなリッチ言語を触ったことがあり、これからGoを触ってみようと考えている人の参考になればと思います。
文章の構成は、基本的に

  1. 概要
  2. コード
  3. コードの説明

の構成にしているつもりです。概要で簡単な説明をし、コードで例を示して、さらにコードの説明で細かい説明をするといった構成です。

繰り返し処理

基本繰り返し処理

同じ処理を複数回行う場合はFor文を使用します。

for 初期化;条件;実行後処理 {
    // 繰り返したい処理
}

上がforで一般的に使われる構文です。
初期化  :最初の反復の前に実行されます。
条件   :全ての反復の前に評価されます。
反復後処理:全ての反復の後に実行されます。
次の①のコードと実行結果を見ると具体的なイメージがつくと思います。

// code:2-1
// ①
func main() {
	for i := 0; i < 5; i++ {
		fmt.Println(i)
	}
}
/*実行結果
0
1
2
3
4
*/
// ②
func main() {
	i := 1
	for {
		fmt.Println(i)
		i++
	}
}

永久に条件を満たさないように初期化、条件、反復後処理を書いた場合は無限ループになります。
極端な例は②のように条件を書かない場合です。これは無限ループになります。

for以外のループ処理構文

Goにはfor文以外のループ構文はありません。多言語にあるwhile文なども無いので全てfor文で代用します。


条件分岐処理

if文

条件分岐は他の言語同様if文を使用します。

// ①
if 条件 {
    // 条件が成り立つ時にしたい処理
}
// ②
if 条件 {
    // 条件が成り立つ時にしたい処理
} else {
    // 条件が成り立たない時にしたい処理
}
  1. 条件が成り立つ場合に処理したい場合
  2. 条件が成り立たない時も処理をしたい場合

例えば次のように奇数・偶数を判定したいコードを作る時に使えます。

// code:2-2
func main() {
    num := 7
    if num % 2 == 0 {
        fmt.Println("偶数")
    } else {
        fmt.Println("奇数")
    }
}
/*
奇数
*/

このプログラムの条件の部分は与えられた値(num)を2で割った余りが0かどうかで奇数か偶数かを判定します。

比較演算子

Goで使用出来る比較演算子です。

演算子
== 等しい
!= 等しくない
< 小なり
<= 小なりイコール
> 大なり
>= 大なりイコール

論理演算子

Goで使用できる論理演算子です。

演算子
&& かつ
|| または
! 否定
// code:2-3
func main() {
	num := 7

	if num > 0 && num <= 9 {
		fmt.Printf("%d は一桁の正の整数です。", num)
	} else {
		fmt.Printf("%d は一桁の正の整数ではありません。", num)
	}
}
/*実行結果
7 は一桁の正の整数です
*/

上は実際に比較演算子や論理演算子を使用する際の例になります。比較演算子と論理演算子を組み合わせて一桁の正の整数かどうかを判定するプログラムです。

ショートステートメント

// code:2-4
func pow(x, n, lim float64) float64 {
    // ①
	if v := math.Pow(x, n); v < lim {
		return v
	}
	return lim
}

func main() {
	fmt.Println(
		pow(3, 2, 10),
		pow(3, 3, 20),
	)
}
/*実行結果
9 20
*/

上では、pow()という関数を実装しています。冪乗(x^n)を求め、その値とlimの値の内、小さい方を返します。
注目して欲しいのは①の部分で冪乗を変数vに代入することと、vとlimを比較することを同時に書いています。Goではこのように、if文を書く際に記述を短縮することができます。

Switch文

繰り返しはfor文しかありませんでしたが、条件分岐にはif文の他にSwitch文があります。

switch 状態 {
case ケース1:
    // ケース1で実行する処理
case ケース2:
    // ケース2で実行する処理
・
・
default:
    // どの条件にも当てはまらなかった時の処理
}

switchは与えられた状態と合致するケースの処理を実行します。defaultを設定しておくとどのケースにも合致しなかった場合の処理も記述できます。他の多くの言語だとケースの最後にはbreakが必要ですがGoはbreakを書く必要はありません。

// code:2-5
package main

import (
	"fmt"
	"runtime"
)

func main() {
	fmt.Print("Go runs on ")
	switch os := runtime.GOOS; os {
	case "darwin":
		fmt.Println("OS X.")
		fmt.Println(os)
	case "linux":
		fmt.Println("Linux.")
		fmt.Println(os)
	default:
		// freebsd, openbsd,
		// plan9, windows...
		fmt.Printf("%s.\n", os)
	}
}

上の例は使用しているOSによってメッセージを変えるプログラムです。ここでもif文と同様のショートステートメントが使えます。

// code:2-6
func a(x int) string {
	var msg string
	switch {
	case x/10 == 0:
		msg = "一桁"
	case x/100 == 0:
		msg = "二桁"
	case x/1000 == 0:
		msg = "三桁"
	default:
		msg = "四桁以上"
	}
	return msg
}
func main() {
	fmt.Println(a(5))
	fmt.Println(a(123))
	fmt.Println(a(5345))
}
/*実行結果
一桁
三桁
四桁以上
*/

上は入力された整数の桁数を求めるプログラムです。ここでは状態を書かずに、ケースに引数xを10, 100, 1000それぞれで割った値が0と一致するかどうかという条件を書くことでケース分けをしています。このように、状態を書かずケースに条件を書くとif-elseを連続で書いた処理と同じことができます。

遅延処理

Defer

DeferはGo特有の特徴を持った遅延処理です。

defer 処理

上のように処理の手前にdeferをつけるだけで遅延処理になります(Goは関数を基本としているので複雑な処理を遅延させる場合は関数にすると良いでしょう)
具体的な例で使い方を見て見ましょう。

// code:2-7
func main() {
	defer fmt.Println("defer:hello")
	fmt.Println("hello")
	
	defer fmt.Println("defer:world")
	fmt.Println("world")
}
/*実行結果
hello
world
defer:world
defer:hello
*/

main関数の処理の順序と実行結果の出力順序を見ていただくとわかるように、deferのついた処理は他の処理の後に実行されます。また、記述されている順序と逆の順序、つまり下から実行されていることがわかります。

例えばファイル操作は、開いて編集したあとは必ず閉じないと他のプログラムからそのファイルにアクセス出来なくなったり問題が発生します。開いたあとの操作は繰り返したり分岐したりと複雑になり閉じる処理を複数箇所書く必要が出てくるかもしれません。以下のようにDeferで最初に閉じる処理を宣言しておけば、操作に関係なく処理の最後に実行されます。

// code:2-8
func main() {
    f := createFile("/tmp/defer.txt")
    defer closeFile(f)
    writeFile(f)
}

このようにDeferは処理の最後にず実行したい処理がある時に有効です。

Exercise: Loops and Functions

最後に該当するエクササイズの概要と私の解答を載せておきます。参考までに。
私の解答