Closed28

A Tour of Go やってみた Part3

kun432kun432

Basics: More types: structs, slices, and maps.

1. Pointers

Goはポインタを扱うことができる。

1-1.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
1-1.go
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
kun432kun432

2. Structs

構造体

2.go
package main

import "fmt"

type Vertex struct {
	X int
	Y int
}

func main() {
	fmt.Println(Vertex{1, 2})
}
出力
{1 2}
kun432kun432

3. Struct Fields

構造体のフィールドには .でアクセスできる。

3.go
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}
kun432kun432

4. Pointers to structs

構造体のフィールドには構造体ポインタ経由でもアクセスできる。構造体vのポインタpを使って、vのフィールドXにアクセスするには、(*p).Xと書くが、より簡単にp.Xと書ける。

4.go
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}
kun432kun432

5. Struct Literals

構造体リテラルは、フィールドの値を列挙するか、名前付きフィールドで値をセットすることで、表される。名前付きフィールドの場合、指定しないフィールドがあればその値はゼロ値になる。&で構造体の値へのポインタとなる。

5.go
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}
kun432kun432

6. Arrays

配列は[要素数]型で表す。例えば要素数10の整数型の配列aは以下

var a[10]int

配列の長さは、その型の一部となるため、配列のサイズを変更はできない。

6.go
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]
kun432kun432

7. Slices

配列はサイズが固定だが、スライスを使えば配列の要素を動的に変更できる。スライスは[]型で指定する。インデックスの上限・下限を[上限:下限]として指定することができる。

7.go
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]
kun432kun432

8. Slice are like references to arrays

スライスは配列へのリファレンスのようなもの。スライスはデータを格納していなくて、単に基の配列の一部分を示す「ビュー」と言える。

よって、スライスの要素を変更すると、元の配列の要素も変更されるし、同じ配列を共有している複数のスライスがあってどれかのスライスで変更を行うと、全てのスライスで変更される(ように見える)

8.go
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]
kun432kun432

9. Slice literals

スライスのリテラルは配列リテラルから長さを省いたもの。

配列リテラル。

[3]bool{true, false, true}

スライスリテラル。こちらは上と同じ配列をまず作成して、その配列を参照するスライスを生成する。

[]bool{true, false, true}
9.go
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}]
kun432kun432

10. Slice defaults

スライスは[上限:下限]を省略するとデフォルト値を省略できる、ってピンとこない。

以下の配列があるとして、

var a [10]int

上記の配列のスライスは以下の4通りの書き方ができる。

a[0:10]
a[:10]
a[0:]
a[:]
10.go
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]
kun432kun432

11. Slice length and capacity

スライスには_length_(長さ)と_capacity_(容量)がある。

  • スライスの長さは、スライスに含まれる要素数。len()で求めることができる。
  • スライスの容量は、スライスの最初の要素から数えて、基となる配列の要素の数。cap()で求めることができる。

これはどういうことかというと、上で書いてあった通り、Go のスライスは、単なる可変長の配列「ビュー」であって、その下には必ず「基になる配列」が存在している。で、len()は現在見えているスライスの要素数、cap()は現在見えているスライスの最初の要素から、基となる配列の終わりまで論理的に見える要素数≒再スライス可能な要素数、ということになる。自分でも何言ってるのかよくわからなくなるけど。

8.go
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

このように、

  1. len(s) は「今そのビューに見えている要素の数」
  2. cap(s) は「そのビューを左右に動かさずに(左端を動かさずに)、右側にどれだけ広げられるかの最大数」

── と考えると、専門用語を使わなくてもイメージしやすいかと思います。

なので、容量を超えて広げることはできない。

8-1.go
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)
kun432kun432

12. Nil slices

スライスのゼロ値はnilとなる。nilなスライスの長さ(len)と容量(cap)はともに0となり、基となる配列を持たない。

12.go
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!
kun432kun432

13. Creating a slice with make

スライスは組み込み関数makeを使うと動的なサイズのスライスを作成できる。makeは、ゼロ化された配列を割り当てて、その配列を指すスライスを返す。

第2引数で長さ、第3引数で容量を指定する。

13.go
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]
kun432kun432

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
kun432kun432

15. Appending to a slice

組み込みの関数appendを使うとスライスへ新しい要素を追加できる。

func append(s []T, vs ...T) []T

append の最初のパラメータ s は型 T のスライスで、残りのパラメータ vs はスライスに追加する T 型の値となる。

append の結果として返されるスライスには、元のスライスの全要素に加えて、指定された値が順に含まれる。

もし元のスライス s の基となる配列の容量が、追加しようとする全ての要素を収めきれない場合は、より大きな配列が新たに割り当てられる。返されたスライスは、その新しく割り当てられた配列を指す。

15.go
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]
kun432kun432

16. Range

forループで使用するrangeは、スライスやマップ(map)を1つづつ反復処理するのに使う。

スライスをrangeで繰り返すと、rangeは2つの変数を返す

  1. インデックス
  2. インデックスの場所の要素のコピー
16.go
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
kun432kun432

17. Range continued

インデックスや値は、_に代入すれば捨てることができる。

17.go
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
kun432kun432

19. Maps

mapはキーと値をマップする。マップのゼロ値はnilであり、nilマップはキーを持たず、キーの追加もできないが、makeを使うと指定された型のマップが初期化され、使用可能な状態で返る。

19.go
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}
kun432kun432

20. Map literals

mapリテラルはキーが必要

20.go
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)
}
kun432kun432

21. Map literals continued

トップレベルのリテラルで型が明示されている場合、その要素では型名を省略できる。

21.go
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}]
kun432kun432

22. Mutating Maps

マップの操作いろいろ

  • 挿入・更新
  • 取得
  • 削除
  • 存在確認
22.go
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
kun432kun432

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

組み合わせたもの

24.go
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
kun432kun432

25. Function closures

Goの関数はクロージャを作ることができる。クロージャは、関数の外側で定義された変数を参照できる関数値であり、クロージャ内の関数はその参照先の変数を読み書きできるので、「関数に変数が束縛(bind)されている」ように振る舞う。

以下のaddr関数はクロージャを返し、各クロージャはそれぞれのsum変数へバインドされる。

25.go
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
kun432kun432

26. Exercise: Fibonacci closure

ここもパス

このスクラップは3ヶ月前にクローズされました