はじめに
Go の勉強のため、A Tour of Goに取り組んでいる
今回はMore types: structs, slices, and maps
章について、学んだことを記していく
まとめ記事はこちら
使えそうなスニペット
※筆者は VSCode を使用
tys
名前、フィールドの順にカーソルが移動する
type name struct {
}
forr
インデックス、要素、イテレータの順にカーソルが移動する
for _, var := range var {
}
struct や map の初期化の際、型を補完してくれる
ページごとの補足
Pointers
ポインタを理解するのに、この記事が参考になった
ざっくりした理解ではあるが、
- オペランド(変数および値)はメモリのどこかに格納されている
- ポインタとは、オペランドを格納しているメモリのアドレス
-
&
オペレータは、そのオペランドのポインタを示す -
*
オペレータは、そのポインタの指す先の変数を示す
i := 10
fmt.Println(i) // 10
fmt.Println(&i) // 0xc000012080
fmt.Println(*&i) // 10
Arrays
配列とは
Go の配列は値である
配列変数は配列全体を示す(C 言語とは違う)
配列は、 名前付きフィールドではなくインデックスフィールドを使用できる構造体の一種
と考えると腹落ちしやすかった
コンパイラに配列の要素数をカウントさせる
// 以下の 2 つは同じ型になる
a := [6]int{2, 3, 5, 7, 11, 13}
b := [...]int{2, 3, 5, 7, 11, 13}
初期値
初期化時に与えた引数より配列のサイズが大きい場合、ゼロ値が代入される
primes := [10]int{2, 3, 5, 7, 11, 13}
fmt.Println(primes) // [2 3 5 7 11 13 0 0 0 0]
Slice length and capacity
Go Slices: usage and internalsが非常に分かりやすい
スライスは、
- 配列へのポインタ
- スライスによって参照される要素の数
- 容量は、基になる配列の要素の数
で構成される、と考えると理解しやすかった
a := [6]int{2, 3, 5, 7, 11, 13}
s := a[:]
printSlice(s) // len=6, cap=6, [2 3 5 7 11 13]
s = s[:0] // 参照される要素の数を変更
printSlice(s) // len=0, cap=6, []
s = s[:4] // 参照される要素の数を変更
printSlice(s) // len=4, cap=6, [2 3 5 7]
s = s[2:] // ポインタを s[0] から s[2]に移動する == その分、参照できるlen, capが減る
printSlice(s) // len=2, cap=4, [5 7]
s = s[:4] // ポインタを移動した状態で、参照される要素の数を変更
printSlice(s) // len=4, cap=4, [5 7 11 13]
func printSlice(s []int) {
fmt.Printf("len=%d, cap=%d, %v\n", len(s), cap(s), s)
}
スライスの容量を越えて延ばすと、panic: runtime error: slice bounds out of range [:5] with capacity 4
が発生する
Creating a slice with make
slice はmake
関数でも定義できる
定義: func make([]T, len, cap) []T
容量(cap)を省略すると、長さ(len)と同じになる
// 同じ値を定義
fmt.Println(make([]int, 5, 5))
fmt.Println(make([]int, 5))
fmt.Println([]int{0, 0, 0, 0, 0})
Slices of slices
サンプル通りに書くと、redundant type from array, slice, or map composite
と警告が出る
入れ子の場合は内部の型を省略できる
// not very well
board := [][]string{
[]string{"_", "_", "_"},
[]string{"_", "_", "_"},
[]string{"_", "_", "_"},
}
// well
board := [][]string{
{"_", "_", "_"},
{"_", "_", "_"},
{"_", "_", "_"},
}
Appending to a slice
もし、元の配列 s が、変数群を追加する際に容量が小さい場合は、より大きいサイズの配列を割り当て直す
再割当てにはコストがかかるため、予め多くを append することが分かっているなら、必要な分だけ容量を確保したほうが良い
var s []int // 20ms: 初期容量は0
s = make([]int, 0, 1000000) // 7ms: 初期容量は1000000
for i := 0; i < 1000000; i++ {
s = append(s, 0)
}
ちなみに、時間測定の方法はこちらが参考になった
func elapsed() func() {
start := time.Now()
return func() {
fmt.Printf("%v", time.Since(start))
}
}
func main() {
defer elapsed()()
}
Exercise: Slices
パッケージインストール
手元で動かす場合は "golang.org/x/tour/pic"
をインストール必要がある
そのまま get しても良いが、Go Module
を使ってローカルに get してみた
Ref. 新規プロジェクトの作りかたがわからない
$ go mod init {PROJECT_NAME}
go get golang.org/x/tour
こうすると、go.mod
が作成され、get したパッケージのバージョン情報が記述される
module github.com/eyuta/goTour
go 1.15
require golang.org/x/tour v0.0.0-20201207214521-004403599411
実装
インデックスで挿入する場合は、
func Pic(dx, dy int) [][]uint8 {
exp := make([][]uint8, dx)
for i := 0; i < dx; i++ {
inner := make([]uint8, dy)
for j := 0; j < dy; j++ {
inner[j] = uint8(i * j)
}
exp[i] = inner
}
return exp
}
append を使う場合、
func Pic(dx, dy int) [][]uint8 {
exp := make([][]uint8, 0, dx)
for i := 0; i < dx; i++ {
inner := make([]uint8, 0, dy)
for j := 0; j < dy; j++ {
inner = append(inner, uint8(i*j))
}
exp = append(exp, inner)
}
return exp
}
尚、スライスの定義を[][]uint8{}
といったような容量を 0 にして定義した場合、若干速度が遅くなる
(Appending to a slice
参照)
初期容量=dx, dy: 22ms ~ 24ms
初期容量=0 : 24ms ~ 28ms
インデックスと append では速度の差はほとんどなかった
Exercise: Maps
make
するの忘れがちなので注意する
func WordCount(s string) (result map[string]int) {
result = make(map[string]int)
for _, field := range strings.Fields(s) {
elem, ok := result[field]
switch ok {
case true:
result[field] = elem + 1
case false:
result[field] = 1
}
}
return
}
Exercise: Fibonacci closure
func fibonacci() func() int {
x, y := 0, 1
return func() int {
result := x
x, y = y, x+y
return result
}
}