サカナ未遂

プログラミング、筋トレ、子育て

スターティングGo言語 4章を読む

前回の続きを読んでいく

blog.tera-chan3700.com

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章を読んでいく。