🚶

Goの基本のキ Part2

2022/11/17に公開

Tour of Goの内容メモ

前回の記事
https://zenn.dev/tamanegi/articles/da4fe0660af203

引き続きチュートリアルを行っていく。

Pointers | ポインタ

ポインタは値のメモリアドレスを指す。
変数 T のポインタ指定は、 *T 、ゼロ値は nil
ポインタによってアドレスが格納された変数は *T型 というポインタ型として扱われる。

var p * int

&オペレータを使うことで、その変数のポインタ型を作ることができる。
下の例では 変数 i のアドレスを格納した 変数 p は、*i型 になる。

i := 42
p = &i

* オペレータは、ポインタが指す先の変数を示す。

fmt.Println(*p) // ポインタpを通してiから値を読みだす
*p = 21         // ポインタpを通してiへ値を代入する

これをdereferencing または indirectingとして知られている。
調べてみると、リファレンスとデリファレンスは主にC言語やPerlで解説されている記事が多い。
リファレンスは、値のアドレスを参照するデータ(ポインタ)にアクセスすることを指す。
デリファレンスは、ポインタ参照先(リファレンスが指している)の値にアクセスすること指す。

Structs | 構造体

構造体とは、フィールドの集まりのこと。

type Vertex struct {
  X int
  Y int
}

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

> {1 2}

構造体が持つフィールドにアクセスするときは、ドットを使用する。

type Vertex struct {
	X int
	Y int
}

func main() {
	v := Vertex{1, 2}
	v.X = 4
	fmt.Println(v.X)
}

> 4

構造体のフィールドは、構造体のポインタを通してアクセスすることもできる。
フィールド X を持つ構造体のポインタ p がある場合、フィールド X にアクセスする方法は (*p).X のように書くことができる。
また、p.X のように省略して書くこともできる。

構造体は、呼び出し時に初期値を割り当てることもできる。また、フィールドを指定して初期化することもできる。
& を頭につけることで、構造体のポインタ型変数を作ることができる。

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, v2, v3, p)
}

> {1 2} {1 0} {0 0} &{1 2}

Arrays | 配列

[n]T型は型Tn個の変数の配列を表す。

intの10個の配列を宣言する場合:

var a [10]int

配列への代入、配列の呼び出し方

func main() {
  var a [2]string
  a[0] = "Hello"
  a[1] = "World"
  fmt.Println(a[0], a[1])
  fmt.Println(a)

  primes := [6]int{2, 3, 5, 7, 11}
  a2 := [6]int{1, 2, 3}
  fmt.Println(primes)
  fmt.Print(a2)
}

> Hello World
> [Hello World]
> [2 3 5 7 11 13]
> [1 2 3 0 0 0]  // 初期値を設定してない要素はゼロ値で初期値される

配列のサイズを変えることはできない。(固定長)

Slices | スライス

配列は固定長なのに対し、スライスは可変長である。
一般的に使うのは配列よりスライス。

[]T型は型Tのスライスを表す。

コロンで区切られた二つのインデックスlowとhighの境界を指定することによってスライスが形成される。

a[low: high]

スライス自体は、どんなデータも格納しておらず、元の配列の部分列を指し示す。
スライスの要素を変更すると、その元となる配列の対応する要素が変更される。
同じ元となる配列を共有している他のスライスは、それらの変更が反映される。

func main() {
	names := [4]string{
		"John",
		"Paul",
		"George",
		"Ringo",
	}
	fmt.Println(names)

	a := names[0:2]
	b := names[1:3]
	fmt.Println(a, b)

	b[0] = "XXX"
	fmt.Println(a, b)
	fmt.Println(names)
}

> [John Paul George Ringo]
> [John Paul] [Paul George]
> [John XXX] [XXX George]
> [John XXX George Ringo]

スライスという名前の通り、元の配列をスライスし。スライスした範囲を変数として生成する。
スライス型の変数を操作すると、元の配列や、同じ元となる配列を使用して作られたスライスにも変更が反映されてしまうため、注意が必要。

スライスのリテラルは、長さのない配列リテラルと同じようなものなので、以下のように記述することで、可変長な配列を作ることができる。

[3]bool{true, true, false} //これは配列リテラル
[]bool{true, true, false}  //これはスライスリテラル

用例:

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}]

スライスするとき、Goの既定値を使用することで、上限や下限の記述を省略することができる。
既定値は下限が0、上限はスライスの長さ。

