A Tour of Go やってみた Part3
前回
「A Tour of Go」の"Basics: Flow control statements: for, if, else, switch and defer" までを終わらせたので、今回は "Basics: More types: structs, slices, and maps." をやる
Basics: More types: structs, slices, and maps.
1. Pointers
Goはポインタを扱うことができる。
package main
import "fmt"
func main() {
var p *int // `var 変数名 *型`でポインタ型の変数を宣言
fmt.Println(p) // pのポインタ(メモリアドレス)を出力。ゼロ値はnil。
var i int = 42 // int型の変数iを宣言して初期値を設定
p = &i // `&変数名`でその変数のアドレスを取得してポインタ型変数pに代入
fmt.Println(p) // pのポインタ(メモリアドレス)を出力
fmt.Println(*p) // `*ポインタ変数`でポインタを通じて変数の値を出力
*p = 21 // ポインタを通じてiの値を更新
fmt.Println(i) // iの値を出力
}
<nil>
0x14000104028
42
21
package main
import "fmt"
func main() {
i, j := 42, 2701
p := &i // iへのポインタをpに代入
fmt.Println(p) // ポインタを出力
fmt.Println(*p) // ポインタを通じてiの値を出力
*p = 21 // ポインタを通じてiの値を更新
fmt.Println(i) // iの値を出力
p = &j // jへのポインタをpに代入
*p = *p / 37 // ポインタを通じてjを37で割る
fmt.Println(j) // jの値を出力
}
0x1400011e008
42
21
73
2. Structs
構造体
package main
import "fmt"
type Vertex struct {
X int
Y int
}
func main() {
fmt.Println(Vertex{1, 2})
}
{1 2}
3. Struct Fields
構造体のフィールドには .
でアクセスできる。
package main
import "fmt"
type Vertex struct {
X int
Y int
}
func main() {
v := Vertex{1, 2}
fmt.Println(v)
v.X = 4
v.Y = 5
fmt.Println(v)
}
{1 2}
{4 5}
4. Pointers to structs
構造体のフィールドには構造体ポインタ経由でもアクセスできる。構造体v
のポインタp
を使って、v
のフィールドX
にアクセスするには、(*p).X
と書くが、より簡単にp.X
と書ける。
package main
import "fmt"
type Vertex struct {
X int
Y int
}
func main() {
v := Vertex{1, 2}
p := &v
fmt.Println((*p).X)
p.X = 1e9
fmt.Println(v)
}
1
{1000000000 2}
5. Struct Literals
構造体リテラルは、フィールドの値を列挙するか、名前付きフィールドで値をセットすることで、表される。名前付きフィールドの場合、指定しないフィールドがあればその値はゼロ値になる。&
で構造体の値へのポインタとなる。
package main
import "fmt"
type Vertex struct {
X, Y int
}
var(
v1 = Vertex{1, 2}
v2 = Vertex{X: 1}
v3 = Vertex{}
p = &Vertex{1, 2}
)
func main() {
fmt.Println(v1, p, v2, v3)
}
{1 2} &{1 2} {1 0} {0 0}
6. Arrays
配列は[要素数]型
で表す。例えば要素数10の整数型の配列a
は以下
var a[10]int
配列の長さは、その型の一部となるため、配列のサイズを変更はできない。
package main
import "fmt"
func main() {
var a [2]string
a[0] = "こんにちは"
a[1] = "世界"
fmt.Println(a[0], a[1])
fmt.Println(a)
primes := [6]int{2, 3, 5, 7, 11, 13}
fmt.Println(primes)
}
こんにちは 世界
[こんにちは 世界]
[2 3 5 7 11 13]
7. Slices
配列はサイズが固定だが、スライスを使えば配列の要素を動的に変更できる。スライスは[]型
で指定する。インデックスの上限・下限を[上限:下限]
として指定することができる。
package main
import "fmt"
func main() {
primes := [6]int{2, 3, 5, 7, 11, 13}
var s []int = primes[1:4]
fmt.Println(s)
}
[3 5 7]
8. Slice are like references to arrays
スライスは配列へのリファレンスのようなもの。スライスはデータを格納していなくて、単に基の配列の一部分を示す「ビュー」と言える。
よって、スライスの要素を変更すると、元の配列の要素も変更されるし、同じ配列を共有している複数のスライスがあってどれかのスライスで変更を行うと、全てのスライスで変更される(ように見える)
package main
import "fmt"
func main() {
names := [4]string{
"John",
"Paul",
"George",
"Pete",
}
fmt.Println(names)
a := names[0:2]
b := names[2:4]
fmt.Println(a, b)
b[1] = "Ringo"
fmt.Println(a, b)
fmt.Println(names)
}
len=10 cap=10 [1 2 3 4 5 6 7 8 9 10]
len=0 cap=10 []
len=4 cap=10 [1 2 3 4]
len=2 cap=8 [3 4]
[John Paul George Pete]
[John Paul] [George Pete]
[John Paul] [George Ringo]
[John Paul George Ringo]
9. Slice literals
スライスのリテラルは配列リテラルから長さを省いたもの。
配列リテラル。
[3]bool{true, false, true}
スライスリテラル。こちらは上と同じ配列をまず作成して、その配列を参照するスライスを生成する。
[]bool{true, false, true}
package main
import "fmt"
func main() {
// 整数のスライス
q := []int{2, 3, 5, 7, 11, 13}
fmt.Println(q)
// ブール値のスライス
r := []bool{true, false, true, true, false, true}
fmt.Println(r)
// 構造体のスライス
s := []struct {
i int
b bool
}{
{2, true},
{3, false},
{5, true},
{7, true},
{11, false},
{13, true},
}
fmt.Println(s)
}
[2 3 5 7 11 13]
[true false true true false true]
[{2 true} {3 false} {5 true} {7 true} {11 false} {13 true}]
10. Slice defaults
スライスは[上限:下限]
を省略するとデフォルト値を省略できる、ってピンとこない。
以下の配列があるとして、
var a [10]int
上記の配列のスライスは以下の4通りの書き方ができる。
a[0:10]
a[:10]
a[0:]
a[:]
package main
import "fmt"
func main() {
s := []int{2, 3, 5, 7, 11, 13}
s = s[1:4]
fmt.Println(s)
s = s[:2]
fmt.Println(s)
s = s[1:]
fmt.Println(s)
}
[3 5 7]
[3 5]
[5]
11. Slice length and capacity
スライスには_length_(長さ)と_capacity_(容量)がある。
- スライスの長さは、スライスに含まれる要素数。
len()
で求めることができる。 - スライスの容量は、スライスの最初の要素から数えて、基となる配列の要素の数。
cap()
で求めることができる。
これはどういうことかというと、上で書いてあった通り、Go のスライスは、単なる可変長の配列「ビュー」であって、その下には必ず「基になる配列」が存在している。で、len()
は現在見えているスライスの要素数、cap()
は現在見えているスライスの最初の要素から、基となる配列の終わりまで論理的に見える要素数≒再スライス可能な要素数、ということになる。自分でも何言ってるのかよくわからなくなるけど。
package main
import "fmt"
func main() {
s := []int{1,2,3,4,5,6,7,8,9,10}
printSlice(s)
// スライスをスライスして、長さを0にする
s = s[:0]
printSlice(s)
// スライスの長さを増やす
s = s[:4]
printSlice(s)
// 最初の2つの要素を削除する
s = s[2:]
printSlice(s)
}
func printSlice(s []int) {
fmt.Printf("len=%d cap=%d %v\n", len(s), cap(s), s)
}
len=10 cap=10 [1 2 3 4 5 6 7 8 9 10]
len=0 cap=10 []
len=4 cap=10 [1 2 3 4]
len=2 cap=8 [3 4]
ステップごとにo3に説明してもらった。
s := []int{1,2,3,4,5,6,7,8,9,10}
- このとき「基の配列」は
[1 2 3 4 5 6 7 8 9 10]
全体。- スライス
s
はこの全体をまるごと「ビュー」として見ている状態なので、
len(s) = 10
(今見えている数は全部で10個)cap(s) = 10
(左右に広げられる余地も10個分ある)s = s[:0]
- 今度は「ビュー」を左端だけに寄せて、幅(ビューの幅)を 0個分 に縮めています。
- 見えている要素は
[]
ですからlen(s)=0
。- でも「ビュー」はまだ元の配列のスタート位置(要素1のところ)から右に 10個 分の幅までは広げられるので、
cap(s)=10
のままです。s = s[:4]
- さっき幅を0にしたビューを、また左端から 4個分の幅まで広げ直しています。
- 見えているのは配列の先頭4つ
[1 2 3 4]
→len(s)=4
- 広げられる最大幅(元の配列の右端まで)はまだ10個分 →
cap(s)=10
s = s[2:]
- いま見えている
[1 2 3 4]
の中で、左から2つ分をスキップしています。- 見えているのは
[3 4]
→len(s)=2
- でもニューは元の配列の「1の位置」ではなく「3の位置」から始まるので、右にはまだ残り 8個分(要素3〜10まで)が広げられる余地があります →
cap(s)=8
このように、
- len(s) は「今そのビューに見えている要素の数」
- cap(s) は「そのビューを左右に動かさずに(左端を動かさずに)、右側にどれだけ広げられるかの最大数」
── と考えると、専門用語を使わなくてもイメージしやすいかと思います。
なので、容量を超えて広げることはできない。
package main
import "fmt"
func main() {
s := []int{1,2,3,4,5,6,7,8,9,10}
printSlice(s)
s = s[:0]
printSlice(s)
s = s[:4]
printSlice(s)
s = s[2:]
printSlice(s)
// 現在の cap(s) は 8 なので、これを超える再スライスはpanicとなる
s = s[:9] // cap(s)=8 の範囲外を指定
printSlice(s)
}
func printSlice(s []int) {
fmt.Printf("len=%d cap=%d %v\n", len(s), cap(s), s)
}
len=10 cap=10 [1 2 3 4 5 6 7 8 9 10]
len=0 cap=10 []
len=4 cap=10 [1 2 3 4]
len=2 cap=8 [3 4]
panic: runtime error: slice bounds out of range [:9] with capacity 8
(snip)
12. Nil slices
スライスのゼロ値はnil
となる。nil
なスライスの長さ(len
)と容量(cap
)はともに0
となり、基となる配列を持たない。
package main
import "fmt"
func main() {
var s []int
fmt.Println(s, len(s), cap(s))
if s == nil {
fmt.Println("nil!")
}
}
[] 0 0
nil!
13. Creating a slice with make
スライスは組み込み関数make
を使うと動的なサイズのスライスを作成できる。make
は、ゼロ化された配列を割り当てて、その配列を指すスライスを返す。
第2引数で長さ、第3引数で容量を指定する。
package main
import "fmt"
func main() {
a := make([]int, 5)
printSlice("a", a)
b := make([]int, 0, 5)
printSlice("b", b)
c := b[:2]
printSlice("c", c)
d := c[2:5]
printSlice("d", d)
}
func printSlice(s string, x []int) {
fmt.Printf("%s len=%d cap=%d %v\n",
s, len(x), cap(x), xz)
}
a len=5 cap=5 [0 0 0 0 0]
b len=0 cap=5 []
c len=2 cap=5 [0 0]
d len=3 cap=3 [0 0 0]
14. Slices of Slices
スライスはスライスを含むことができる。つまりこれでn次元のスライス表現できる。
package main
import (
"fmt"
"strings"
)
func main() {
// tic-tac-toeの盤面を作る
board := [][]string{
[]string{"_", "_", "_"},
[]string{"_", "_", "_"},
[]string{"_", "_", "_"},
}
// プレイヤーの指し手を入力する
board[0][0] = "X"
board[2][2] = "O"
board[1][2] = "X"
board[1][0] = "O"
board[0][2] = "X"
// 盤面を表示する
for i := 0; i < len(board); i++ {
fmt.Printf("%s\n", strings.Join(board[i], " "))
}
}
X _ X
O _ X
_ _ O
15. Appending to a slice
組み込みの関数append
を使うとスライスへ新しい要素を追加できる。
func append(s []T, vs ...T) []T
append
の最初のパラメータ s
は型 T
のスライスで、残りのパラメータ vs
はスライスに追加する T
型の値となる。
append
の結果として返されるスライスには、元のスライスの全要素に加えて、指定された値が順に含まれる。
もし元のスライス s
の基となる配列の容量が、追加しようとする全ての要素を収めきれない場合は、より大きな配列が新たに割り当てられる。返されたスライスは、その新しく割り当てられた配列を指す。
package main
import "fmt"
func main() {
var s[]int
printSlice(s)
// nilスライスに対してもappendできる
s = append(s, 0)
printSlice(s)
// スライスは必要に応じて容量を増やす
s = append(s, 1)
printSlice(s)
// 一度に複数の要素を追加できる
s = append(s, 2, 3, 4)
printSlice(s)
}
func printSlice(s []int) {
fmt.Printf("len=%d cap=%d %v\n", len(s), cap(s), s)
}
len=0 cap=0 []
len=1 cap=1 [0]
len=2 cap=2 [0 1]
len=5 cap=6 [0 1 2 3 4]
16. Range
for
ループで使用するrange
は、スライスやマップ(map
)を1つづつ反復処理するのに使う。
スライスをrange
で繰り返すと、range
は2つの変数を返す
- インデックス
- インデックスの場所の要素のコピー
package main
import "fmt"
var pow = []int{1, 2, 4, 8, 16, 32, 64, 128}
func main() {
for i, v := range pow {
fmt.Printf("2**%d = %d\n", i, v)
}
}
2**0 = 1
2**1 = 2
2**2 = 4
2**3 = 8
2**4 = 16
2**5 = 32
2**6 = 64
2**7 = 128
17. Range continued
インデックスや値は、_
に代入すれば捨てることができる。
package main
import "fmt"
func main() {
pow := make([]int, 10)
for i := range pow{
pow[i] = 1 << uint(i) // == 2**i
}
for _, value := range pow {
fmt.Printf("%d\n", value)
}
}
1
2
4
8
16
32
64
128
256
512
18. Exercise: Slices
Exerciseはパス
19. Maps
map
はキーと値をマップする。マップのゼロ値はnil
であり、nil
マップはキーを持たず、キーの追加もできないが、make
を使うと指定された型のマップが初期化され、使用可能な状態で返る。
package main
import "fmt"
type Vertex struct {
Lat, Long float64
}
// キーがstring型、値がVertex型のマップ m を宣言
// ここでは m は nil である
var m map[string]Vertex
func main() {
// マップを初期化して、使用可能にする
m = make(map[string]Vertex)
// キー "Bell Labs" に対応する値を設定
m["Bell Labs"] = Vertex{
40.68433, -74.39967,
}
fmt.Println(m["Bell Labs"])
}
{40.68433 -74.39967}
20. Map literals
map
リテラルはキーが必要
package main
import "fmt"
type Vertex struct {
Lat, Long float64
}
// マップリテラルでは、キーが必要
var m = map[string]Vertex{
"Bell Labs": Vertex{
40.68433, -74.39967,
},
"Google": Vertex{
37.42202, -122.08408,
},
}
func main() {
fmt.Println(m)
}
21. Map literals continued
トップレベルのリテラルで型が明示されている場合、その要素では型名を省略できる。
package main
import "fmt"
type Vertex struct {
Lat, Long float64
}
var m = map[string]Vertex{
"Bell Labs": {40.68433, -74.39967},
"Google": {37.42202, -122.08408},
}
func main() {
fmt.Println(m)
}
map[Bell Labs:{40.68433 -74.39967} Google:{37.42202 -122.08408}]
22. Mutating Maps
マップの操作いろいろ
- 挿入・更新
- 取得
- 削除
- 存在確認
package main
import "fmt"
func main() {
m := make(map[string]int)
// 要素の挿入
m["Answer1"] = 10
m["Answer2"] = 20
fmt.Println(m)
// 要素の更新
m["Answer1"] = 30
fmt.Println(m)
// 要素の削除
delete(m, "Answer2")
fmt.Println(m)
// 要素の存在確認は、2つの値を返す
// 1つ目は要素の値、2つ目は要素の存在確認(bool)
// 存在しない要素の場合は、要素の型のゼロ値とfalseが返る
v1, ok1 := m["Answer1"]
v2, ok2 := m["Answer2"]
fmt.Println("The value:", v1, "Present?", ok1)
fmt.Println("The value:", v2, "Present?", ok2)
}
map[Answer1:10 Answer2:20]
map[Answer1:30 Answer2:20]
map[Answer1:30]
The value: 30 Present? true
The value: 0 Present? false
23. Exercise: Maps
ここもスキップ
24. Function values
関数も値として扱えるので、変数に代入したり、関数の引数・戻り値として渡すことができる。
関数を変数に代入
package main
import "fmt"
func add(a, b int) int {
return a + b
}
func main() {
var f func(int, int) int = add
fmt.Println(f(1, 2))
}
3
引数として関数を渡す
package main
import "fmt"
func add(a, b int) int {
return a + b
}
func compute(fn func(int, int) int, x, y int) int {
return fn(x, y)
}
func main() {
result := compute(add, 10, 20)
fmt.Println(result)
}
30
戻り値として返す
package main
import "fmt"
func makeMultiplier(factor int) func(int) int {
return func(x int) int {
return x * factor
}
}
func main() {
doubler := makeMultiplier(2)
fmt.Println(doubler(5))
}
10
組み合わせたもの
package main
import (
"fmt"
"math"
)
// 関数を引数に取る関数
func compute(fn func(float64, float64) float64) float64 {
// 引数に渡された関数を実行し、その結果を返す
return fn(3, 4)
}
func main() {
// 無名関数を定義
hypot := func(x, y float64) float64 {
// ピタゴラスの定理を使って、xとyの平方根を計算
return math.Sqrt(x*x + y*y)
}
// 無名関数を直接実行
fmt.Println(hypot(5, 12))
// 無名関数を関数computeの引数に渡す
fmt.Println(compute(hypot))
// 標準ライブラリの関数を関数computeの引数に渡す
fmt.Println(compute(math.Pow))
}
13
5
81
25. Function closures
Goの関数はクロージャを作ることができる。クロージャは、関数の外側で定義された変数を参照できる関数値であり、クロージャ内の関数はその参照先の変数を読み書きできるので、「関数に変数が束縛(bind)されている」ように振る舞う。
以下のaddr
関数はクロージャを返し、各クロージャはそれぞれのsum
変数へバインドされる。
package main
import "fmt"
// int を受け取って 累積和を返すクロージャを返す
func adder() func(int) int {
sum := 0 // クロージャが参照する外側の変数
return func(x int) int {
sum += x // クロージャ内から外側の変数を更新
return sum // 更新後の累積和を返す
}
}
func main() {
// 2つの異なるクロージャを作成
pos, neg := adder(), adder()
for i := 0; i < 10; i++ {
fmt.Println(
pos(i), // 1つ目のクロージャに値を渡す
neg(-2*i), // 2つ目のクロージャに値を渡す
)
}
}
0 0
1 -2
3 -6
6 -12
10 -20
15 -30
21 -42
28 -56
36 -72
45 -90
26. Exercise: Fibonacci closure
ここもパス
続き