このドキュメントは、http://golang.org/doc/go_for_cpp_programmers.htmlの翻訳です。


コンセプトの違い
構文
定数
スライス
値の作成
インタフェース
ゴルーチン
チャネル

Go言語は、システムプログラミング言語であり、目標は、C++のような多目的システム言語です。このドキュメントは、熟練したC++プログラマ向けに書かれたもので、GoとC++との違いについて説明します。両言語の類似点については、このドキュメントの範囲外です。

より一般的なGo言語の手引きは、チュートリアルまたは実践Go言語をご覧ください。

Go言語の詳細な解説は、Go言語仕様をご覧ください。

コンセプトの違い

  • Go言語には、コンストラクタやデストラクタを持つようなクラスはありません。Go言語では、クラスメソッド、継承によるクラスの階層、仮想関数が無い代わりとして、あとで解説するインタフェースが用意されています。なお、C++でもインタフェースは、テンプレートとして使われています。
  • Go言語では、ガーベージコレクションを採用しています。これは、明示的にメモリを開放するときは不必要(または適していません)ですが、このガーベージコレクション(の目標)は、近頃の高性能になったプロセッサ上では、高い効果を発揮します。
  • Go言語には、ポインタはありますが、ポインタの演算はできません。そのため、文字列内のバイトデータを、ポインタ型変数を使って、ひとつずつ取り出すことはできません。
  • Go言語の配列は、通常の値です。配列が関数パラメータとして使用されるときには、関数はその配列へのポインタではなく、コピーを受け取ります。ただし実際、ほとんどのケースでは、関数のパラメータとして配列の代わりに、内部に配列へのポインタを持つスライスを使用します。スライスについては、もう少しあとで解説します。
  • 文字列は言語でサポートされています。一度作成されると、文字列は変更できません。
  • ハッシュテーブルは言語でサポートされています。これはマップと呼ばれています。
  • 実行スレッドの分割と、それらの間における通信経路が言語でサポートされています。 これらについても、あとで解説します。
  • 特定の型(後述するマップとチャネル)は、値でなく参照渡しされます。つまり、関数にマップを渡したときは、マップはコピーされません。関数内でマップを変更すると、その変更は呼び出し側からも参照できます。C++的に言えば、これらは参照型であるとも考えられます。
  • Go言語では、ヘッダファイルを使いません。その代わりに、各ソースファイルは定義済みパッケージの一部となります。パッケージで定義したオブジェクト(型、定数、変数、関数)の名前が大文字で始まるとき、そのパッケージをインポートしている他のファイルから、そのオブジェクトにアクセスできます。
  • Go言語では、暗黙的な型変換をサポートしていません。異なる型同士の演算には、キャスト(Goでは変換と呼ばれる)が必要です。
  • Go言語では、関数のオーバロード、およびユーザによる演算子の定義はサポートしていません。
  • Go言語では、constvolatile修飾子はサポートしていません。
  • C++では、無効なポインタにはNULLや、単に0が使われますが、Go言語では、nilが使われます。

構文

宣言の構文は、C++と逆で、名前の後に型を記述します。型の構文は、C++とは異なり、変数が使われる方向とは一致しません。型の宣言は、単純に左から右へ読むことができます。

Go                           C++
var v1 int                // int v1;
var v2 string             // const std::string v2;  (ほぼ同じ)
var v3 [10]int            // int v3[10];
var v4 []int              // int* v4;  (ほぼ同じ)
var v5 struct { f int }   // struct { int f; } v5;
var v6 *int               // int* v6;  (ただしポインタの演算は不可)
var v7 map[string]int     // unordered_map<string, int>* v7;  (ほぼ同じ)
var v8 func(a int) int    // int (*v8)(int a);

通常、宣言は、宣言するオブジェクト名の前にキーワードを記述する形式をとります。このキーワードは、varfuncconsttypeのいずれかです。メソッドの宣言はすこし例外的で、宣言するオブジェクト名の前にレシーバを記述します。これについては、インタフェースの解説を参照ください。

キーワードの後ろに括弧を使用して、一連の宣言を行うこともできます。