以下の配列は、

var a[10]int

これらのスライス式と等価である。

a[0:10]
a[:10]
a[0:]
a[:]

スライスは、長さ(length)と要領(capacity)の両方を持っている。
長さは、スライスされた時の要素数のこと。
容量は、元の配列の要素数のこと。

func main() {
	s := []int{2, 3, 5, 7, 11, 13}
	printSlice(s)

	// Slice the slice to give it zero length.
	s = s[:0]
	printSlice(s)

	// Extend its length.
	s = s[:4]
	printSlice(s)

	// Drop its first two values.
	s = s[2:]
	printSlice(s)
}

func printSlice(s []int) {
	fmt.Printf("len=%d cap=%d %v\n", len(s), cap(s), s)
}

> len=6 cap=6 [2 3 5 7 11 13]
> len=0 cap=6 []
> len=4 cap=6 [2 3 5 7]
> len=2 cap=4 [5 7]

スライスのゼロ値はnil
nilスライスは0の長さと容量をもっており、元となる配列は持っていない。

func main() {
	var s []int
	fmt.Println(s, len(s), cap(s))
	if s == nil {
		fmt.Println("nil!")
	}
}

> [] 0 0
> nil!

スライスは組み込み関数makeを使用して作成することができ、この方法は動的サイズの配列を作成する方法でもある。

make関数はゼロかされた配列を割り当てて、その配列を指すスライスを返す。

a := make([]int, 5) // len(a)=5

makeの3番目の引数に。スライスの容量を指定することができる。

b := make([]int, 0, 5) // len(b)=0, cap(b)=5

b = b[:cap(b)]  // len(b)=5, cap(b)=5
b = b[1:]       // len(b)=4, cap(b)=4

スライスには、他のスライスを含む任意の型を含ませることができる。

ss := [][]int{
  []int{1, 2, 3},
  []int{4, 5, 6},
  []int{7, 8, 9},
}

for i := 0; i < len(ss); i++ {
  fmt.Printlen(ss[i])
}

> [1 2 3]
> [4 5 6]
> [7 8 9]

スライスに新しい要素を追加するには、組み込み関数の append を使用する。

使い方:

append(追加元のスライス, 追加する変数群)

戻り値は追加元のスライスに追加する変数群が合わさったスライス。
もし追加元のスライスが、追加する際に容量が小さい場合は、大きいサイズを割り当てしなおす。

func main() {
	var s []int
	printSlice(s)

	// append works on nil slices.
	s = append(s, 0)
	printSlice(s)

	// The slice grows as needed.
	s = append(s, 1)
	printSlice(s)

	// We can add more than one element at a time.
	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]

Range

for ループに利用する range は、スライスやマップ(map)をひとつずつ反復処理するのに使用する。
スライスをrangeで繰り返す場合、rangeは反復毎にインデックスと、インデックス場所の要素(値)のコピーを返す。

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

Pythonにおけるenumerate()に近い。
インデックスはや値は、_ へ代入することで捨てることができる。

for i, _ := range pow
for _, value := range pow

インデックスだけ必要な場合は、二つ目の値を省略することができる。

for i := range pow

練習

ネストした配列を作成する関数を作ってみる。

func f(x, y int) [][]int {
	s := make([][]int ,x)			// 長さxのスライスsを作成
	for i, _ := range s {			// sの長さだけforを回し、インデックスだけ利用する
		s[i] = make([]int, y)		// s[i] に 長さyのスライスを作成して格納
		for j := range s[i] {		// s[i]の長さ(つまりy)だけforを回し、インデックスだけ利用する。iの時のfor文と同じだが学習のため省略形で書いてみた。
			s[i][j] = i+j		// s[i][j] に 適当に値を入れてみる、とりあえずi+jとか。
		}
	}
	return s
}

func main() {
	fmt.Println(f(1,2))
	fmt.Println(f(2,3))
	fmt.Println(f(3,4))
	fmt.Println(f(4,4))
}

> [[0 1]]
> [[0 1 2] [1 2 3]]
> [[0 1 2 3] [1 2 3 4] [2 3 4 5]]
> [[0 1 2 3] [1 2 3 4] [2 3 4 5] [3 4 5 6]]

Maps | マップ

