式の概要
式は値を計算します。演算子とオペランドの組み合わせに加え、呼び出しや型変換なども式になります。
オペランドと演算子
オペランドは、演算の対象になる値や式です。演算子は、オペランドを組み合わせて別の値を得る記号やキーワードです。Goの演算子は、オペランドの数によって二項演算子と単項演算子に分かれます。呼び出し(len("go"))や型変換(float64(n))も式ですが、演算子の分類には含まれません。
二項演算子
二項演算子は、左と右の2つのオペランドの間に書きます。この記事後半の算術(+)、比較(==)、論理(&&)が二項演算子です。
a, b := 1, 2
sum := a + b // 二項: aとbの間の+ → 3
ok := a < b // 二項: aとbの間の< → true単項演算子
単項演算子は、オペランド1つの前に書きます(Goでは前置のみ)。+と-は符号、!は論理否定、^はビット反転、*はポインタの間接参照、&はアドレス取得、<-はチャネル受信です。^は二項ではXORです。++と--は文としてのみ使えるため、単項演算子には含まれません。
on := true
stopped := !on // 単項: onの前の! → false
var n int = 10
p := &n // 単項: &n
v := *p // 単項: *p → 10算術演算子
+ - * / %とビット演算& | ^ << >> &^があります。整数除算は切り捨てです。
a, b := 10, 3
sum := a + b // 13
diff := a - b // 7
prod := a * b // 30
quot := a / b // 3(切り捨て)
rem := a % b // 1
x, y := uint8(0b1100), uint8(0b1010)
bitAnd := x & y // 8(0b1000)
bitOr := x | y // 14(0b1110)
bitXor := x ^ y // 6(0b0110)
shiftLeft := x << 1 // 24
shiftRight := x >> 1 // 6
bitAndNot := x &^ y // 4(0b0100)比較演算子
== != < <= > >=は比較可能なオペランドに使えます。スライス、マップ、関数は互いに==できません(スライスはnilとの比較だけ可能)。配列は要素が比較可能なら==できますが、スライスは長さと配列への参照を持つ型であり、要素ごとの等価は言語が定義していません。ポインタのnil比較などは可能ですが、中身の等価が必要なときは別の手段を使います。
x, y := 1, 2
isLess := x < y // true
isLessOrEqual := x <= y // true
isGreater := x > y // false
isGreaterOrEqual := x >= y // false
isEqual := x == y // false
isNotEqual := x != y // true
// []int{1} == []int{1} // コンパイルエラー: スライス同士は比較不可
// map[int]int{1: 1} == map[int]int{1: 1} // コンパイルエラー: マップ同士は比較不可
// func() {} == func() {} // コンパイルエラー: 関数同士は比較不可
var pending *int
isNil := pending == nil // true論理演算子
&& || !はブール値に使います。&&と||は短絡評価されます。
&&は左のオペランドがfalseなら、結果はfalseと決まるため右を評価しません。||は左がtrueなら、結果はtrueと決まるため右を評価しません。右側に関数呼び出しなど副作用のある式を書いたとき、実際に評価されるかはこの規則で決まります。!は!activeのように直後の真偽値を反転します。
func sideEffect(n int) int { return n }
active := true // true
paused := !active // false
if false && sideEffect(2) == 2 { // → false(sideEffect は呼ばれない)
// 左が false のため sideEffect は呼ばれない
}
if true || sideEffect(2) == 2 { // → true(sideEffect は呼ばれない)
// 左が true のため sideEffect は呼ばれない
}++と--
++と--は文としてのみ使え、式の中では使えません。i++は単独の文としてのみ有効で、f(i++)のように式のオペランドにはできません。
i := 0
i++ // i--// f(i++) // コンパイルエラー型変換
異なる型の値を混ぜるときは、毎回明示的に変換します。Goには暗黙の数値変換がなく、intとfloat64をそのまま足すとコンパイルエラーになります。
var i int = 42
var f float64 = float64(i)
var u uint = uint(f)定数式の中では、表現可能な範囲でより広い型に暗黙的に解釈される場合があります。変数どうしの演算では暗黙変換は起きません。
const c = 42
var f float64 = c // OK(無型定数42はfloat64に表現可能)
var i int = 42
// var g float64 = i // コンパイルエラー: 変数は暗黙変換されない
var g float64 = float64(i)
var x int = 1
var y float64 = 2
// z := x + y // コンパイルエラー: 型が違う変数同士は演算できないtruthy/falsyがない
数値や文字列、nil相当の値が条件式で自動的に真または偽として扱われる考え方(truthy / falsy)がありますが、Goはこれを採用していません。
ifやforの条件、&& || !のオペランドはbool型の式に限られます。数値どうしと同様、他型からboolへの暗黙変換はありません。bool(n)のような明示的な変換も定義されていません。変数どうしは型を合わせる必要がありますが、intやstringからboolへは変換できないため、比較式で真偽を書きます。
n := 1
s := "go"
var err error
// if n { } // コンパイルエラー: 条件はbool型
// if s { } // コンパイルエラー
// ok := bool(n) // コンパイルエラー: intからboolへの変換はない
hasN := n != 0 // true
hasS := s != "" // true
hasErr := err != nil // false互換性のない型への変換はコンパイルエラーです。
定数式
定数式はコンパイル時に評価できます。定数の宣言や配列の長さなどに使われます。
const size = 1 + 2*3
var arr [size]int演算子の優先順位
複数の演算子が並ぶ式では、結びつきの強さでどの部分が先にまとめられるかが決まります。
二項演算子
二項演算子は左右2つのオペランドの間に書きます。仕様では優先度1(弱い)から5(強い)が付けられています。
| 優先度 | 演算子 |
|---|---|
| 5 | * / % << >> & &^ |
| 4 | + - | ^ |
| 3 | == != < <= > >= |
| 2 | && |
| 1 | || |
同じ優先度の二項演算子は左から右に結びつきます。42 + a - bは(42 + a) - bと同じ意味です。
n := 1 + 2*3 // 2*3 が先 → 7単項演算子
単項演算子はオペランド1つの前に書き、どの二項演算子よりも強く結びつきます。+ - ! ^ * & <-が対象です。^は単項ではビット反転、二項ではXORです。++と--は文としてのみ使えるため、優先順位の表には含まれません。
評価順序
式を評価するとき、まとまったオペランドは一般に左から右に評価されます。一方、関数呼び出しの複数の引数をどの順で評価するかは仕様で定められていません。引数の式が共有状態を更新するような副作用を持つとき、評価順によって結果が変わります。
func f() int { return 1 }
func g() int { return 2 }
sum := f() + g() // 左のf()、次にg()の順で評価 → 3
var seq int
func next() int {
seq++
return seq
}
func sub(a, b int) int { return a - b }
sub(next(), next()) // 先に評価された方の引数が1、後が2。結果は-1か1になりうるセレクタとインデックス
式の結果から、.または[ ]を使って一部を取り出します。
セレクタ
値.名前の形です。構造体のフィールドやメソッドにアクセスします。
type Item struct{ Name string }
func (Item) Tag() string { return "item" }
it := Item{Name: "a"}
field := it.Name // セレクタ: フィールド Name → "a"
label := it.Tag() // セレクタ: メソッド Tag → "item"インデックス
値[添字]の形です。配列、スライス、マップ、文字列から要素を選びます。
nums := [2]int{10, 20}
fromArray := nums[0] // 配列 → 10
xs := []int{10, 20}
fromSlice := xs[1] // スライス → 20
m := map[string]int{"x": 1}
fromMap := m["x"] // マップ → 1
word := "go"
fromString := word[0] // 文字列 → 'g'(byte)関数呼び出し
関数やメソッドを呼ぶ式です。結果は戻り値の型になります。
import "strconv"
result := strconv.Itoa(42) // "42"