はじめに
どうも、最近自分と同い年のプロダクトに触れて死ぬほど苦しんだけんつです。
ここ数ヶ月ずっと MySQL の検証や DBMS の勉強をしてきて思ったのだが、コード書いてない。
読む機会は履いて捨てるほどあるが、コード書くことなさすぎてめちゃくちゃ書けなくなっていた。昨日書いてみてこんなに書けないかと驚いた。
なので、リハビリをかねてハイパー中途半端に知っているぐらいの Go の再入門やろうと思います。
未読だった Effective Go で「ここわかんねーわ」とか「お、これは学びだな」と、思ったところをやっていきます。
今日はデータ構造周り。
Allocation with new()
Go にはメモリ割当を行うための組み込み関数として new と make がある。まずは new から。
new は単純に、ある型 T に対して new(T) と使用すると、メモリ領域を初期化せずに zero-value を持たせた上で確保しそのポインタを返すという挙動を取る。
例えば次のようなコードを実行する場合、実行結果は以下の通りになる。
package main import "fmt" type Person struct { Age int Name string } func main() { p := new(Person) fmt.Printf("%+v\n", p) num := new(int) fmt.Println(*num) }
❯ go run alloc_new.go &{Age:0 Name:} 0
これが例えば、Effective Go にあるような sync.Mutex や bytes.Buffer にあるようなものだとこうなる。
package main import ( "fmt" "sync" "bytes" ) type SyncedBuffer struct { Lock sync.Mutex Buffer bytes.Buffer } func main() { p := new(SyncedBuffer) fmt.Printf("%+v\n", p) }
❯ go run alloc_new2.go &{Lock:{state:0 sema:0} Buffer:{buf:[] off:0 lastRead:0}}
zero value によってすぐに利用できる構造体も存在する。
ちなみに var hoge T で変数を宣言した場合、それはポインタではないが new と同じように zero value をもつ。
初期化を伴わない*1と言われることが多い。
Allocation with make()
次は組み込み関数の make。make(T, args) という形を取るが、T に当てはまる型は slice, map, channel だけという特徴がある。
また new は返す値がポインタなのに対してこっちはポインタではない。またこちらは初期化を伴う。zero value と初期化の違いがわかりにくいので、中でもわかりやすい slice を例にする。
make を使って int を要素にもつスライスを作成するコードとその実行結果をあげる。
package main import "fmt" func main() { s := make([]int, 10, 100) fmt.Println(s) fmt.Printf("len(%d)\n", len(s)) fmt.Printf("cap(%d)\n", cap(s)) }
❯ go run alloc_make.go [0 0 0 0 0 0 0 0 0 0] len(10) cap(100)
実行結果からわかるように make を使用してスライスを作成した場合は array, len, cap *2の初期化を伴う。
これが new, make の違いとなる。
Arrays
配列はメモリの詳細なレイアウトを決める時に役立ち、割当を回避することができる場合もあるが基本的にはスライスのビルディングブロッックになっている。
また C の配列と比較して以下の違いを持っている。
- 配列は値。配列に配列を渡すと全ての値がコピーされる。
- 関数へ渡す時はポインタではなくコピーが渡される
- サイズも含めて配列は一つの型として扱われる。[3]int, [2]int は別の型として扱われる。
Slices
Slice は Array のラッパーで連続するデータに対して、汎用的で強力なインターフェースを提供している。
行列のように明確な次元をもつ必要が無い限り、大抵は Slice を利用する。Slice は元になる配列への参照を持っていて、あるスライスを別のスライスに代入すると二つのスライスは単一の配列に対する参照を持つこととなっている。
これは関数の引数にする場合も同様に動作することに注意が必要。
例えば以下のコードを実行すると、次のような結果が得られる。
package main import "fmt" func main() { a := [10]int{1,2,3,4,5,6,7,8,9,10} s1 := a[:] s2 := a[2:] fmt.Println(a) fmt.Println(s1) fmt.Println(s2) s1 = s2 s2[0] = 1 fmt.Println(a) fmt.Println(s1) fmt.Println(s2) }
❯ go run slice.go [1 2 3 4 5 6 7 8 9 10] [1 2 3 4 5 6 7 8 9 10] [3 4 5 6 7 8 9 10] [1 2 1 4 5 6 7 8 9 10] [1 4 5 6 7 8 9 10] [1 4 5 6 7 8 9 10]
おわりに
この調子で進めていきたい