構造体

構造体は、既存の型を組み合わせて新しい型を作る機能だ。とりあえずは C の構造体を同じようなものだと考えればいい。
構造体の定義は type キーワードを使って次のようにする。

構造体を構成するデータは、フィールドと呼ばれる。上のようにフィールドとその型を列記すればいい。

構造体の初期化は、構造体名{フィールド1, フィールド2, …} のようにフィールドを順に並べるほか、フィールド名とそのデータを組みにして渡す方法がある。後者では定義と順番が異なってもいい。また、フィールドへのアクセスは . 演算子を使う。例を示そう。

ここでは Point という名前の構造体を使っている。構造体の初期化は、main 関数の冒頭で行っている。変数 pq は上に書いたように初期化している。変数 o は初期化していないので、自動的にゼロ値に初期化される。
distance 関数の中で、構造体のフィールドにアクセスしている。

^o^ > go run struct.go
{0 0}
{10 10}
{100 200}
0
0
10
10
100
200
14.142135623730951
223.60679774997897
210.23796041628637

次は、構造体のポインタの例を示そう。構造体の場合も普通の型のように、アドレスを取得するには & を、値にアクセスするには * を使えばいい。ただし、フィールドにアクセスするときは、C のように -> を使うのではなく、. を使う。このへんは構造体の変数なのかポインタなのかを気にしなくていいので楽だな。

^o^ > go run struct2.go
&{0 0}
&{10 10}
&{100 200}
0
0
10
10
100
200
14.142135623730951
223.60679774997897
210.23796041628637

構造体をポインタとして使うときは、初期化用の関数を使うのが通例のようだ。次の例では、newPoint 関数がそれにあたる。この関数は、ポインタのフィールドになる値を引数にとって、構造体のポインタを返す。

^o^ > go run struct3.go
&{0 0}
&{10 10}
0
0
10
10
14.142135623730951

最後に、構造体をスライスに格納する例を示して今日は終わりにしよう。構造体だからと言って何も特別なことはない。

^o^ > go run struct_slice.go
[{0 0} {10 10} {100 100}]
[       ]
[0xc042008270 0xc042008280 0xc042008290 0xc0420082a0 0xc0420082b0 0xc0420082c0 0xc0420082d0 0xc0420082e0]
&{0 0}
&{1 1}
&{2 2}
&{3 3}
&{4 4}
&{5 5}
&{6 6}
&{7 7}

メモリの動的割り当て

new 関数は、動的にメモリを割り当ててそのアドレス、つまりポインタを返す。

例を示そう。

^o^ > go run dynamic_alloc.go
0xc042008210
0
0xc042008218
0
&[0 0 0 0 0 0 0 0]
[0 0 0 0 0 0 0 0]
100
1.2345
[10 0 0 0 0 0 0 80]

本題と関係ないけど、配列(と、たぶんスライスも)を指すポインタから要素へのアクセスは * をつけなくてできるんだな。

ポインタ

Go のポインタは C のポインタに似ている。変数のアドレスを得るためには & を使い、ポインタのさす値を参照するには * を使う。宣言するのに * を使うのも一緒だけど、Go の場合には変数名ではなく型の前につける。

^o^ > go run pointer.go
10
10
100
100
0xc042008210
true

C と違う点はほかにもある。Go のポインタは、整数値の代入や加減算はできないようになっている。配列のアドレスもそうだ。C では配列へのポインタはその配列の先頭要素へのポインタになっていて、各要素にアクセスするにはポインタをインクリメントしたりとかする。Go ではこういうことはできない。配列へのポインタは配列そのものへのポインタであって、配列の要素へのアクセスはできないようだ。もちろん、配列の要素へのポインタは作れる。

^o^ > go run pointer_array.go
&[1 2 3 4]
[1 2 3 4]
1
0xc042002700
1
0xc042002708
2
2
[10 20 3 4]
[10 20 30 40]

以前に、関数の呼び出しは値渡しだと書いた。関数のポインタを渡してやると、ポインタは仮引数にコピーされるけど、そのさしている値は呼び出し元と同じ値だ。つまり、ポインタを使えば参照渡しと同等のことかできる。

^o^ > go run pointer_call.go
10
20
[1 2 3 4]
20
10
[10 20 30 40]

さて、ポインタの基本はこんなところ。

クロージャ

昨日、一昨日のエントリで、関数を引数にとる高階関数を見てきた。今度は関数を返す関数を考えよう。
関数を返すには、return で関数を返してやればいいだけだ。それだけだと芸がないので、もう一ひねりしてみる。Go では、関数を定義した環境の変数を保持することができる。クロージャだ。
例を示そう。

