エンジニアもどきの雑記

メモ帳です。きっと。macアプリ配信windowsでもできるようになるまで絶対にメイン機にしないマン。特筆しない限り環境は【win10home,i5-7200u,8g,64bit,巨乳が好き】

GoLangやるpart6

前回
dddpga1.hateblo.jp

宣言通りmapをやる。
参照型の続き。

phpでいう連想配列c#でいうdictionaryみたいな。
これもmakeを使う。
mapの時のmakeは以下。

make(名前)
make(名前, 要素数)


挙動は使って確認する。
わかりやすく、まずはphp連想配列を見てみよう

$arr = [
    "hoge" => 1,
    "fuga" => 5,
];

var_dump($arr["hoge"]);
// 1


いわゆるkey&value
スライスや配列に入る値に対して、インデックスの代わりに指定するkeyが入る。
これが連想配列でそれのようなことをgoではmapとして使える。


main.go

package main

import "fmt"

func main() {
        m := make(map[string]int)

        m["hoge"] = 1
        m["fuga"] = 5

        fmt.Println(m)
}


実行

[(゜-゜):daichi]@:sample$ go run main.go
map[hoge:1 fuga:5]


var m map[keyの型]valueの型で宣言する。
呼び出し型も今までと同様。
代入も同様。
で、スライスや配列のように最初から値の指定も当然できる。
それが以下

main.go

package main

import "fmt"

func main() {
        m := map[int]string{1: "hoge", 5: "fuga"}

        fmt.Println(m)
}


実行

[(゜-゜):daichi]@:sample$ go run main.go
map[1:hoge 5:fuga]


なんとなく想像はできると思うけど、やり方は以下
map[keyの型]valueの型{key: value[, key: value...]}

phpばっかりやってると関数のスコープ以外で{}を使うのって
すごい違和感なんだよね。最近慣れてきたけど。
で、上記のリテラルを改行で書く時、最近はやりの最後でも,を付けるってのをやらないと
エラーになる。
diff見るときにわかりやすくなるらしいね。


var m map[int]int{
    1:1,
    2:2,
    3:3,
}

みたいにしないとダメ。

そして想像できる限りの入れ子が当然できる。
mapの中にslice、mapの中に配列。mapの中にmapなど。

main.go

package main

import "fmt"

func main() {
        m := map[int][]int{
                1: {1},
                2: {1, 2},
                3: {1, 2, 3},
        }

        fmt.Println(m)

        m2 := map[string]map[int]string{
                "hoge": {1: "hhhh"},
                "fuga": {2: "ffff"},
        }

        fmt.Println(m2)
}


実行

[(゜-゜):daichi]@:sample$ go run main.go
map[1:[1] 2:[1 2] 3:[1 2 3]]
map[hoge:map[1:hhhh] fuga:map[2:ffff]]


[]とか型とかが続いて見づらいけど、最初にやった宣言の仕方で何も変わりない。
しっかりと読むことが大事。


また、参照の仕方とかは相変わらず同じなんだけど、存在チェックが少し特殊なので
やっておく。

main.go

package main

import "fmt"

func main() {
        m := map[int][]int{
                1: {1},
                2: {1, 2},
                3: {1, 2, 3},
        }

        mv, ok := m[1]
        fmt.Println(ok)
        fmt.Println(mv)

        m2 := map[string]map[int]string{
                "hoge": {1: "hhhh"},
                "fuga": {2: "ffff"},
        }

        mv2, ok := m2["hoge"]
        fmt.Println(ok)
        fmt.Println(mv2)

        mv3, ok := m2["foo"]
        fmt.Println(ok)
        fmt.Println(mv3)
}


実行

[(゜-゜):daichi]@:sample$ go run main.go
true
[1]
-----
true
map[1:hhhh]
-----
false
map[]


okという二つ目の値を入れてやる。
それのboolean値であるかないかの判断ができる。
okを使うのがマナーらしいのでここは基本okで

なんでこんな便利なもんがあるかっていうと、null的なものが初期で入っているわけではないため。
それが、一番最後のmap[]ってとこを見ればわかる。
ない値なのに、nilが返ってくるわけでもない。

そういったもののミスを減らすためだと思う。

mapから値を削除する。

deleteって関数がある
使い方は以下

delete(配列, key)

keyで指定して削除するだけ。
書かないことには覚えないので書く。

main.go

package main

import "fmt"

func main() {
        m := map[int][]int{
                1: {1},
                2: {1, 2},
                3: {1, 2, 3},
        }

        fmt.Println(m)

        delete(m, 2)

        fmt.Println(m)
}


