もがき系プログラマの日常

もがき系エンジニアの勉強したこと、日常のこと、気になっている技術、備忘録などを紹介するブログです。

Go言語 勉強(基礎編2)

はじめに

前回の続きです。

やってみた

制御構文

if文

if の条件式の中で変数定義ができ、その変数のスコープはif文の中のみ有効になる

func main() {
    if aaa := "test"; aaa == "test" {
        fmt.Println("OK => " + aaa)
    } else {
        fmt.Println("NG => " + aaa)
    }

    // ここではaaaは使えない
    // fmt.Println(aaa)
}
// OK => test

switch文

switch文の各caseでbreakは必要ない。

func main() {
    var aaa string = "午後"
    switch aaa {
    case "午前":
        fmt.Println("おはよう")
    case "午後":
        fmt.Println("こんにちは")
    case "夜":
        fmt.Println("こんばんは")
    }
}
// こんにちは

if文と同じような書き方もできる

func main() {
    switch aaa := "午後"; aaa {
    case "午前":
        fmt.Println("おはよう")
    case "午後":
        fmt.Println("こんにちは")
    case "夜":
        fmt.Println("こんばんは")
    }

    // ここではaaaは使えない
    // fmt.Println(aaa)    
}
// こんにちは

こんな書き方も可能

func main() {
    var aaa string = "午後"
    switch {
    case aaa == "午前":
        fmt.Println("おはよう")
    case aaa == "午後":
        fmt.Println("こんにちは")
    case aaa == "夜":
        fmt.Println("こんばんは")
    }
}
// こんにちは

関数

複数の戻り値が設定できる

func main() {
    var price, discountPrice = GetPrice(20)
    fmt.Println("価格 => " + strconv.Itoa(price))
    fmt.Println("割引価格 => " + strconv.Itoa(discountPrice))
}

func GetPrice(discount int) (int, int) {
    var price int = 100
    return price, price - discount
}
// 価格 => 100
// 割引価格 => 80

戻り値に名前をつけられる

func main() {
    var normalPrice, discountPrice = GetPrice(100)
    fmt.Println("通常価格 => " + strconv.Itoa(normalPrice))
    fmt.Println("割引価格 => " + strconv.Itoa(discountPrice))
}

func GetPrice(discount int) (normalPrice int, discountPrice int) {
    // 戻り値の名前をしていると、それをそのまま関数内で使用できて、returnに値をセットする必要がなくなる
    normalPrice = 500
    discountPrice = normalPrice - discount
    return
}
// 通常価格 => 500
// 割引価格 => 400

関数を変数に代入できる。型を指定しなければいけないところがなるほどと思った。

func main() {
    var priceFunc func(int) (int, int)
    priceFunc = GetPrice
    fmt.Println(priceFunc(200))
}

func GetPrice(discount int) (normalPrice int, discountPrice int) {
    normalPrice = 500
    discountPrice = normalPrice - discount
    return
}
// 500 300

defer文

defer で指定した処理が、関数の終了時に実行されるようになる。

__destruct()みたいな感じかもしれない。

func main() {
    Story()
}

func Story() {
    defer fmt.Println("おしまい")

    fmt.Println("昔々あるところにおじいさんとおばあさんがいました")
    fmt.Println("おじいさんは山へリオレウスを狩りに")
    fmt.Println("おばあさんは川へフルフルを狩りにでかけました")
}
// 昔々あるところにおじいさんとおばあさんがいました
// おじいさんは山へリオレウスを狩りに
// おばあさんは川へフルフルを狩りにでかけました
// おしまい

パニック

処理を停止させる。関数?

スタックトレースが表示される。

func main() {
    Story()
}

func Story() {
    fmt.Println("昔々あるところにおじいさんとおばあさんがいました")
    fmt.Println("おじいさんは山へリオレウスを狩りに")
    panic("パニック!モンハンと桃太郎の世界戦が一緒だ!")
 
    // ↑でpanicを呼び出しているので、このPrintは呼び出されない
    fmt.Println("おばあさんは川へフルフルを狩りにでかけました")
}
// 昔々あるところにおじいさんとおばあさんがいました
// おじいさんは山へリオレウスを狩りに
// panic: パニック!モンハンと桃太郎の世界戦が一緒だ!
// goroutine 1 [running]:
// main.Story()
//         /xxx/xxx/study_02/main.go:72 +0xbd
// main.main()
//         /xxx/xxx/study_02/main.go:63 + 0x20
// exit status 2

ちなみにdeferをつけると、deferを呼び出した後、panicへ流れるようだった。

func main() {
    Story()
}

func Story() {
    defer fmt.Println("おしまい")
    fmt.Println("昔々あるところにおじいさんとおばあさんがいました")
    fmt.Println("おじいさんは山へリオレウスを狩りに")
    panic("パニック!モンハンと桃太郎の世界戦が一緒だ!")
 
    // ↑でpanicを呼び出しているので、このPrintは呼び出されない
    fmt.Println("おばあさんは川へフルフルを狩りにでかけました")
}
// 昔々あるところにおじいさんとおばあさんがいました
// おじいさんは山へリオレウスを狩りに
// おしまい
// panic: パニック!モンハンと桃太郎の世界戦が一緒だ!
// goroutine 1 [running]:
// main.Story()
//         /xxx/xxx/study_02/main.go:72 +0x119
// main.main()
//         /xxx/xxx/study_02/main.go:63 +0x20
// exit status 2

メソッド

