値渡しと参照渡し

関数への引数の渡し方には、値渡しと参照渡しがある。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]

多値と多重代入

すでにさらっと書いたけど、Go の関数は複数の値を返すことができる。いわゆる「多値」ってやつ。で、それを受け取るほうも普通に多重代入ができる。例を示すほうが早いだろう。

^o^ > go run multivalue.go
3
1

見てわかるように、多値を返すには関数の返り値の型をカッコでくくって並べて書き、return に同じ数の値(もちろん型があってなきゃいけない)を渡してやるだけだ。Scheme の多値よりも扱いが簡単だね。

さて、多重代入ができるってことはこんなこともできる。

^o^ > go run swap.go
10 20
20 10

ローカル変数のスコープ

関数は変数のスコープを作るので、引数や関数内で宣言された変数はその関数内だけで有効なローカル変数になる。
ほかにも {} でブロックを作ってやると、そのブロックは新しいスコープを作ってブロック内で宣言された変数はブロック内だけで有効になる。例えば、次の例がわかりやすいだろう。

注意が必要なのは、if 文なども新しいスコープを作るので、次の例はエラーになってしまう。

^o^ > go run var_local2.go
# command-line-arguments
.\var_local2.go:14: undefined: b

b を出力しようとしているところで、b が定義されていないと怒られている。これを避けるためには、先に b を宣言しておく。

^o^ > go run var_local3.go
Yes

マップ

Go のマップは Ruby でいうハッシュ、Python の辞書、要するに連想配列だ。
マップの宣言、初期化は例を見たほうが早いだろう。次のようにする。

^o^ > go run map.go
map[]
map[foo:1 bar:2]
map[hoge:10 fuga:20]

ここで注意。上の例の変数 a のように宣言だけして初期化をしないと、nilマップというものになる。これは空のマップとは違って、キーの追加とかができない。試してみよう。

^o^ > go run map2.go
map[]
panic: assignment to entry in nil map

goroutine 1 [running]:
main.main()
        C:/Users/takatoh/Documents/w/learning-go/map2.go:10 +0xa7
exit status 2

この通り、a["hoge"] = 100 のところでエラーを起こしている。なんでこんな仕様になってるんだろ?
で、空のマップがほしい時には make 関数を使う。

これなら新しいキー(と値)を追加できる。

^o^ > go run map3.go
map[]
map[foo:10 bar:20]

参照するには [] でキーを指定すればいいけど、返ってくる値は2つある。キーが存在する場合には対応する値と true が、存在しない場合にはゼロ値と false が返ってくる。これはつまり Go には多値があるってことだけど、これについては別の機会に書く。
ともかく、キーが存在するかどうかは2つ目の返り値を見ないとわからないので、ちょっと面倒。
あと、キー(と値)を削除するには delete 関数を使う。

^o^ > go run map4.go
map[foo:1 bar:2]
1
map[baz:3 foo:1]

スライス(2)

スライスは make 関数を使っても生成できる。

「大きさ」と「容量」があるけど、「大きさ」は要素の数(ここではゼロ値に初期化される)、「容量」はメモリ上に確保される領域のサイズのこと、と言ったらいいかな。「容量」は省略できる。
「大きさ」、「容量」はそれぞれ len 関数、cap 関数で得ることができる。

^o^ > go run slice4.go
[0 0 0 0 0]
5
10

スライスは、append 関数を使って末尾に要素を追加できる。

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

容量を超えて要素を追加しようとすると、スライスはよりを大きい容量の新しいメモリ領域を確保する。これは自動的に行われる。ちょっと試してみよう。