実行

[(゜-゜):daichi]@:sample$ go run main.go
map[3:[1 2 3] 1:[1] 2:[1 2]]
map[1:[1] 3:[1 2 3]]


うい。
さて、次にchannelやる


channel


これをやる前にgo routineをまず憶えなきゃいけない。
なのでgoroutineをやる。

async/await。
いわば非同期処理を簡単に実行できる機能がゴルーチン。
そしてそれを表現するためにfor文を使う。
ので、先に簡単に説明する。

他の言語でもある通りのfor文がgoでも利用できる。
利用方法が少し特殊な場合もあるけど、今まで同様に書ける。

また、それを用いてゴルーチンがどういったものかとりあえず以下に。

main.go

package main

import "fmt"

func routine(a string) {
        for {
                fmt.Println(a)
        }
}

func main() {
        go routine("routine")
        for {
                fmt.Println("main")
        }
}


実行

は、ちょっと貼り付けられない状態なので説明すると、
mainとroutineが不規則に表示される。
goがゴルーチンを実行させる予約語
上記はroutine関数をゴルーチンで実行させてる。
forは繰り返しね。


で、channel
channelゴルーチン間で値の受け渡しをするためのもの。

var 変数名 chan 型

これもmakeを使って宣言できる。

make(名前[, buffer size])
バッファーのサイズは指定しないと0がデフォルトらしい。

チャネルはqueueの性質を持つ。
FIFOで先に入れたデータを先に取り出す、ロケットエンピツ(最近知らない人が増えたとか・・・)
そのデータがはいっていられる場所がバッファのサイズ。

ゴルーチン間で利用するものであって、送信用と受信用のものの宣言がある。
送信<-chan受信chan<-アローの場所受けるか、出すか、みたいな。

そしてゴルーチンで利用するためのものなのでゴルーチンがないと当然動かない。
とりあえず動かそう。

main.go

package main

import "fmt"

func routine(ch <-chan int) {
        for {
                i := <-ch
                fmt.Println(i)
        }
}

func main() {
        ch := make(chan int)

        go routine(ch)

        i := 0
        for i < 50 {
                ch <- i
                i++
        }
}


実行

[(゜-゜):daichi]@:sample$ go run main.go
0
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49


ゴルーチンの意味のない処理だけど。

func routine(ch <-chan int) {

のroutineの引数は<-chanch<-chanを受信する。
<-の受信、送信に注意したい。

main()関数の中でもmakeでチャンネルを生成してchに代入し、
iをchに入れてやる。その時も:=ではなく<-であることに注意。
chan型のものに入れるから<-を使ってる。

routine関数内でi := <-chってやってるから余計にわかりづらいけど、
こっちは受け取った<-ch(送信)をiに代入している。
ここも上手く説明できないからまだまだ力不足。
頑張ろう。

結果的にはゴルーチンで非同期に実行され続けてるだけ。


channelのclose

ファイル操作の時みたいにopen,closeという概念がchannelにはある。
makeで宣言した時、生成した時にはopen状態。
open状態だと<-で送信されたものをバッファサイズ分受け入れ放題。
が、それを閉じることができる。それがclose


例を作ってみる。

main.go

package main

import "fmt"

func routine(ch <-chan int) {
        for {
                i := <-ch
                fmt.Println(i)
        }
}

func main() {
        ch := make(chan int)

        go routine(ch)

        i := 0
        for i < 50 {
                ch <- i
                i++

                if i == 30 {
                        close(ch)
                }
        }
}


実行

0
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
0
0
panic: send on closed channel

goroutine 1 [running]:
main.main()
        /home/daichi/go_dev/src/sample/main.go:19 +0x8b
exit status 2


closeはchannelの入り口に蓋をするような状態。
closeしたchは送信を受けられなくなる。()
が、queueにたまっている値は受け取り続けることはできたりする。
蓋だけ閉じられたけど、出す部分は生きてるので中身がある限り出し続けます
みたいな状態。



と、今回はここまで。
次回以降gorutineをちゃんとと、for文while文とかやると思う。
そのあとにinterfaceとか構造体、ポインタとかかな。

それでとりあえず終わり。
↑のエラーで出てるpanicとか、gotoとかあんまり利用しないものは飛ばすつもり。
ラベルはやりたいけど。

この記事を書いてから気づいたけど、
自分で書くのもそうだけど読み返すのも復習になっていいなって思った。
ガリガリ更新してこう。

また次回。

次回
dddpga1.hateblo.jp