前回の続き でも使用したけど。もう一回おさらい

ポインタ部分がすぐ変換できない。。。

type Item struct {
    name  string
    price int
    stock int
}

func (item Item) GetName() string {
    return item.name
}
func (item Item) GetPrice() int {
    return item.price
}
func (item Item) GetStock() int {
    return item.stock
}
func (item *Item) Reduce(count int) {
    item.stock -= count
}

func main() {
    var item = Item{
        name:  "黄金の林檎",
        price: 500,
        stock: 5,
    }

    item.Reduce(2)
    item.Reduce(1)

    fmt.Println("アイテム名: " + item.GetName())
    fmt.Println("金額: " + strconv.Itoa(item.GetPrice()))
    fmt.Println("在庫: " + strconv.Itoa(item.GetStock()))
}
// アイテム名: 黄金の林檎
// 金額: 500
// 在庫: 2

インターフェース

こんな形かな? もうちょっと規模が大きいものを書いて、その時に使用してみたい。

type Car interface {
    EngineStart()
}

type MyCar struct {
    started bool
}

func (myCar MyCar) EngineStart() {
    myCar.started = true
    fmt.Println("エンジン点火!ブルルルーン!")
}

func main() {
    var myCar Car = MyCar{
        started: false,
    }

    myCar.EngineStart()
}
// エンジン点火!ブルルルーン!

エラーハンドリング

Go言語例外処理がないらしいが、Errorインターフェースというのがあるみたい。

用意されてる標準関数とかも、エラー時はErrorインターフェースを返すみたいだ。

自作の関数とかでErrorを返却したい場合は、errorsパッケージを読み込んで New関数を使うよう。

import (
    "errors"
    "fmt"
    "strconv"
)

type Item struct {
    stock int
}

func (item Item) GetStock() int {
    return item.stock
}
func (item *Item) Reduce(count int) error {
    item.stock -= count
    if item.stock <= 0 {
        return errors.New("在庫はもうない!")
    }

    return nil
}

func main() {
    var item = Item{
        stock: 5,
    }

    for i := 0; i < 5; i++ {
        fmt.Println(strconv.Itoa(i+1) + "回目の購入!")
        e := item.Reduce(2)
        if e != nil {
            fmt.Println(e.Error())
            break
        }
        fmt.Println("在庫を2つ消費した!現在の在庫は" + strconv.Itoa(item.GetStock()))
    }
}
// 1回目の購入!
// 在庫を2つ消費した!現在の在庫は3
// 2回目の購入!
// 在庫を2つ消費した!現在の在庫は1
// 3回目の購入!
// 在庫はもうない!

ゴルーチン

並列処理を行える。

ゴルーチンで呼び出した関数の終了を待たないので、戻り値は受け取れない。

mainから呼び出した場合は、mainの処理が終了した時点で、呼び出したゴルーチンも途中で処理を中断して終了する。

import (
    "fmt"
    "time"
)

func main() {
    CountUp()
    go CountUp()

    time.Sleep(2 * time.Second)
    fmt.Println("Count Up終わり!")
}

func CountUp() {
    defer fmt.Println("おしまい")
    for count := 0; count < 5; count++ {
        fmt.Println(count)
        time.Sleep(1 * time.Second)
    }
}
// 0
// 1
// 2
// 3
// 4
// おしまい
// 0
// 1
// 2
// Count Up終わり!

チャネル

ゴルーチン間で値の送受信を行うために使う。

import (
    "fmt"
    "time"
)

func main() {
    var ch = make(chan string)

    go CountUp("Goルーチン1号君", ch)
    go CountUp("Goルーチン2号君", ch)
    fmt.Println(<-ch)
    fmt.Println(<-ch)
    close(ch)
}

func CountUp(title string, ch chan<- string) {
    fmt.Println(title + " 起床")
    time.Sleep(300 * time.Millisecond)
    fmt.Println(title + " 朝食")
    time.Sleep(300 * time.Millisecond)
    fmt.Println(title + " 通勤")
    time.Sleep(300 * time.Millisecond)
    fmt.Println(title + " 勤務")
    time.Sleep(300 * time.Millisecond)
    fmt.Println(title + " 帰宅")

    ch <- title + " 就寝。今日も一日お疲れ様"
}
// Goルーチン2号君 起床
// Goルーチン1号君 起床
// Goルーチン2号君 朝食
// Goルーチン1号君 朝食
// Goルーチン1号君 通勤
// Goルーチン2号君 通勤
// Goルーチン1号君 勤務
// Goルーチン2号君 勤務
// Goルーチン1号君 帰宅
// Goルーチン1号君 就寝。今日も一日お疲れ様
// Goルーチン2号君 帰宅
// Goルーチン2号君 就寝。今日も一日お疲れ様

とりあえずサンプル書いてみたけど、チャネルはあまり理解できてない。。

すごく単純なプログラムだからだと思うけど、この動きを見ると、goルーチンが終わるまで待っている感じ。

もう少し深く勉強しなければ。

おわりに

前回書いた後いろいろと調べると、変数も関数もUpperCamelCaseで書いている記事が複数あったのですが、関数は UpperCamelCase, 変数は LowerCamelCaseと分けている記事もありました。

参考にしている資料は、以下だったので、とりあえずこっちに合わせておこうと思います。

関数は UpperCamelCase, 変数は LowerCamelCase

もうちょい基礎頑張って次はフレームワークも触ってみます。

あ、10/8は技術書典行きます!