mapは、キーと値を関連付ける。
マップのゼロ値はnilnilマップはキーを持っておらず、キーを追加することもできない。
make関数で、指定された型のマップを初期化して使用可能な状態で返すことができる。

type Vertex struct {
	Lat, Long, float64
}

var m map[string] Vertex

func main() {
	m = make(map[string] Vertex)
	m["Bell Labs"] = Vertex {
		40.68433, -74.39967
	}
	fmt.Println(m["Bell Labs"])
}

> {40.68433 -74.39967}

mapの値を呼び出したいときは、関連付くキーを指定する。

var m = map[string]Vertex{
	"Bell Labs": Vertex{
		40.68433, -74.39967,
	},
	"Google": Vertex{
		37.42202, -122.08408,
	},
}


func main() {
	fmt.Println(m["Google"])
}

> {37.42202 -122.08408}

トップレベルの型が単純な型名の場合は、リテラルの要素から推測できるため、型名を省略することができる。

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要素の挿入や更新:

m[key] = elem

要素の取得

elem = m[key]

要素の削除

delete(m, key)

キーに対する要素が存在するか確認

elem, ok := m[key]

if ok {
	fmt.Println("True")
} else {
	fmt.Println("False")
}

もし、キーが存在しない場合、elmはmapの要素の型のゼロ値となる。

func main() {
	m := make(map[string]int)

	m["Answer"] = 42
	fmt.Println("The value:", m["Answer"])

	m["Answer"] = 48
	fmt.Println("The value:", m["Answer"])

	delete(m, "Answer")
	fmt.Println("The value:", m["Answer"])

	v, ok := m["Answer"]
	fmt.Println("The value:", v, "Present?", ok)
	
	if ok {
		fmt.Println("True")
	} else {
		fmt.Println("False")
	}
}

> The value: 42
> The value: 48
> The value: 0
> The value: 0 Present? false
> False

練習

スペースで区切られた単語の出現回数をカウントする関数を作成する

package main

import (
	"golang.org/x/tour/wc"
	"strings"
)

func WordCount(s string) map[string]int {
	ss := strings.Fields(s)
	m := make(map[string]int)
	
	for _, word := range ss {
		_, ok := m[word] 
		if ok {
			m[word] += 1
		} else {
			m[word] = 1
		}
	}
	
	return m
}

func main() {
	wc.Test(WordCount)
}

> PASS
>  f("I am learning Go!") = 
>   map[string]int{"Go!":1, "I":1, "am":1, "learning":1}
> PASS
>  f("The quick brown fox jumped over the lazy dog.") = 
>   map[string]int{"The":1, "brown":1, "dog.":1, "fox":1, "jumped":1, "lazy":1, "over":1, "quick":1, "the":1}
> PASS
>  f("I ate a donut. Then I ate another donut.") = 
>   map[string]int{"I":2, "Then":1, "a":1, "another":1, "ate":2, "donut.":2}
> PASS
>  f("A man a plan a canal panama.") = 
>   map[string]int{"A":1, "a":2, "canal":1, "man":1, "panama.":1, "plan":1}

Function values | 関数値

関数も変数として扱える。他の変数と同様に関数を渡すことができる。

import (
	"fmt"
	"math"
)

func compute(fn func(float64, float64) float64) float64 {
	return fn(3, 4)
}

func main() {
	hypot := func(x, y float64) float64 {
		return math.Sqrt(x*x + y*y)
	}
	fmt.Println(hypot(5, 12))

	fmt.Println(compute(hypot))
	fmt.Println(compute(math.Pow))
}

> 13
> 5
> 81

Goの関数はクロージャの性質を持つ。関数の外側にある変数を参照することができる。
JSやPythonでも同様のことができるし、同じ性質だと考えてよさそう。

以下の例は、adder関数はクロージャを返している。戻り値の関数から見て外側にある変数sumを操作できているのがポイント。
クロージャの中でも同名の変数がバインドされていることが分かる。

func adder() func(int) int {
	sum := 0
	return func(x int) int {
		sum += x
		return sum
	}
}

func main() {
	pos, neg := adder(), adder()
	for i := 0; i < 10; i++ {
		fmt.Println(
			pos(i),
			neg(-2*i),
		)
	}
}

> 0 0
> 1 -2
> 3 -6
> 6 -12
> 10 -20
> 15 -30
> 21 -42
> 28 -56
> 36 -72
> 45 -90

Discussion