Open35
【Go】ゴリラと学ぶGo言語入門
for のラベル
LOOP : for {
for{
break LOOP
}
}
- 通常のbreakは一つのfor文から脱出できる
- ラベルをつけておくとラベルの外まで脱出できる
fallthrought
- Goはforやswitchの中で、breakを書かずとも処理が終了する言語
- fallthroughtを使えば処理の続行ができる
独自の型
- type hoge int でint型のhogeという型を作成できる
型変換
- 文字列を数値に変換する例
package main
import (
"fmt"
"strconv"
)
func main() {
str := "5"
change, _ := strconv.Atoi(str)
fmt.Println(change)
}
- 文字(Aなど)を数値に変換すると「0」が返される
package main
import (
"fmt"
"strconv"
)
func main() {
str := "B"
change, _ := strconv.Atoi(str)
fmt.Println(change) // 0
}
- 本来なら、エラー処理してあげるべき
package main
import (
"fmt"
"log"
"strconv"
)
func main() {
str := "B"
change, err := strconv.Atoi(str)
if err != nil {
log.Fatal(err)
}
fmt.Println(change)
}
構造体
- 値をまとめておけるやつ
- typeで構造体の値を定義しておけば、都度定義する必要はない
メソッド
package main
import (
"fmt"
)
type Gorilla struct {
name string
}
func (g Gorilla) Uho() {
fmt.Println(g.name, g.Banana())
}
func (g Gorilla) Banana() string {
return "バナナ食べたい"
}
func main() {
var gorilla = Gorilla{
name: "Gorilla",
}
gorilla.Uho()
}
- 「func (g Gorilla) Uho() { 」のgは省略できる
- 省略した場合、構造体にアクセスできないだけ
- 単に、何かの値を返したい時とかに有用
ポインタ
- 「*」で構造体の内部が書き換えられるようになるよ
package main
import (
"fmt"
)
type Gorilla struct {
Name string
}
func (g *Gorilla) setName(name string) {
g.Name = name
}
func (g *Gorilla) Uho() {
fmt.Println(g.Name, g.Banana())
}
func (g Gorilla) Banana() string {
return "バナナ食べたい"
}
func main() {
var g = Gorilla{
Name: "Gorilla",
}
g.setName("GoriGorilla")
fmt.Println(g.Name)
}
メソッドの実態
- 第一引数に型と変数を受け取る関数である
コンパイル前
func (g *Gorilla) setName(name string) {
コンパイル後
func setName(g *Gorilla, name string) {
ポインタ型
- ポインタ型のゼロ値はnil
- 代入するとヌルポ的なことになる
- 値を取得するときは&を使う
var i int ;
var ip *int;
ip = &i
fmt.Println(ip)
- 更新は*を使う
func main() {
var ip = new(int)
fmt.Println(*ip) // 0
*ip = 4
fmt.Println(*ip) // 4
fmt.Println(ip) // 0x140000a6018
}
- 講座の中の図がとてもイメージ湧きやすいので是非〜
インターフェース
- 実装すべきメソッドを強制できるもの
- implementsに該当するものは無い
アサーション
- 「型。(他の型)」で型変換できる
- アサーション時の第二引数でエラーが受取れる!!!
エラーについて
- try catch 的な考えは無い
- エラー文を自分で生成するスタイル
並行処理
func main() {
go func() {
fmt.Println("waited")
}()
fmt.Println("wait...")
time.Sleep(1 * time.Second)
fmt.Println("go!!!!!")
}
- wait...
- waited
- 一秒止まる
- go!!!!!
- 並行処理同士は順序が保証されない
他の処理を待ちたいとき
- WaitGroupを使用する
- wg.Add(n)でn個待っているという宣言をする
- 下記は「wait1...」「wait2...」をそれぞれ待つことを宣言する
func main() {
var wg sync.WaitGroup
wg.Add(1)
go func() {
time.Sleep(1 * time.Second)
fmt.Println("wait1...")
wg.Done()
}()
wg.Add(1)
go func() {
time.Sleep(5 * time.Second)
fmt.Println("wait2...")
wg.Done()
}()
fmt.Println("waited")
time.Sleep(1 * time.Second)
wg.Wait()
fmt.Println("go!!!!!")
}
wg.Add(2)としたときの例
func main() {
var wg sync.WaitGroup
wg.Add(2)
go func() {
time.Sleep(1 * time.Second)
fmt.Println("wait1...")
wg.Done()
}()
go func() {
time.Sleep(5 * time.Second)
fmt.Println("wait2...")
wg.Done()
}()
fmt.Println("waited")
time.Sleep(1 * time.Second)
wg.Wait()
fmt.Println("go!!!!!")
}
- つまり、「wg.Add(n)であればwg.Done()の数はn個」である必要がある
- 「wg.Done()」はdeferを使用すれば安全だね!
- Goはデッドロックを感知してくれる!すごい!
- 並行処理内のエラーはerrGroup.Groupで取り扱う
- インポートに手こずっているので詳しくは後で
Goルーチン間のデータのやり取り
概要
- チャネルを使えば、Goルーチン間のデータのやり取りができる
- make(chan 型) で作成する
- 「チャネル変数 <-送りたい値」でデータを送ることができる
- 「<-チャネル変数」でデータを受け取る
- close(チャネル変数) でチャネルを閉じる。閉じたらゼロ値が送られる
無限ループ対策
- done<-colose( structs{}) で空の構造体を持たせたものを用意
- 空の構造体はサイズがゼロである。それゆえに、メモリ上にデータが展開されない。なので省エネ。
複数のチャネルを待つ時
- selectを使用する
- switch文みたいな感覚
- 参照
ポイントは、「select文は上から順番に評価されない」こと。
チャネルへの送受信は実行可能かを判断して、可能であれば実行される。
つまり、送信の場合は「キャパシティ(バッファ)がいっぱいになっていないか」、受信の場合は、「チャネルに値が送信されたか、チャネルが閉じられたか」を確認し、処理を進める準備が出来ていれば 実行される。
もし、どのチャネルも準備が出来ていなければ、defaultが実行される。もしdefaultを省略していたら>select文全体がブロックされる。
「レジ待ち」
package main
import (
"fmt"
"math/rand"
"time"
)
func main() {
rand.Seed(time.Now().UnixNano())
player_A_waited_second := make(chan int)
player_B_waited_second := make(chan int)
ch := make(chan struct{})
go func() {
waitSecond := rand.Intn(7)
fmt.Println("player_A = ", waitSecond)
time.Sleep(time.Duration(waitSecond) * time.Second)
waitSecond2 := rand.Intn(7)
fmt.Println("player_A = ", waitSecond2)
time.Sleep(time.Duration(waitSecond2) * time.Second)
player_A_waited_second <- waitSecond + waitSecond2
close(ch)
}()
go func() {
waitSecond := rand.Intn(7)
fmt.Println("player_B = ", waitSecond)
time.Sleep(time.Duration(waitSecond) * time.Second)
waitSecond2 := rand.Intn(7)
fmt.Println("player_B = ", waitSecond2)
time.Sleep(time.Duration(waitSecond2) * time.Second)
player_B_waited_second <- waitSecond + waitSecond2
close(ch)
}()
select {
case s := <-player_A_waited_second:
fmt.Println("player_A waited ...", s)
case s := <-player_B_waited_second:
fmt.Println("player_B waited ...", s)
}
fmt.Println("Done!!!!!")
}
コンテキストについて
- 特定の処理をキャンセルする
package main
import (
"context"
"time"
)
func main() {
ctx, cancel := context.WithCancel(context.Background())
go func(ctx context.Context) {
for {
select {
case <-ctx.Done():
// cancel()が来たらここ
println("cancel doing")
default:
// 6秒間はここ
println("do something")
}
time.Sleep(1 * time.Second)
}
}(ctx)
// 6秒待ってキャンセル
time.Sleep(6000 * time.Millisecond)
cancel()
time.Sleep(1 * time.Second)
}
テストに書き方
- 簡単に出力を返す例
- 「go test ./... 」で実行できる
package main
import "testing"
func Echo(name string) string {
return name
}
func TestEcho(t *testing.T) {
want := "gorilla"
got := Echo(want)
if got != want {
t.Fatalf("unexpected result. want=%q, got=%q", want, got)
}
}
- 成功例
ok Users/is/dev-env/go/go/go 0.430s
- 失敗例(echo関数で返す値に"a"を追加したとき)
main_test.go:13: unexpected result. want="gorilla", got="gorillaa"
FAIL
FAIL Users/is/dev-env/go/go/go 0.410s
FAIL