^o^ > go run slice6.go
len=0 cap=0 0x526b88 []
len=1 cap=1 0xc0420381d8 [1]
len=2 cap=2 0xc042038220 [1 2]
len=3 cap=4 0xc04203e500 [1 2 3]
len=4 cap=4 0xc04203e500 [1 2 3 4]
len=5 cap=8 0xc042040140 [1 2 3 4 5]
len=6 cap=8 0xc042040140 [1 2 3 4 5 6]
len=7 cap=8 0xc042040140 [1 2 3 4 5 6 7]
len=8 cap=8 0xc042040140 [1 2 3 4 5 6 7 8]
len=9 cap=16 0xc042052180 [1 2 3 4 5 6 7 8 9]
len=10 cap=16 0xc042052180 [1 2 3 4 5 6 7 8 9 10]
len=11 cap=16 0xc042052180 [1 2 3 4 5 6 7 8 9 10 11]
len=12 cap=16 0xc042052180 [1 2 3 4 5 6 7 8 9 10 11 12]
len=13 cap=16 0xc042052180 [1 2 3 4 5 6 7 8 9 10 11 12 13]
len=14 cap=16 0xc042052180 [1 2 3 4 5 6 7 8 9 10 11 12 13 14]
len=15 cap=16 0xc042052180 [1 2 3 4 5 6 7 8 9 10 11 12 13 14 15]
len=16 cap=16 0xc042052180 [1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16]
len=17 cap=32 0xc042032100 [1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17]

%p は変数のポインタを表す書式指定子。上の例を見ると、大きさ(len)が容量(cap)を超えるタイミングで容量も大きくなり、同時にポインタの値も変化していく様子が見て取れる。

スライス

スライスは配列に似ているが、要素を追加することによって大きさ(長さ)を変更できるデータ構造。大きさの変更は自動的になされる。小さくすることはできない。たぶん。

スライスの宣言は次のように大きさを指定せずに行う。

同時に初期化するには:

大きさを指定しないことを除けば、配列と一緒だな。ちょっと試してみよう。

^o^ > go run slice.go
[]
[1 2 3 4 5]
[6 7 8 9 10]

スライスはまた、配列の部分列を取り出すことでも作れる。

操作 意味
s[m:n] m から n – 1 まで
s[m:] m から最後尾まで
s[:n] 先頭から n – 1 まで
s[:] 先頭から最後尾まで

^o^ > go run slice2.go
[3 4 5 6]
[4 5 6 7 8]
[1 2 3]
[1 2 3 4 5 6 7 8]

スライスを配列の部分列として取り出した場合、元の配列とデータ格納領域を共有することに注意。つまり、スライスの要素を変更すると、元の配列の対応する要素も変更される。

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

配列

配列は次のように大きさ(長さ)と要素の型を合わせて宣言する。

要素の型が決まっているので、同じ配列に異なる型の要素を格納することはできない。
また、大きさも含めて配列の型なので、要素の型が同じでも大きさが異なれば別の型とみなされる。例えば [4]int[8]int は別の型だ。

宣言と同時に初期化するには次のようにする。

または

初期化しないと、配列の要素はゼロ値になる。

少し試してみよう。

o^ > go run array.go
[0 0 0 0]
[0 1 2 3 4 5]
[0 1 2 3 4 5 6 7]

配列の要素にアクセスするには、[] を使ってインデックスを指定する。インデックスは 0 始まりだ。

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

最後に len 関数について書いておこう。len 関数は配列の大きさ(長さ)を返す。

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

switch文

条件分岐には if 文のほかにもうひとつ、switch 文がある。switch 文は次の通り。

条件式の値と case 節の式A、B、C の値が順に比較され、等しくなる最初の case 節の処理が実行される。どの case 節の式とも等しくなければ default 節が実行される。
試してみよう。お題は FizzBuzz 問題だ。

^o^ > go run switch.go
1
2
Fizz
4
Buzz
Fizz
7
8
Fizz
Buzz
11
Fizz
13
14
FizzBuzz
16
17
Fizz
19
Buzz

例題のために冗長な書き方にしたけど、期待通り動いている。見ればわかるように、case 節の式はひとつでなくてもいい。
また、条件式を書かない書き方もある。この場合には case 節の式の値が true になる最初の節が実行される。

^o^ > go run switch2.go
1
2
Fizz
4
Buzz
Fizz
7
8
Fizz
Buzz
11
Fizz
13
14
FizzBuzz
16
17
Fizz
19
Buzz

無限ループとbreak、continue

for 文で、条件部を省略してただ for と書くと無限ループになる。

ループの制御として、breakcontinue がある。break はループを抜け、continue はループの頭に戻る。
試してみよう。

^o^ > go run for_inf.go
1
2
4
5
7
8
10