ソースコードの表現
Go言語のソースコードはUTF-8でエンコードされたUnicodeテキストです。大文字と小文字は別の文字として区別されます。
コメント
コメントは、行コメント(//)とブロックコメント(/* */)の2種類です。入れ子にはできません。
// 行コメント(行末まで)
/*
ブロックコメント(複数行)
*/改行を含まないブロックコメントは空白1つとして扱われます。一方、行コメントや改行を含むブロックコメントは改行として扱われます。
識別子とキーワード
識別子は、変数、関数、型、パッケージなどに付ける名前です。先頭は文字で始まり、2文字目以降には数字も使えます。文字にはUnicodeの文字とアンダースコア_が含まれます。
アンダースコア_は小文字として扱われるため、識別子の先頭に置くこともできます。先頭が小文字または_の名前はパッケージ外から参照できません。外部に公開されるのは、先頭が大文字の名前だけです。
var _count int // 先頭が_なのでパッケージ外からは見えない
var count int // 先頭が小文字でも同様に見えない
var Count int // 先頭が大文字なら他パッケージから見える次の25語は構文を定義するためのキーワードであり、識別子には使えません。
break default func interface select
case defer go map struct
chan else goto package switch
const fallthrough if range type
continue for import return var一方、true、false、nilや型名、組み込み関数はキーワードではなく事前宣言された識別子です。キーワードとは異なり、同名の変数として再定義できてしまいます。
true := 1
// var if int = 1 // コンパイルエラーセミコロン
Go言語の構文上、文末にはセミコロンが必要です。ただし、字句解析の段階でコンパイラがセミコロンを自動で補うため、ソースコードに;を直接書く機会はほとんどありません。
行末のトークンが次のいずれかである場合、その直後にセミコロンが自動挿入されます。
- 識別子
- リテラル
break、continue、fallthrough、return++、--、)、]、}
明示的に書いた;もそのまま解釈され、行末にあっても二重に挿入されることはありません。
また、閉じ括弧)や}の直前ではセミコロンを省略できます。改行がない1行コードでも、末尾の括弧の手前に;を書かずに済みます。
func inc(x int) int { return x + 1 }改行を含むブロックコメントは改行として扱われるため、直前のトークン次第でセミコロンが自動挿入されます。
a = b /* 改行を含むコメントは改行扱いになる
*/ + c // コンパイルエラー: a = b; + c と解釈されるifやforなどの制御構文では開き括弧{を次の行に置くことができません。条件式の直後で改行するとセミコロンが補われてしまい、構文エラーになります。そのため、開き括弧{は同じ行に書きます。
if x < 0 {
return -x
}if x < 0
{
// コンパイルエラー: if x < 0; と解釈される
}変数宣言
varで型と初期値を明示できます。型を省略すると右辺から推論します。関数内では:=による短い変数宣言も使えます。
var x int = 10
var y = 20 // 型推論でint
z := 30 // 短い変数宣言(関数内のみ)パッケージ直下では:=は使えず、varが必要です。
package demo
var count = 0 // OK
// n := 1 // コンパイルエラー: パッケージレベルでは:=不可宣言した変数を使わないとコンパイルエラーになります。意図的に捨てるときは_へ代入します。
x := 1
_ = x同じブロック内で複数の変数をまとめられます。
var (
host = "localhost"
port = 8080
)複数の変数に同時代入することもできます。関数や一時変数を使わなくても変数の値を入れ替えることもできます。
func swap(a, b int) (int, int) { return b, a }
x, y = 1, 2
x, y = swap(x, y) // 2, 1
x, y = y, xゼロ値
Go言語には未初期化のローカル変数はなく、宣言された変数は常にゼロ値か、明示的な初期化値を持ちます。
| 型 | ゼロ値 |
|---|---|
| 数値 | 0 |
bool | false |
string | "" |
| ポインタ、スライス、マップ、チャネル、関数、インターフェース | nil |
| 構造体 | 各フィールドのゼロ値 |
| 配列 | 各要素のゼロ値 |
nilは使うための内部のデータ構造がまだ用意されていない状態を表します。操作がその内部構造への読み書きを前提にするとパニックになります。内部構造がなくても意味がある操作や、appendなどの必要ならこちらで確保する操作は動きます。
var m map[string]int // m == nil
// m["x"] = 1 // ハッシュ表が無いのでpanic
m = make(map[string]int) // または m = map[string]int{}
m["x"] = 1
var s []int
s = append(s, 1) // 要素を格納する配列を確保して動く定数宣言
定数はコンパイル時に評価できる式に限られます。変数の参照や関数呼び出しは定数式にならず、コンパイルエラーになります。
const Pi = 3.14159
const (
StatusOK = 200
StatusNotFound = 404
)
var limit = 10
// const max = limit // コンパイルエラー: 変数は定数式に使えない
// const n = len([]int{1, 2, 3}) // コンパイルエラー: スライスの長さは定数にならない
// const now = time.Now() // コンパイルエラー: 実行時の値は使えないiota
定数宣言ブロック内でiotaは行ごとに0から増える連番です。新しいconstブロックを開くとiotaは0にリセットされます。
const (
Sunday = iota // 0
Monday // 1
Tuesday // 2
)式と組み合わせた例です。各行でiotaの値が先に決まり、その行の式に代入されます。
const (
_ = iota // 0を捨てる
KB = 1 << (10 * iota) // 1024
MB // 1048576
GB // 1073741824
)オペランドと演算子
オペランドは演算の対象となる値や式であり、演算子はそれらを結びつける記号です。演算子はオペランドの数に応じて二項演算子と単項演算子に分かれます。
二項演算子
二項演算子は2つのオペランドの間に置きます。
| 種類 | 演算子 |
|---|---|
| 算術 | + - * / % |
| ビット | & | ^ &^ << >> |
| 比較 | == != < <= > >= |
| 論理 | && || |
a, b := 1, 2
sum := a + b // 3
masked := a & b // 0
ok := a < b // true
both := true && ok // true単項演算子
単項演算子はオペランドの直前に置きます。Go言語では前置のみをサポートしています。
| 演算子 | 意味 |
|---|---|
+ | 値はそのまま |
- | 正負の反転 |
! | 真偽値の反転 |
^ | ビットの反転 |
* | ポインタが指す値の参照 |
& | アドレスの取得 |
<- | チャネルからの値の受信 |
pos := +10 // 10
neg := -10 // -10
on := true
stopped := !on // false
flipped := ^uint8(0b00001100) // 0b11110011
var n int = 10
p := &n
v := *p // 10
ch := make(chan int, 1)
ch <- 1
got := <-ch // 1算術演算子
| 演算子 | 意味 | 対象 |
|---|---|---|
+ | 加算、文字列の連結 | 整数、浮動小数点、複素数、文字列 |
- | 減算 | 整数、浮動小数点、複素数 |
* | 乗算 | 整数、浮動小数点、複素数 |
/ | 除算。整数除算はゼロ方向へ切り捨て | 整数、浮動小数点、複素数 |
% | 剰余 | 整数 |
& | ビット積 | 整数 |
| | ビット和 | 整数 |
^ | 排他的論理和 | 整数 |
&^ | 指定ビットの消去。右辺で1のビット位置を0にする | 整数 |
<< | 左シフト。右オペランドは0以上 | 整数 |
>> | 右シフト。右オペランドは0以上 | 整数 |
a, b := 10, 3
sum := a + b // 13
joined := "go" + "lang" // "golang"
diff := a - b // 7
prod := a * b // 30
quot := a / b // 3
negQuot := (-10) / 3 // -3
rem := a % b // 1
x, y := uint8(0b1100), uint8(0b1010)
bitAnd := x & y // 8
bitOr := x | y // 14
bitXor := x ^ y // 6
bitClear := x &^ y // 4
shiftLeft := x << 1 // 24
shiftRight := x >> 1 // 6比較演算子
| 演算子 | 意味 | 対象 |
|---|---|---|
== | 等しい | 比較可能な型 |
!= | 等しくない | 比較可能な型 |
< | より小さい | 順序付け可能な型 |
<= | 以下 | 順序付け可能な型 |
> | より大きい | 順序付け可能な型 |
>= | 以上 | 順序付け可能な型 |
順序付け可能な型は整数、浮動小数点、文字列です。boolやポインタは等値比較はできても順序比較はできません。
スライス、マップ、関数は値同士を比較できませんが、いずれも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: スライス、マップ、関数はnilと比較可能論理演算子
| 演算子 | 意味 |
|---|---|
&& | 論理積 |
|| | 論理和 |
! | 論理否定 |
&&と||は短絡評価します。&&は左辺がfalseなら右辺を評価しません。||は左辺がtrueなら右辺を評価しません。右辺に関数呼び出しなどの副作用がある場合でも、評価されない限り実行されません。
func sideEffect(n int) int { return n }
active := true
paused := !active // false
if false && sideEffect(2) == 2 {
// 左辺がfalseのためsideEffectは評価されない
}
if true || sideEffect(2) == 2 {
// 左辺がtrueのためsideEffectは評価されない
}++と--
| 演算子 | 意味 |
|---|---|
++ | 1増やす |
-- | 1減らす |
文であり、式としては扱えません。前置の++iはなく、f(i++)のように式のオペランドに含めることもできません。
i := 0
i++
i--
// f(i++) // コンパイルエラー: 文のため式の中では使えない型変換
二項演算子のオペランドは、シフト演算と非型付き定数を除き同一の型でなければなりません。型付きの変数同士に暗黙の数値変換はないため、intとfloat64をそのまま計算するとコンパイルエラーになります。一方のオペランドが非型付き定数なら、相手の型で表現できる値であればその型として使えます。
var x int = 1
var y float64 = 2
// z := x + y // コンパイルエラー: 型の異なる変数同士は演算不可
mixed := float64(x) + y
sum := x + 2 // 非型付き定数2はintとして扱われるifやforの条件式と論理演算子のオペランドは真偽値に限ります。boolのほか、基底型がboolの型も使えます。数値や文字列からboolへの暗黙変換はなく、bool(n)のような明示的な変換もありません。真偽判定には比較式を用います。
n := 1
s := "go"
var err error
// if n { } // コンパイルエラー: 条件式は真偽値が必要
// 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演算子の優先順位
二項演算子
仕様では優先度1から5まで定義されており、数値が大きいほど強く結合し、先に計算されます。
| 優先度 | 演算子 |
|---|---|
| 5 | * / % << >> & &^ |
| 4 | + - | ^ |
| 3 | == != < <= > >= |
| 2 | && |
| 1 | || |
同じ優先度の二項演算子は左から右へ結合します。たとえば42 + a - bは(42 + a) - bと解釈されます。
n := 1 + 2*3 // 乗算が優先され7になる単項演算子
単項演算子は、すべての二項演算子よりも強く結合します。++と--は文であるため優先順位の定義には含まれません。
x := uint8(0b1100)
n := ^x >> 1 // 単項演算の^xが優先されるため121評価順序
関数呼び出し、メソッド呼び出し、チャネル受信、二項論理演算は、記述された順序に従って左から右へ評価されます。たとえばf() + g()ではf()が先に実行されてからg()が実行されます。関数の引数同士も同様で、sub(next(), next())では左側のnext()が先に呼び出されます。
一方、変数の参照と関数呼び出しの相対的な評価順序は仕様で定められていません。
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
n := 1
inc := func() int { n++; return n }
pair := []int{n, inc()} // nの参照とinc()の実行順序が未規定のため[1, 2]または[2, 2]ブロック
ブロックは宣言と文のグループです。波括弧{ }で囲みます。関数本体、制御構文の本体、ifの初期化文を含む全体などがブロックを形成します。
スコープ
識別子は宣言されたブロックとその内側のブロックで有効です。内側のブロックで同名を宣言すると、外側の名前はそのブロック内では隠れます。
x := 1
y := 2
{
fmt.Println(y) // 2
x := 3
fmt.Println(x) // 3
}
fmt.Println(x) // 1再宣言と:=
:=は、左辺に少なくとも1つ新しい変数があるときに使います。すでにそのブロックで宣言されている名前には代入され、それ以外は新たに宣言されます。
x, y := 1, 2
x, z := 3, 4 // xは代入、zは新規ラベルのスコープ
ラベルは、Outer:のように文の前に付ける名前です。入れ子のループで外側へ抜けるときはbreak Outer、外側ループの次の反復へ進むときはcontinue Outerと書きます。gotoの飛び先にも使います。
Outer:
for i := 0; i < 3; i++ {
for j := 0; j < 3; j++ {
if j == 1 {
break Outer // 内側も外側も抜け、両方のループを終了
}
}
}Outer:
for i := 0; i < 3; i++ {
for j := 0; j < 3; j++ {
if j == 1 {
continue Outer // 内側を抜け、外側ループの次のiへ
}
}
}i := 0
Again:
fmt.Println(i)
i++
if i < 3 {
goto Again
}ラベルはそれを含む関数内でのみ有効です。別の関数のラベルには飛べません。