文法の概要編集

Hello World編集

Go/実行の方法の再掲載です。

コード例
package main

import "fmt"

func main() {
	fmt.Println("Hello, World")
}

文法のあらまし編集

ソースファイルのトップレベルの構造編集

Goの1つのソースファイルは

パッケージ節
パッケージ節はキーワード package で始まり、同じパッケージに属するファイルを識別することと、インポート宣言のデフォルトのパッケージ名を指定するために使われます。
package main
外部パッケージのインポート
パッケージのインポートはキーワード import を使って宣言します。
import "fmt"
例2
import (
	"fmt"
	"math"
)
その他の実装部分
いわゆる本体

このように3つのパートに別れ、必ずこの順序である必要があります。 例えば、途中で思い立ってインポートはできません。

エントリーポイント編集

プログラムの開始点は、エントリーポイントと呼ばれます。 Goでは、エントリーポイントは「main パッケージの main関数」と決まっています。

関数編集

Goでは、サブルーチンやプロシージャと呼ばれる手続きも含めて関数として表現します。

関数の例
package main

import "fmt"

func main() {
	fmt.Println(subr(3, 2))
}

func subr(x, y int) (int, int) {
	return x + y, x - y
}
実行結果
5 1
解説
func subr(x, y int) (int , int) {
xy が(int型の)仮パラメーターで、戻値をそれぞれintで返します。
subrの呼び出しは前方参照となっていますが、Go ではパッケージ内の識別子の参照とその依存関係は宣言などのプログラミング上の負荷なく解決されます。もし循環参照があればコンパイル時に診断されます。

変数編集

package main

import "fmt"

var i int
var j int = 42
var k = 69

func main() {
	a := 2.0
	fmt.Println(i, j, k, a)
}
実行結果
0 42 69 2
変数宣言
var i int
のように var キーワードを使い、C言語系とは語順が違います(Pascalに近い語順)。
初期化を伴った変数宣言
var j int = 42
変数は、宣言と同時に初期化できます(初期化しない変数の値はゼロ値になります)。
型推定
var k = 69
宣言と同時に初期化した場合、型名を省略できます。その場合の変数の型は初期化した値から型推論[1]されます。
短い変数宣言
	a := 2.0
短い変数宣言は関数の中でだけ許されます、この場合の i の型は初期値 123 から int と型推論されます。
:= はPascalの代入を彷彿とさせますが、Goでは関数スコープあるいは制御構文スコープの変数宣言です。
C++では
auto i = 123;
のようにキーワード auto を型名の位置に使い型推論を表現しますが、キーワードを増やさないことが特徴の Go では、単に型名を省略したり専用の初期化付宣言構文で表現します。
また auto がないので戻り値を型推論することはできません。

定数編集

package main

import "fmt"

// const i int ← 定数は初期化が必須なのでこれはエラー(ゼロ値とはならない)
const j int = 42
const k = 69

const (
	zero = iota
	one
	two
	three
	_
	five
)

func main() {
	// const a := 2.0 短い変数宣言で定数は宣言できない
	fmt.Println(j, k)
	fmt.Println(zero, one, two, three, five)
}
実行結果
42 69
0 1 2 3 5
定数は代入が許されない変数で、このため宣言時の初期化が必須です。
( ) で囲むとグループ化して定数の宣言をできます。
また、iota を初期値とすると 0 から始まる等差数列を作れます。
iota はC言語の列挙型の様な用途に使えます。

グループ化編集

import, var, const の宣言は ( ) でグループ化できます。

package main

import (
	"fmt"
	"math"
)

var (
	f float64
	i int
)

const (
	PI_2 = math.Pi / 2
	PI_4 = math.Pi / 4
)

func main() {
	fmt.Println(f, i, PI_2, PI_4)
}
実行結果
0 0 1.5707963267948966 0.7853981633974483

演算子編集

演算子の例
package main

import "fmt"

func main() {
	a, b, c := 42, 12, 3
	t, f := true, false

	/* 単項演算子 */
	fmt.Println(+c) // 単項プラス
	fmt.Println(-c) // 単項マイナス
	fmt.Println(^c) // ビット反転(C言語系の ~ ではなく ^)
	fmt.Println(!t) // 論理否定

	/* 二項演算子 */
	fmt.Println(a * b)  // 乗算
	fmt.Println(a / b)  // 除算
	fmt.Println(a % b)  // 剰余
	fmt.Println(a << c) // 左シフト
	fmt.Println(a >> c) // 右シフト
	fmt.Println(a & c)  // ビットごとの論理積
	fmt.Println(a &^ c) // ビットクリア( &^ でトークン)
	fmt.Println(a & ^c) // ビットクリア(これとおなじ)

	fmt.Println(a + c) // 加算
	fmt.Println(a - c) // 減算
	fmt.Println(a | c) // ビットごとの論理和
	fmt.Println(a ^ c) // ビットごとの排他的論理和

	fmt.Println(a == b) // 一致
	fmt.Println(a != b) // 不一致
	fmt.Println(a < b)  // より小
	fmt.Println(a <= b) // より小または一致
	fmt.Println(a > b)  // より大
	fmt.Println(a >= b) // より大または一致

	fmt.Println(t && f) // 論理積(短絡評価あり)

	fmt.Println(t || f) // 論理和(短絡評価あり)
}
実行結果
3
-3
-4
false
504
3
6
336
5
2
40
40
45
39
43
41
false
true
false
false
true
true
false
true
  • 代入は、演算子ではなく文です。
  • インクリメントおよびデクリメントは、演算子ではなく文です。
  • Goには三項演算子(条件演算子; 式 ? 値1 : 値2 )はありません。
  • 冪乗演算子もないので、Goでは結合方向(同じ優先度の二項演算子が続いた場合の評価順序)は左からです。

代入編集

Goの構文要素に文・式・演算子がありますが、代入は文です。 C言語系の言語では代入は式ですが、Goでは文であるところが異なります。

代入が式で有ることで、a = b = 42; の様な多重代入はできず、if文などの構造構文の条件式で代入はできません。 このことで、右結合であるか左結合であるかへの配慮を必要とする状況を劇的に少なくなりました。

代入の例
package main

import "fmt"

func main() {
	x, y := 42, 123 // 2つの変数を宣言し型なしの定数で初期化

	fmt.Println("x =", x)
	fmt.Println("y =", y)

	// x = y = 10      ・・・ Go では(代入は文なので)は構文エラー

	x, y = y, x // Go では多重代入が可能
	fmt.Println("x =", x)
	fmt.Println("y =", y)
}
実行結果
x = 42
y = 123
x = 123
y = 42

インクリメント・デクリメント編集

C言語系の言語には n++ という構文、 n += 1 すなわち n = n + 1 を表すシンタックスシュガーがあります。 C言語系の言語ではインクリメントは式ですが、Goでは文であるところが異なります。 また、Goのインクリメント文は後置インクリメントだけです(そもそも式ではないのでインクリメントと値の参照の順序は問題になりません)。

インクリメント・デクリメントの例
package main

import "fmt"

func main() {
	x, y := 42, 123 // 2つの変数を宣言し型なしの定数で初期化

	fmt.Println("x =", x)
	fmt.Println("y =", y)

	// x = y++  ・・・  Go ではインクリメントは文なので構文エラー

	x++
	// ++y インクリメントは後置のみ
	y--
	fmt.Println("x =", x)
	fmt.Println("y =", y)
}
実行結果
x = 42
y = 123
x = 43
y = 122

構造構文編集

選択文編集

選択文には、if文switch文select文があります。

if文編集

これだけだと「C言語と同じアレか」と思われるでしょうが、Goでは、

if文
if x > max {
   max = x
}

のように条件式を囲むカッコが要らず { } が必須です(擬似コードではなく、動くプログラムの一部です)。また、

if文スコープの変数
if x := f(); x < y {
	return -1
} else if x > y {
	return +1
} else {
	return 0
}

のように、条件式の前に「単純な文」を置くことができ、とりわけ上の例のように「短い変数宣言」で宣言された変数はif文スコープになります。

懸垂else問題
C言語のような、if分に(ブロック { ... } だけではなく)単文を許す言語では
if (a)
    if (b)
        x();
else // ← 懸垂else
    y();
は、実際には
if (a) {
    if (b) {
        x();
    } else {
        y();
    }
}

とelseは最寄りのifと結合します。

Goでは、{ … } を必須とし懸垂else問題を回避し、if文スコープの変数と単文の組合わせの座りの悪さ[2]を回避するとともに、"if" と "{" に条件式(とオプショナルな単純な文)があると決め打ちできるので構文解析の簡素化に役立っています。


switch文編集

switch文
switch oct {
default: error()
case 0, 2, 4, 6: even()
case 1, 3, 5, 7: odd()
}

switch x := f(); {  // 条件式は省略されると true とみにす。この場合 ; は必須。
case x < 0: return -x
default: return x
}

switch {
case x < y: lt()
case x > y: gt()
case x == y: eq()
}
C言語と異なり、case節の式は定数式でなくても構いません。
if文と同じ様に条件式のカッコは不要で、条件式の前に「単純な文」を書けます(必須ではありません)。
条件式は省略可能で、省略すると true が仮定されます。
また break するのが標準動作で、次のcase節に対応する文に処理を継続する場合は、fallthrough文 を使います。

反復文編集

Goの反復文はfor文だけです。これもC言語とは異なる文法で

for文
for a < b { // C言語の while文 に相当しますが、やはり条件式にカッコは不要です 
	a *= 2 
}

for i := 0; i < 10; i++ { // ここでも :=を使った短い変数宣言
	f(i)
}

for { // C言語の for (;;) あるいは while(1) ⇒ 無限ループ
	f2(i)
}

var a [10]string
for i, s := range a { // range句は Go独特
	// i の型は int
	// s の型は string
	// s == a[i]
	g(i, s)
}

for _, s := range a { // range句は Go独特
	// インデックスが不要なら、 _ に置きかえる
	// s の型は string
	// s == a[i]
	h(s)
}
rangeは、stringの他、配列・スライス・マップ・チャンネルとも組合わせることができます。

小まとめ編集

このようにGoの構文(や意味論)はC言語C++あるいはJavaとは大きく異なるので、ユーザーには予備知識としてC言語などの知識は必要ありません。 むしろC言語ファミリーのプログラマーは、同じキーワードが別の意味もつ事が多々あり、違いを常に意識する必要があるという意味で転換教育には頭の切り替えが必要です。

他方、JavaScript などスクリプティング言語経験者は ; の自動挿入など馴染みの深い文法機能に親近感が持てるかも知りません(JSのからC/C++への転換教育で1つの大きなハードルになります)。

構文は懸け離れていますが、字配りに制約のある文法ということではPythonとGoは近いものがあります。

else節を伴ったif文
	if x < 0 {
		fmt.Println("マイナス")
	} else {
		fmt.Println("プラス")
	}

elseの前では改行できない
	if x < 0 {
		fmt.Println("マイナス")
	}
    else {
		fmt.Println("プラス")
	}
とelseの前で改行すると
./prog.go:11:3: syntax error: unexpected else, expecting }
の様に文法的に違法となります(改行すると、; が自動挿入されます)。
また if の行末の { の前も改行不能です(改行すると、やはり ; が自動挿入されます)。

また、Goには三項演算子 a ? b : c もありません。

このように、同じロジックを書くと大きな差異を生じないよう慎重に言語設計がなされています。

クラスはないがメソッドはある編集

Goには、C++ や Java のようなクラス(class)はありません。またJavaScriptやLuaの様なプロトタイプもありません。

しかし、Goでは組込み型以外の型(複合型とユーザー定義型)に対してメソッドを定義することができます。 メソッドは、レシーバー引数を取る特殊な関数です。 レシーバーは、funcキーワードとメソッド名の間にある、自身の引数のリストで表されます。 つぎの例では、 Abs メソッドは cmplx という名前の Complex 型のレシーバを持つことを意味しています。

メソッドの例
package main

import (
	"fmt"
	"math"
)

type Complex struct {
	Real, Imag float64
}

func (cmplx Complex) Abs() float64 {
	return math.Hypot(c.Real, c.Imag)
}

func main() {
	c := Complex{3, 4}
	fmt.Println(c.Abs())
}

また、Goはインターフェース型を持ちます。インターフェース型はインターフェース (interface) と呼ばれるメソッド集合を指定する機能で、クラス階層における継承の機能を受け持ちます(アプローチは全く異なります)。

また、入れ子の構造体の入れ子側の構造体(埋め込み構造体)のフィールドは、埋め込み親のフィールドの構文で参照できるので、継承的な使い勝手を提供します。


コメント編集

Goでは、 // から行末までがコメントになります。 また、/**/で複数行に渡るコメントを書くこともできます。


脚註編集

  1. ^ The Go Programming Language Specificationでは、型推論(type inference)という用語の使用は慎重に避けていて、型なし(untyped)という用語が型推論の文脈で多く使われ、例えば 123 のようなリテラルを型なし定数(untyped constant)と呼んでいます。
  2. ^ C++のAT&T2.0ではfor文で宣言された変数のスコープはfor文の外でしたがANSIC++ではfor文の中になりました。それはそれで問題ないのですが、for文の本体は複文だけでなく短文も許すので一見するとスコープがわかりにくいと不評です。for文で変数を宣言できる仕様はC言語にもそのままバックポートされたので、現在ではC言語のfor文もスコープ(とシャドーイング)に注意が必要です。Goのfor文はif文同様 { … } が必須なのでfor文×短文のややこしさはありません。