前回の続きを読んでいく
Chapter 4 参照型
参照型はslice、map、channelの3つが定義されていて、make関数を仕様して生成する。
スライス
可変長配列を表現する型 以下の場合、要素数が10のint型のスライスが生成される
s := make([]int, 10) // 代入はインデックスを指定 s[0] = 100 // 要素数を超えた場合はランタイムパニックが起こる s[10] = 101 // これはエラー // 現在の要素数を調べるにはlenを使う len(s)
rubyだとsはオブジェクトになり、s.lengthと書けるけどGoだとlenが関数と定義されてるので、関数に渡す必要がある。
cap
capは容量
s := make([]int, 10, 15) fmt.Println(cap(s)) // => 15
容量は予め確保する容量。上記の場合、インデックスで参照できるのはs[9]までだけど、要素数を拡張するときに、capの範囲内なら新たにメモリを拡張する必要がない。 容量が10の場合、容量を拡張すると、Goのランタイムはもとの容量の10より大きいメモリ領域を確保して、拡張前のデータを丸ごと確保した領域へコピーする。この動作は処理コストが高いため、予めわかってる範囲で容量を取っておくとパフォーマンスの劣化を防ぐことができる。
スライスは配列型と同じようなリテラルで生成できる
// スライス s := []int{1, 2, 3, 4, 5} // こっちは配列 a := [5]int{1, 2, 3, 4, 5} // 配列からスライスを生成 s2 := a[0:2]
配列との書き方も似ているし違いがわかりにくい。 スライスは可変長なので上の配列でいう5を指定しない場合はスライスになる。
配列とスライスの違いは「拡張性」である。 Goの配列は要素数を含めて1つの型を表すので、[10]intと[11]intでは別の型として扱われる。
スライスの要素を拡張するにはappned関数を仕様する。appendを使用する場合は:=か=による代入を行う必要がある。変数の代入がないとコンパイルエラーになる。
s := []int{1, 2, 3, 4, 5} s = append(s, 6) // 代入しないとエラーになる
appendで要素を追加するときcapを超えるとスライスが拡張されるが、どの程度確証されるかはGoのランタイムに依存する。
copy
スライスにスライスの値を一括でコピーするcopy関数 以下のように記述する
// s1とs2はスライス n := copy(s1, s2)
s1の中身をs2で塗りつぶす。nにはコピーが成功した要素数が変える。 nにスライスが返りそうに見えるが、s1に対して破壊的変更が発生している。
簡易スライスと完全スライス式
簡易スライス式と完全スライス式の違い
a := [10]int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10} // 簡易スライス式 // この場合、aのインデックス2〜4の値をとって、容量は2から最後まで s1 := a[2:4] // 10 - 2 でcapが8になる。 // 完全スライス式 s2 := a[2:4:4] // aのインデックス2 〜 4までが容量になるので capが2になる。 s3 := a[2:4:6] // aのインデックス2 〜 6までが容量になるので capが4になる
スライスのfor
スライスの全要素をループするのはforとrangeを使う
s := []string{"alice", "bob", "carol"} for i, v := range s { fmt.Printf("%d回目、%sです\n", i, v) }
スライスと可変長引数
関数の引数に、以下のようにs ...intと定義することで、パラメータが[]int型のスライスにまとめることができる。
func sum(s ...int) int { ... return n }
この関数sumに引数を渡さない場合は0が渡される。 可変長引数の定義は引数の末尾に1つだけ定義できるので、可変長引数のあとに別の引数を定義することはできない
スライスは参照型
配列を関数に渡すときは値渡しになるが、スライスを渡すときは参照渡しになる。 配列からスライスを生成した場合、配列の値を書き換えるとスライスの値も変わる(参照元が変わるため)
ただ、配列からスライスを生成した場合でもappendを使って拡張した場合、スライスは新たにメモリを確保するため、もとの配列からの参照は外れる。
マップ
mapはGoでの連想配列に類するデータ構造、キーと値をペアで保存できる配列。
m := make(map[int]string) m["1"] = "US" m["81"] = "Japan" m["86"] = "China" // マップをリテラルでの書き方 m2 := map[int]string{1: "US", 81: "Japan", 86: "China"}
キーが重複すると要素の値は上書きされる。 mapの参照で、存在しないキーを指定して参照した場合、型の初期値が表示される。 mapの値の型がintの場合、0となる。
mapのキーを指定したとき、2つ目の戻り値にbool型が代入される。 キーがあればtrue、なければfalseが入る。
s, ok := m[10] // ifと絡めて書くことがおおい if _, ok := m[10]; ok { //キーがある場合の処理を書く }
マップの要素型がスライスなどの参照型である場合は初期値がnilになる。
マップのfor
mapもスライスと同様、forとrangeの組み合わせで全ての要素を参照できる。
for k, v := range m { }
rangeの戻り値にkとvを用意する。 キーの値は不定であることに注意する
len
len関数で、要素数を取得できる
delete
deleteを使うことでマップから要素を取り除くことができる。
delete(m, 2)
該当しないキーを設定した場合は何も処理されない。
チャネル
ゴルーチンとゴルーチンの間でデータの受け渡しを行うデータ構造。 チャネルの型は「chan [データ型]」と書く。
// 双方向チャネル var ch chan int // int型の受信専用チャネル var ch1 <-chan int // int型の送信専用チャネル var ch2 chan<- int
受信と送信がわかりにくい、、、<を手と思うようにし、chanが手の中にはいってるので<-chanが受信 chanから手が出てるのが送信chan<- と考えればよいか。
チャネルの生成
チャネルもmakeを使って生成する。
// バッファサイズ0 ch := make(chan int) // バッファサイズ10 ch := make(chan int, 10)
チャネルはキューの性質を備えるデータ構造であり、バッファはキューを格納する領域。
ch := make(chan int, 2) ch <- 5 ch <- 6 // ch <- 7 サイズが2なのでこのコメントアウトを外すとエラーになる。 i := <- ch // 5が入る i := <- ch // 6が入る
チャネルはゴルーチン間でデータを共有するもの。他のゴルーチンがチャネルへデータを送信するのを待ってる。
func main() { ch := make(chan int) // ch <- 10 ゴルーチンが動く前にい送信すると、受信するゴルーチンがないためエラーになる go reciver(ch) //チャネルを受信するゴルーチンを動かす i := 0 for i < 10000 { ch <- i // ゴルーチンが動いたあとはエラーにならない i++ } }
close
チャネルを生成するとオープンになり、close関数を使うことで閉じることができる。 クローズされたチャネルにデータ送信を行うとランタイムパニックが発生する。 クローズされたチャネルから受信をする場合は、チャネルのバッファ内は問題なく受信でき、バッファが空になったら型の初期値を返す。 クローズされたかどうかは第2の戻り値のbool型を判定する。
i, ok := <-ch
select
selectは複数のチャネルを効率的に処理するための構文
select { case e1 := <-ch1 // ch1が受信したときの処理 case e2 := <-ch2 // ch2が受信したときの処理 default: }
各チャネルが受信したタイミングで処理が走る。 引き続き5章を読んでいく。