genCounter の中で定義している匿名関数(これが genCounter の返り値になる)は、その外側にある変数 i を覚えている。なので、返り値の関数は呼び出されるごとに i をインクリメントしてから値を返す。

^o^ > go run closure.go
1
2
3

この通り。

mapcarとfilter

高階関数の練習に、mapcarfilter を作ってみた。

まずは mapcar から。

^o^ > go run mapcar.go
[1 2 3 4 5]
[1 4 9 16 25]
[1 8 27 64 125]

つぎは filter

^o^ > go run filter.go
[2 4 6 8]

高階関数と匿名関数

Go では高階関数もサポートされている。
引数の型のところに関数の型を書けばいい。関数の型は func(引数の型のリスト) 返り値の型 というふうに書く。説明するより、例を見たほうが早いだろう。

sumOf が高階関数。最初の引数が関数になっていて、f func(int) int と書いてあるのがそれだ。
実行すると:

^o^ > go run sumof.go
SUm of squares: 55
SUm of cubes:   225

あたりまえだけど、きちんと動く。

さて、上の例にある squarecube みたいな短い関数ならわざわざ定義してから使わなくても、使う場所、つまり sumOf の引数のところに書いてしまうこともできる。匿名関数、または無名関数ってやつだね。こんどはこの匿名関数を使ってみよう。

^o^ > go run sumof2.go
Sum of squares: 55
SUm of cubes:   225

この通り、匿名関数をつかっても期待通りに動く。とはいえ、匿名関数も型を明示的に書かなきゃいけないのはちょっとお手軽感がないなぁ。

再帰

Go の関数は再帰呼び出しをサポートしている。よくある階乗を求めるプログラムを見てみよう。

^o^ > go run fact.go
0 : 1
1 : 1
2 : 2
3 : 6
4 : 24
5 : 120
6 : 720
7 : 5040
8 : 40320
9 : 362880
10 : 3628800
11 : 39916800
12 : 479001600

func で定義した関数でなく匿名関数でも、先に関数を代入する変数を宣言しておけば、再帰呼び出しができる。

^o^ > go run fact2.go
0 : 1
1 : 1
2 : 2
3 : 6
4 : 24
5 : 120
6 : 720
7 : 5040
8 : 40320
9 : 362880
10 : 3628800
11 : 39916800
12 : 479001600

匿名関数については、また改めて書く。

スライスのコピー

copy 関数を使う。コピー元のスライスより、コピー先のほうが長い場合はもちろん、短い場合でもエラーにならない。長さはコピー先のスライスに合わせられる。

^o^ > go run slice_copy.go
[1 2 3 4 5 6 7 8]
[10 20 30 40 50 60 70 80 90 100]
[10 20 30 40]
[1 2 3 4 5 6 7 8 90 100]
[1 2 3 4]

値渡しと参照渡し

関数への引数の渡し方には、値渡しと参照渡しがある。Go は基本的に値渡しだ。つまり、実引数の値が関数に渡されるときに仮引数にコピーされる。例を挙げよう。

^o^ > go run funccall.go
1 abc
10 abcdef
1 abc

関数 foo の中で x は再代入されているし、y は破壊的に変更されているけど、それでも関数から出た後の出力を見ると、値が元に戻っている。main の中の xyfoo の中の xy が別のものだってことだ。

ところが、よくわからないんだけど、スライスやマップ(配列も?)の場合には、呼び出した関数の中で破壊的に変更すると、呼び出し元でも変更されている。これは参照渡しのように見える。

^o^ > go run funccall2.go
[1 2 3]
[10 2 3]
[10 2 3]

これはスライスの変数が、スライス自体ではなくスライスへの参照を保持していて、それが仮引数へコピーされるから、らしい。ともかく、これは気を付けよう。

ちなみに、ポインタを使えば参照渡しと同等のことができる。ポインタについてはそのうち書く。

可変長引数

Go の関数は可変長引数をサポートしている。可変長引数は引数リストの最後にだけ置くとこができ、args ...int のように書く。こうすると、args には引数がスライスとして格納される。

foo0 は1つの可変長引数、言い換えると 0 個以上の引数をとり、foo1 は1つ以上の引数をとる。

^o^ > go run variable_length.go
[]
[1]
[1 2]
[1 2 3]
1 []
1 [2]
1 [2 3]
1 [2 3 4]

スライスに要素を追加する関数 append も、実は可変長引数だ。このとおり。

^o^ > go run append.go
[1 2 3]
[1 2 3 4 5 6]

可変長引数に渡すデータがスライスの時、s... のように書くと、スライスを展開して可変長引数に渡すことができる。これを利用して、スライスの連結ができる。

^o^ > go run append2.go
[1 2 3]
[4 5 6]
[1 2 3 4 5 6]