var (
    i int
    m float64
)

関数の宣言の際、すべてのパラメータに名前を付けるか、もしくは、どのパラメータにも名前を付けないかを選べます。すなわち、名前を付けないパラメータと名前を付けたパラメータの混在はできません。同一の型のときは、次のようにグルーピングが可能です。

func f(i, j, k int, s, t string)

変数は、宣言時に初期化を行えます。初期化を行うときは型を指定できますが、これは必須ではありません。型を指定しないときは、初期化式の型がこの変数の型となります。

var v = *p

下で解説している定数についても参照してみてください。変数を明示的に初期化しないときは、型を指定しなければなりません。その場合、変数は暗黙的に、その型のゼロ値(0、nilなど)で初期化されるため、Go言語では初期化されていない値は存在しません。

関数内でのみ使用可能な、省略形式の宣言の構文には、変数と:=を使います。

v1 := v2

これは、下と等価です。

var v1 = v2

Go言語では、複数の代入が許されます。この代入は、それぞれ個別に処理されます。

i, j = j, i    // iとjのスワップ

関数は、括弧でリストすることで複数の戻り値を持つことができます。返された値は、変数リストへ代入して格納します。

func f() (i int, j int) { ... }
v1, v2 = f()

Go言語のコードでは、セミコロンはほとんど使いません。厳密には、Go言語のすべてのステートメントは、セミコロンで終わるのですが、非空行は、ステートメントが明らかに不完全な場合を除き、「行末」をセミコロンとして扱います。(厳密な規則は、言語仕様で定義しています) このため、改行の使用が許されないケースがいくつかあり、下の例のような記述をしてはいけません。

func g()
{                  // 不正
}

g()の後ろに挿入されたセミコロンにより、関数の定義ではなく、関数の宣言になってしまいます。同様に、次のようにも記述してはいけません。

if x {
}
else {             // 不正
}

セミコロンは、elseの前の}の直後に挿入されるので、構文エラーとなります

セミコロンによってステートメントが終了させられるので、C++同様にセミコロンを使い続けることもできますが、これは推奨するスタイルではありません。慣用的なGo言語のコードでは、不必要なセミコロンは省略します。実際には、forループの区切りや、一行内に複数の短いステートメントを記述したいとき以外は、すべて省略できます。

話題として取り上げてはいますが、セミコロンと波括弧の位置をいちいち気にするより、コードをgofmtプログラムを使って整形することを推奨します。このプログラムは、Go言語の統一スタイルを出力するので、フォーマットを気にすることなく、コーディングに専念できます。このスタイルは、最初は変に思われるでしょうが、慣れさえすれば他のどのスタイルに勝るとも劣りません。

構造体へのポインタを扱うときは、->の代わりに.を使ってください。したがって、構造体と構造体へのポインタは、構文的には同じです。

type myStruct struct { i int }
var v9 myStruct              // v9は、構造体型
var p9 *myStruct             // p9は、構造体へのポインタ
f(v9.i, p9.i)

Go言語は、ifの条件を囲む丸括弧を必要としません。forステートメントの式、switchステートメントの値も同様です。一方、ifforステートメントの本体を囲む波括弧は必須です。

if a < b { f() }             // 有効
if (a < b) { f() }           // 有効(条件は、丸括弧の式)
if (a < b) f()               // 無効
for i = 0; i < 10; i++ {}    // 有効
for (i = 0; i < 10; i++) {}  // 無効

Go言語には、whileステートメントがなく、do/whileステートメントも持ちません。forステートメントは、ループ条件だけで使用できるので、whileステートメントと同じことができます。条件も完全に省略すると、無限ループになります。

Go言語には、ラベルへのbreakcontinueが許されています。ラベルは、forswitchselectステートメントのいずれかに対して付けなければなりません。

switchステートメントのcaseラベルは、フォールスロー(下のケースの処理を続けて実行)しません。フォールスローさせたいときは、fallthroughキーワードを使います。これは、近接したcaseにも当てはまります。

switch i {
case 0:  // caseの本体は、空
case 1:
    f()  // fは、i == 0のときは呼び出されない!
}

