新しい本が出ていたので、凝りもせずに買ってしまった。Go の勉強が落ち着いたら、改めて Haskell をやってみよう。
投稿者: takatoh
スライス(2)
スライスは make 関数を使っても生成できる。
変数名 := make([]型, 大きさ, 容量)
「大きさ」と「容量」があるけど、「大きさ」は要素の数(ここではゼロ値に初期化される)、「容量」はメモリ上に確保される領域のサイズのこと、と言ったらいいかな。「容量」は省略できる。
「大きさ」、「容量」はそれぞれ len 関数、cap 関数で得ることができる。
package main
import "fmt"
func main() {
a := make([]int, 5, 10)
fmt.Println(a)
fmt.Println(len(a))
fmt.Println(cap(a))
}
^o^ > go run slice4.go [0 0 0 0 0] 5 10
スライスは、append 関数を使って末尾に要素を追加できる。
package main
import "fmt"
func main() {
a := make([]int, 0)
fmt.Println(a)
for i := 0; i < 5; i++ {
a = append(a, i)
fmt.Println(a)
}
}
^o^ > go run slice5.go [] [0] [0 1] [0 1 2] [0 1 2 3] [0 1 2 3 4]
容量を超えて要素を追加しようとすると、スライスはよりを大きい容量の新しいメモリ領域を確保する。これは自動的に行われる。ちょっと試してみよう。
package main
import "fmt"
func main() {
a := make([]int, 0)
fmt.Printf("len=%d cap=%d %p %v\n", len(a), cap(a), a, a)
for i := 1; i < 18; i++ {
a = append(a, i)
fmt.Printf("len=%d cap=%d %p %v\n", len(a), cap(a), a, a)
}
}
^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)を超えるタイミングで容量も大きくなり、同時にポインタの値も変化していく様子が見て取れる。
スライス
スライスは配列に似ているが、要素を追加することによって大きさ(長さ)を変更できるデータ構造。大きさの変更は自動的になされる。小さくすることはできない。たぶん。
スライスの宣言は次のように大きさを指定せずに行う。
var 変数名 []型
同時に初期化するには:
var 変数名 []型 = []型{ 値1, 値2, 値3, ... }
var 変数名 = []型{ 値1, 値2, 値3, ... }
大きさを指定しないことを除けば、配列と一緒だな。ちょっと試してみよう。
package main
import "fmt"
func main() {
var s1 []int
var s2 []int = []int{ 1,2,3,4,5 }
var s3 = []int{ 6,7,8,9,10 }
fmt.Println(s1)
fmt.Println(s2)
fmt.Println(s3)
}
^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[:] | 先頭から最後尾まで |
package main
import "fmt"
func main() {
var array = [8]int{ 1,2,3,4,5,6,7,8 }
var s1 = array[2:6]
var s2 = array[3:]
var s3 = array[:3]
var s4 = array[:]
fmt.Println(s1)
fmt.Println(s2)
fmt.Println(s3)
fmt.Println(s4)
}
^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]
スライスを配列の部分列として取り出した場合、元の配列とデータ格納領域を共有することに注意。つまり、スライスの要素を変更すると、元の配列の対応する要素も変更される。
package main
import "fmt"
func main() {
var array = [4]int{ 1,2,3,4 }
fmt.Println(array)
var s = array[:]
s[2] = 10
fmt.Println(s)
fmt.Println(array)
}
^o^ > go run slice3.go [1 2 3 4] [1 2 10 4] [1 2 10 4]
配列
配列は次のように大きさ(長さ)と要素の型を合わせて宣言する。
var 変数名 [大きさ]型
要素の型が決まっているので、同じ配列に異なる型の要素を格納することはできない。
また、大きさも含めて配列の型なので、要素の型が同じでも大きさが異なれば別の型とみなされる。例えば [4]int と [8]int は別の型だ。
宣言と同時に初期化するには次のようにする。
var 変数名 [大きさ]型 = [大きさ]型{ 値0, 値1, 値2, 値3, ... }
または
var 変数名 = [大きさ]型{ 値0, 値1, 値2, 値3, ... }
初期化しないと、配列の要素はゼロ値になる。
少し試してみよう。
package main
import "fmt"
func main() {
var a [4]int
var b [6]int = [6]int{ 0, 1, 2, 3, 4, 5 }
var c = [8]int { 0, 1, 2, 3, 4, 5, 6, 7 }
fmt.Println(a)
fmt.Println(b)
fmt.Println(c)
}
o^ > go run array.go [0 0 0 0] [0 1 2 3 4 5] [0 1 2 3 4 5 6 7]
配列の要素にアクセスするには、[] を使ってインデックスを指定する。インデックスは 0 始まりだ。
package main
import "fmt"
func main() {
var a [4]int = [4]int{ 1, 2, 3, 4 }
fmt.Println(a[2])
fmt.Println(a)
a[1] = 7
fmt.Println(a[1])
fmt.Println(a)
}
^o^ > go run array2.go 3 [1 2 3 4] 7 [1 7 3 4]
最後に len 関数について書いておこう。len 関数は配列の大きさ(長さ)を返す。
package main
import "fmt"
func main() {
var a [4]int = [4]int{ 1, 2, 3, 4 }
fmt.Println(a)
fmt.Println(len(a))
}
^o^ > go run array3.go [1 2 3 4] 4
switch文
条件分岐には if 文のほかにもうひとつ、switch 文がある。switch 文は次の通り。
switch 条件式 {
case 式A:
処理A1
処理A2
処理A3
case 式B:
処理B1
処理B2
処理B3
case 式C:
処理C1
処理C2
処理C3
default:
処理Z1
処理Z2
処理Z3
}
条件式の値と case 節の式A、B、C の値が順に比較され、等しくなる最初の case 節の処理が実行される。どの case 節の式とも等しくなければ default 節が実行される。
試してみよう。お題は FizzBuzz 問題だ。
package main
import "fmt"
func main() {
i := 1
for i <= 20 {
switch i % 15 {
case 0:
fmt.Println("FizzBuzz")
i++
case 3, 6, 9, 12:
fmt.Println("Fizz")
i++
case 5, 10:
fmt.Println("Buzz")
i++
default:
fmt.Println(i)
i++
}
}
}
^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 になる最初の節が実行される。
package main
import "fmt"
func main() {
i := 1
for i <= 20 {
switch {
case i % 15 == 0:
fmt.Println("FizzBuzz")
case i % 3 == 0:
fmt.Println("Fizz")
case i % 5 == 0:
fmt.Println("Buzz")
default:
fmt.Println(i)
}
i++
}
}
^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 と書くと無限ループになる。
for {
処理
}
ループの制御として、break と continue がある。break はループを抜け、continue はループの頭に戻る。
試してみよう。
package main
import "fmt"
func main() {
i := 0
for {
if i > 10 {
break
} else if i % 3 == 0 {
i++
continue
} else {
fmt.Println(i)
}
i++
}
}
^o^ > go run for_inf.go 1 2 4 5 7 8 10
for文
Go の繰り返し構文には for しかない。だけどこの for には 3 通りの使い方があって、ひとつは普通の for、ひとつは while 的なもの、そしてもひとつはいわゆる for each 的なものだ。
順番に見ていこう。
まずひとつめ、普通の for。「普通の」というのは、繰り返し用変数の初期化と繰り返し条件、変数の更新処理がある C や JavaScript なんかと同じ、くらいの意味。ただしカッコは要らない。
package main
import "fmt"
func main() {
names := []string{ "Andy", "Bill", "Charlie" }
for i := 0; i < 3; i++ {
fmt.Println(names[i])
}
}
names := []string{ "Andy", "Bill", "Charlie" } というところは、スライスっていう配列みたいなものを作ってるんだけど、これについては別の機会に書く。
実行してみよう。
^o^ > go run for.go Andy Bill Charlie
ふたつめの使い方は while のようなもの。というか while そのものだ。for の後に繰り返し条件だけを書く。
package main
import "fmt"
func main() {
names := []string{ "Andy", "Bill", "Charlie" }
i := 0
for i < 3 {
fmt.Println(names[i])
i++
}
}
^o^ > go run for_while.go Andy Bill Charlie
最後はいわゆる for each 的な使い方。range キーワードを使って、スライス(または配列)のインデックスと要素を一つずつ取り出して繰り返す。
package main
import "fmt"
func main() {
names := []string{ "Andy", "Bill", "Charlie" }
for idx, name := range names {
fmt.Println(idx, name)
}
}
^o^ > go run for_range.go 0 Andy 1 Bill 2 Charlie
ここではインデックスを idx 変数で受け取っているけど、場合によっては使わないこともある。そういう時は次のように _ (アンダースコア)で受けてやる。そうしないと idx 変数を使っていない、という警告を受けることになる。アンダースコアは、「ここに入る値は使わないよ」ということを示すものだ。
for _, name := range names {
fmt.Println(name)
}
論理演算子
Go の論理演算子には次のものがある。
| 演算子 | 意味 |
| !x | x の否定 |
| x && y | x かつ y |
| x || y | x または y |
x、y は bool 型でなければならない。
グローバル変数
変数を関数の外で宣言するとグローバル変数になる。グローバル変数は、同じファイル内のどの関数からでもアクセスできる。
package main
import "fmt"
var name = "Andy"
func hello() {
fmt.Println("Hello, " + name + "!")
}
func main() {
hello()
fmt.Println("Good morning, " + name + ".")
}
^o^ > go run var_global.go Hello, Andy! Good morning, Andy.
関数の中で宣言されているのはローカル変数。ローカル変数はその関数の中だけで使える。
変数の宣言と初期化(2)
先日、変数の宣言と初期化を一緒に行うには「var 変数 = 値」という書き方をする、てなことを書いた。
これ以外に、関数の中に限っては「変数 := 値」という書き方ができる。
package main
import "fmt"
func main() {
name := "Andy"
fmt.Println("Hello, " + name + "!")
}
^o^ > go run var4.go Hello, Andy!