3章 合成型
from 初めてのGo言語 第2版 ―他言語プログラマーのためのイディオマティックGo実践ガイド
配列
インデックスに最大要素を超える値や負の値を指定すると、コンパイルエラーとなる
配列の大きさも型の一部なので、[3]int と [4]int は異なる型
一応、スライスを介して変換することは可能
code:go
a1 := 4int{1, 2, 3, 4}
s1 := a1:
a2 := 3int(s1)
初期化時には、1: 2 のようにインデックス番号を指定して値をセットすることが可能
code:go
var x = 3int{1: 2}
fmt.Println(x) // 0 2 0
スライス も同様
リテラルでは ... を使うことで配列長の指定を省略できる: var x = [...]int{10, 20, 30}
比較可能(comparable)
ゼロ値は「すべての要素に指定した型のゼロ値を持つ配列」
code:go
var x 3int
fmt.Println(x) // 0 0 0
スライス
配列とは異なり比較不可なので、== や != を使うとコンパイルエラーとなる
代わりに slices.Equal や slices.EqualFunc を用いる
code:go
x := []int{1, 2, 3, 4, 5}
y := []int{1, 2, 3, 4, 5}
z := []int{1, 2, 3, 4, 5, 6}
slices.Equal(x, y) // true
slices.Equal(x, z) // false
ゼロ値は nil
clear 関数を使うと、指定したスライスの全要素をゼロ値にできる
code:go
s := []string{"first", "second", "third"}
fmt.Println(s, len(s)) // first second third 3
clear(s)
fmt.Println(s, len(s)) // 3
capacity
配列やスライスの各要素が格納されている連続した メモリ領域
スライスの場合、len の値が capacity の値に達した状態で更に要素を append しようとすると、ランタイム は更に大きい capacity を持つ領域を確保する
具体的には、元のスライスのすべての値を新しいスライスにコピー した上で append された値を追加する
capacity の拡張ルールは v1.18 の段階では 256 未満の場合は 2 倍、以降は (現在の capacity + 768) / 4 ずつ増やす
radish-miyazaki.icon
v1.24 時点でも同じっぽい: 該当コード
実際の動作確認
code:go
x := make([]int, 1024, 1024)
fmt.Printf("Before append: len %d, cap %d\n", len(x), cap(x))
// Before append: len 1024, cap 1024
x = append(x, 0)
fmt.Printf("After append: len %d, cap %d\n", len(x), cap(x))
// After append: len 1025, cap 1536
// 1024 + ((1024 + 768) / 4) = 1024 + 448 = 1472
結果が違う…
コードを見る限り、roundupsize で メモリアロケーション しているっぽい: 該当コード
スライスをどう生成するか
考慮すべきは「どうやったら capacity の拡張回数を減らせるか」
スライスが大きくなる可能性がない: var を用いて nil スライスを生成する
var data []int
初期値が存在する場合・スライスの値が変化しない場合: スライスリテラルを用いる
data := []int{2, 4, 6, 8}
実際に動かせば予想できるが、実装中は分からない場合: make を用いて生成する
capacity と length にどのような値を指定するか
スライスを バッファ として利用する場合: length を指定する
必要なサイズがわかっている場合: そのサイズを length に指定する
上記以外: length に 0 と capacity を指定する
スライス式
スライス式で作成したサブスライスは、元のスライスとメモリを共有している
code:go
x := []string{"a", "b", "c", "d"}
y := x:2
z := x1:
x1 = "y"
y0 = "x"
z1 = "z"
fmt.Println("x:", x) // x: x y z d
fmt.Println("y:", y) // y: x y
fmt.Println("z:", z) // z: y z d
append が絡むと更にややこしくなる
code:go
x := []string{"a", "b", "c", "d"}
y := x:2
y = append(y, "z")
y = append(y, "1")
y = append(y, "2")
fmt.Println("x:", x) // x: a b z 1
fmt.Println("y:", y) // y: a b z 1 2
これを避けるために、サブスライスでは append を使わないようにするか、完全スライス式(フルスライス式) を使って append 時に上書きが生じないようにすべき
または make と copy を使えば良い
code:go
x := []int{1, 2, 3, 4}
y := make([]int, 4)
num := copy(y, x)
fmt.Println(y, num) // 1 2 3 4 4
配列 → スライス
スライス式を用いる
code:go
xArray := 4int{5, 6, 7, 8}
xSlice := xArray:
warning.icon スライス → スライス同様、メモリ共有は生じる
スライス → 配列
型変換を用いる
code:go
xSlice := []int{1, 2, 3, 4}
xArray := 4int(xSlice)
warning.icon
スライス内のデータが新しいメモリ領域にコピーされる(メモリ共有が生じない)
ただし、配列の ポインタ に変換すれば共有できる
code:go
xArrayPointer := (*4int)(xSlice)
配列のサイズをスライスのサイズより小さくできる
code:go
smallArray = 2int(xSlice)
大きくすることはできないが、これをコンパイラは検出できないため、panic を起こす
code:go
panicArray := 5int(xSlice)
fmt.Println(panicArray)
// panic: runtime error: cannot convert slice with length 4
// to array or pointer to array with length 5
文字列
Go の文字列は rune ではなく、バイト(byte)で構成される(バイト列)
このバイト列は「特定の 文字コード である」とは仮定されていない
しかし、ライブラリの中には「文字コードが UTF-8 でエンコードされたコードポイントの列から構成されていること」を仮定しているものがある
文字列リテラルも通常 UTF-8 で書かれる
len を使うとコードポイントの数ではなく、バイト数が返ってくる
code:go
var s string = "Hello ☀"
fmt.Println(len(s)) // 9
文字列を操作する際には、スライスやインデックスを使うよりも、strings や unicode/utf8 の関数を用いた方が楽に操作できる
rune ↔︎ 文字列 ↔︎ byte
code:go
// rune, byte → 文字列
var a rune = 'x'
var s string = string(a)
var b byte = 'y'
var s2 string = string(b)
// 文字列 → byte, rune
var s string = "Hello,☀️"
var bs []byte = []byte(s)
var rs []rune = []rune(s)
fmt.Println(bs) // 72 101 108 108 111 44 226 152 128 239 184 143
fmt.Println(rs) // 72 101 108 108 111 44 9728 65039
マップ
マップのゼロ値は nil
nil マップは書き込みを行うと panic を起こす
code:go
var nilMap mapstringint
nilMap"abc" = 3 // panic: assignment to entry in nil map
そのため、以下のように初期値を与えるのが一般的
totalWins := map[string]int{}
nil マップと空マップは異なる
code:go
fmt.Println(totalWins == nil) // false
存在しないキーの値を取り出そうとすると、値の型のゼロ値を返す
code:go
totalWins := mapstringint{}
fmt.Println(totalWins"ナイツ") // 0
マップのキーは comparable である必要がある
そのため、マップやスライス、関数、チャネル は指定できない
マップ自体は比較不可なので、== や != を使うとコンパイルエラーとなる
代わりに maps.Equal や maps.EqualFunc を用いる
code:go
m := mapstringint{
"hello": 5,
"world": 10,
}
n := mapstringint{
"world": 10,
"hello": 5,
}
fmt.Println(maps.Equal(m, n)) // true
clear 関数にマップを渡すと、length を 0 にできる
code:go
m := mapstringint{
"hello": 5,
"world": 10,
}
fmt.Println(m, len(m)) // maphello:5 world:10 2
clear(m)
fmt.Println(m, len(m)) // map[] 0
セット
Go には存在しないが、値が bool のマップを用いてシミュレートできる
code:go
intSet := mapintbool{}
vals := []int{5, 10, 2, 5, 8, 7, 3, 9, 1, 2, 10} // 5 と 10、2 が重複
for _, v := range vals {
intSetv = true
}
fmt.Println(len(vals), len(intSet)) // 11 8
fmt.Println(intSet5)
fmt.Println(intSet5000)
bool の代わりに空の 構造体 でも実現できる
boolは 1 Byte 消費するのに対し、空の構造体(struct{})はメモリを消費しない点で優れている
しかし、存在チェックを comma ok idiom でする必要があるのでコードが冗長になる
code:go
intSet := mapintstruct{}{}
vals := []int{5, 10, 2, 5, 8, 7, 3, 9, 1, 2, 10}
for _, v := range vals {
intSetv = struct{}{}
}
if _, ok := intSet5; ok { ... }
if _, ok := intSet5000; ok { ... }
構造体
初期化時に指定していないフィールドの値はゼロ値になる
構造体が comparable かどうかは、各フィールドが comparable かに依存する
Ruby や Python のように ==・!= を オーバーライド することはできない
ある構造体から別の構造体に型変換できるのは、全フィールドの名前、順番、型がすべて同じ場合に限られる
ただしこれらは別の型なので、インスタンス同士を比較することはできない(変換が必要)
code:go
type firstPerson struct {
name string
age int
}
type secondPerson struct {
name string
age int
}
f := firstPerson{name: "Alice", age: 20}
s := secondPerson{name: "Alice", age: 20}
fmt.Println(f == s) // f == s (mismatched types firstPerson and secondPerson)
fmt.Println(f == firstPerson(s)) // true
無名構造体の全フィールドが同じ名前、順番、型を持っていれば代入も比較も可能
code:go
f := firstPerson{name: "Alice", age: 20}
var g struct {
name string
age int
}
g = f
fmt.Println(f == g)
#Go #読書メモ