ですが、caseは複数の値を持つことができます。

switch i {
case 0, 1:
    f()  // fは、i == 0 || i == 1のとき呼び出される
}

caseの値は、定数である必要はありません。整数である必要もなく、文字列やポインタなどのイコール比較演算子をサポートした型であれば、すべて使えます。また、switchの値が省略されると、デフォルトとしてtrueが使われます。

switch {
case i < 0:
    f1()
case i == 0:
    f2()
case i > 0:
    f3()
}

++--演算子は、ステートメント内でのみ使え、式の中では使えません。c = *p++とは記述できません。*p++は、(*p)++として解析されます。

deferステートメントは、このステートメント呼び出した側の関数がリターンした後に、指定した関数を呼び出したいときに使用します。

fd := open("filename")
defer close(fd)         // fdは、この関数がリターンしたときにクローズされる

定数

Goの定数は、型を持たないことがあります。これは、const宣言で名前が付けられた定数であっても、宣言時に型が与えられず、さらに初期化式に型のない定数だけを使ったときにも当てはまります。型を持たない定数から得られる値は、型を必要とする状況で使用されるときに、初めて型を持つようになります。定数は、通常の明示的な型変換を必要とせずに比較的自由に扱えます。

var a uint
f(a + 1)  // 型を持たない数値定数"1"は、uintの型になる

言語としては、型を持たない数値定数、および定数式には、サイズ上限を設けていません。上限が適用されるのは、定数が使われて型が必要となるときだけです。

const huge = 1 << 100
f(huge >> 98)

Go言語では、enumをサポートしていません。その代わりに、特殊な名前iotaを使って、ひとつのconst宣言内で、連続して増加する値を得ることができます。const内で、初期化式が省略されたときは、前の式が再利用されます。

const (
    red = iota   // red == 0
    blue         // blue == 1
    green        // green == 2
)

スライス

スライスは、概念的には、3つのフィールド(配列へのポインタ、長さ、キャパシティ(容量))を持った構造体です。スライスは、内部の配列の要素にアクセスするための[]演算子をサポートします。組み込み関数lenは、スライスの長さを返し、組み込み関数capは、キャパシティを返します。

配列、または別のスライスから、a[I:J]で新しいスライスを作成します。こうすると、aを参照し、開始インデックスがIで、インデックスJの前で終わる、新しいスライスが作成されます。このスライスの長さは、J - Iです。新しいスライスは、aが参照している配列と同じ配列を参照します。つまり、新しいスライスを使って行った変更は、aを使って参照可能です。新しいスライスのキャパシティは、単純にaのキャパシティからIを引いた値です。配列の場合は、キャパシティは、配列の長さと同じです。また、配列型のポインタを、スライス型の変数に代入することも可能です。var s []int; var a[10] intと宣言し、s = &aと代入することは、s = a[0:len(a)]と同じです。

すなわち、C++ではポインタを使っていた箇所のうち何カ所かの代わりに、Go言語ではスライスを使います。仮に、[100]byte型(100バイトの配列で、たぶんバッファとして使われる)の値を作成し、それをコピーせずに関数に渡したいときは、関数のパラメータを[]byte型として宣言し、配列のアドレスをを渡すようにすべきです。C++と異なり、バッファの長さ情報を渡す必要はありません。len関数を通して効率的にアクセスできるからです。

スライスの構文は、文字列にも使えます。文字列に対して使用すると、元の文字列の部分文字列を持った新しい文字列を返します。文字列は不変であるため、文字列のスライスでは、スライスの内容を格納するために新しくメモリを割り当てるようなことは行ないません。

値の作成

Go言語の組み込み関数newは、指定した型に、ヒープ領域を割り当てます。割り当てられた領域は、その型のゼロ値で初期化されます。例えば、new(int)とすると、新しくヒープ領域にintを割り当て、0で初期化した上で、その*int型のアドレスを返します。C++とは異なり、newは演算子でなく関数であるため、new intとすると構文エラーになります。

マップとチャネルの値は、組み込み関数makeを使って割り当てる必要があります。マップまたはチャネル型の変数を初期化指定せずに宣言すると、自動的にnilに初期化されます。make(map[int]int)と呼び出すと、新しく割り当てられた、map[int]int型の値を返します。(※makeは、ポインタではなく値を返す点に注意してください。) これは、マップとチャネルの値が、参照渡しされるということと関係しています。マップ型に対してmakeを呼び出すときは、オプションの引数で、マップに必要なキャパシティ(容量)を指定します。チャネル型に対してmakeを呼び出すときは、オプションの引数で、チャネルがバッファリングするキャパシティを指定します。このデフォルト値は0(バッファリングなし)です。

make関数は、スライスを割り当てるときにも用いられます。この場合は、内部の配列用にメモリを割り当て、それを参照するスライスを返します。1番目の引数は必須で、スライスの要素数を指定します。2番目の引数はオプションで、スライスのキャパシティを指定します。例えば、make([]int, 10, 20)のようにします。これは、new([20]int)[0:10]と同じです。Go言語ではガーベージコレクションされるため、新しく割り当てた配列は、返されたスライスへの参照がなくなった後、どこかのタイミングで破棄されます。

インタフェース

C++がクラス、サブクラス、テンプレートを持つ代わりに、Go言語には、インターフェースがあります。Go言語のインタフェースは、C++の純粋抽象クラス(データメンバを持たず、メソッドはすべて純粋仮想メソッド)に類似しています。しかしGo言語では、あるインタフェースで定義されたメソッドを持つ型は、そのインタフェースを実装しているとみなされます。継承していることを明示的に宣言する必要はありません。インタフェースの実装と、インタフェース自身とは完全に切り離されています。

メソッドは通常の関数定義のように見えますが、レシーバを持っている点が異なります。レシーバは、C++のクラスメソッドのthisポインタと似ています。

type myType struct { i int }
func (p *myType) get() int { return p.i }

このメソッド宣言は、myTypeと関連付けられ、レシーバは、関数の本体内でpとして使用されます。

メソッドは、名前がつけられた型に対して定義します。値を別の型に変換すると、新しい値は、古い型のメソッドではなく、新しい型のメソッドを持つようになります。

言語に組み込まれている型から新しい型を宣言し、そこにメソッドを定義することもできます。ただし、新しい型は、言語に組み込まれている型とは、別個の型になります。

type myInteger int
func (p myInteger) get() int { return int(p) } // 変換が必要
func f(i int) { }
var v myInteger
// f(v)は、不正
// f(int(v))は、有効。int(v)に定義済みメソッドがないため

インタフェースを定義します。

type myInterface interface {
	get() int
	set(i int)
}

myTypeが、このインタフェースを満たすように変更できます。

func (p *myType) set(i int) { p.i = i }

これで、myInterfaceをパラメータに取るすべての関数が、*myType型の変数を受け付けられるようになりました。

func getAndSet(x myInterface) {}
func f1() {
	var p myType
	getAndSet(&p)
}

言い換えると、myInterfaceをC++の純粋抽象基底クラスとみなしたとき、*myTypeに定義したsetおよびgetによって、*myTypeは、自動的にmyInterfaceから継承したことなります。また、型は同時に複数のインタフェースを満たせます。

匿名フィールドは、C++の子クラスのように、何かを実装するために用いられます。

type myChildType struct { myType; j int }
func (p *myChildType) get() int { p.j++; return p.myType.get() }

この例では、myTypeの子としてmyChildTypeを事実上、実装しています。

func f2() {
	var p myChildType
	getAndSet(&p)
}

setメソッドがmyChildTypeから事実上、継承されることになる理由は、匿名フィールドと関連付けられたメソッドは、外側の型のメソッドへと昇格するからです。この場合、myChildTypeは、myType型の匿名フィールドを持つため、myTypeのメソッドは、myChildTypeのメソッドにもなります。上の例では、getメソッドはオーバライドされ、setメソッドは継承されます。

これは、C++の子クラスと、全く同じというわけではありません。匿名フィールドのメソッドが呼び出されるとき、レシーバとなるのはフィールドであり、外側の構造体ではありません。言い換えると、匿名フィールドが持つメソッドは、仮想関数ではありません。仮想関数と同等の機能が必要であれば、インタフェースを使用してください。

インタフェース型の変数は、型アサーションと呼ばれる特殊な方法を使って、異なるインタフェース型の変数に変換できます。これは、C++のdynamic_castのように、実行時に動的に実行されます。dynamic_castと異なり、2つのインタフェース間における関連性の宣言は、一切不要です。

type myPrintInterface interface {
  print()
}
func f3(x myInterface) {
	x.(myPrintInterface).print()  // myPrintInterfaceへ型アサーション
}

myPrintInterfaceへの変換は、完全に動的に行われます。xの根底にある型(動的な型)に、printメソッドが定義されているとき、この型アサーションが機能します。

この変換は動的に行われるので、C++のテンプレートのようなジェネリックプログラミングを行うために使えます。これは、下のような最小限のインタフェースの値を操作することで行います。

type Any interface { }

コンテナは、Any型として作られますが、呼び出し側は、型アサーションによる型変換で、値の型を元に戻さなければなりません。型付けは、静的でなく動的であるため、C++のテンプレートのようにインラインでコンテナを操作する方法はありません。プログラム実行中は、コンテナの操作は確実に型チェックが行われますが、全ての操作は関数を経由する必要があります。

type iterator interface {
	get() Any
	set(v Any)
	increment()
	equal(arg *iterator) bool
}

ゴルーチン

Go言語で、新しいスレッド(goroutineと呼ばれます)を実行開始するには、goステートメントを使います。このgoステートメントは、カレントのゴルーチンとは別の、新しく作成されたゴルーチン内で関数を実行します。プログラム内の全てのゴルーチンは、同一アドレス空間を共有します。

ゴルーチンは内部的に、オペレーティングシステム内の複数のスレッド間に多重化されたコルーチンのように動作しますが、プログラムを組む上で、これらの詳細について気に掛ける必要はありません。

func server(i int) {
    for {
        print(i)
        sys.sleep(10)
    }
}
go server(1)
go server(2)

(注:この、server関数内のforステートメントは、C++のwhile (true)ループと同じです)

ゴルーチン(の目指すところ)は、低コストです。

関数リテラル(Go言語ではクロージャとして実装)は、goステートメントと合わせて使うと便利です。

var g int
go func(i int) {
	s := 0
	for j := 0; j < i; j++ { s += j }
	g = s
}(1000)  // 関数リテラルの引数に1000という値を渡している。

チャネル

チャネルは、ゴルーチン間の通信に使われます。また、チャネルにはどんな値でも送信できます。チャネル(の目指すところ)は、効率と低コストです。値をチャネルに送信するときは、単項演算子として<-を使用します。チャネルから値を受信するときは、二項演算子として<-を使用します。また、関数呼び出しでは、チャネルは参照渡しされます。

Go言語のライブラリでは、ミューテックスを提供していますが、ミューテックスの代わりに共有チャネルを使うこともできます。下のmanager関数を使った例では、値をアクセス制御として使用しています。

type cmd struct { get bool; val int }
func manager(ch chan cmd) {
	var val int = 0
	for {
		c := <- ch
		if c.get { c.val = val; ch <- c }
		else { val = c.val }
	}
}

この例では、同一のチャネルが入出力に使われています。このmanager関数を、同時に複数のゴルーチン間の通信に使うのは誤った使い方です。managerからの応答を待っているゴルーチンが、代わりにもう一つのゴルーチンから要求を受け取る可能性があるからです。これを解決するにはチャネルを受け渡すようにします。

type cmd2 struct { get bool; val int; ch <- chan int }
func manager2(ch chan cmd2) {
	var val int = 0
	for {
		c := <- ch
		if c.get { c.ch <- val }
		else { val = c.val }
	}
}

manager2を使うには、チャネルを値の中に格納します。

func f4(ch chan <- cmd2) int {
	myCh := make(chan int)
	c := cmd2{ true, 0, myCh }   // 複合リテラル構文
	ch <- c
	return <-myCh
}