フラミナル

考え方や調べたことを書き殴ります。IT技術系記事多め

Golang学習(値渡し/ポインタ/switch/for/defer/panic/recover)

f:id:lirlia:20210321183729p:plain

教材

学んだ事

値渡しについて

Goでは関数への変数受け渡しは値渡しとなる。参照渡しをしたい場合はポインタを利用する。

package main

func main() {
    firstName := "john"
    updateName(firstName)
    println(firstName)

    // ポインタ: 別の変数のメモリアドレスを格納する変数
    // &演算子: &の後にあるオブジェクトのメモリアドレスを取得
    // &firstName: firstNameのメモリアドレスを取得
    // firstNameが格納されたメモリアドレスを渡す
    updateNamePointer(&firstName)
    println(firstName)
}

// Goでは値渡しを行う
func updateName(name string) {
    name = "david"
}

// 意図的に参照渡しを行う
// ここでいうとnameがポインタ、*nameでポインタを逆参照する
// *演算子: ポインタに格納されたメモリアドレスにあるオブジェクトへのアクセスを付与
// ここでいうとfirstName変数のメモリアドレスに対してname変数で触れるようにしている
// メモリアドレスに格納された変数の型で受け取る
func updateNamePointer(name *string) {
    *name = "123"

    // すでにstring型でメモリを確保しているのでint型して突っ込めない
    //cannot assign int to *name (type string) in multiple assignment
    //*name, _ = strconv.Atoi(*name)
}

switch~caseの使い方

https://docs.microsoft.com/ja-jp/learn/modules/go-control-flow/4-use-defer-statement

通常の使い方、他の言語でもこんな感じ。

switch i {
case 0:
    fmt.Print("zero...")
case 1:
    fmt.Print("one...")
case 2:
    fmt.Print("two...")
default:
    fmt.Print("no match...")
}

switchで関数呼び出し&caseで複数条件

複数の条件に対しても使える&switchで関数呼び出し可能

switch time.Now().Weekday().String() {

case "Monday", "Tuesday", "Wednesday", "Thursday", "Friday":
    fmt.Println("It's time to learn some Go.")
default:
    fmt.Println("It's weekend, time to rest!")
}

caseで関数呼び出し可能

var email = regexp.MustCompile(`^[^@]+@[^@.]+\.[^@.]+`)
var phone = regexp.MustCompile(`^[(]?[0-9][0-9][0-9][). \-]*[0-9][0-9][0-9][.\-]?[0-9][0-9][0-9][0-9]`)

contact := "foo@bar.com"

switch {
case email.MatchString(contact):
    fmt.Println(contact, "is an email")
case phone.MatchString(contact):
    fmt.Println(contact, "is a phone number")
default:
    fmt.Println(contact, "is not recognized")
}

フォールスルー(fall through)

通常は1つのcaseに合致すればそこで処理が終了する。

i := 0
switch {
case i == 0:
  fmt.Println("zero")
case i == 1:
  fmt.Println("one")
case i == 2:
  fmt.Println("two")
default:
  fmt.Println("no match")
}
$ go run statement.go
zero

しかしこんな場合はどうだろうか。

switch {
case i == 0:
  fmt.Println("zero")
case i == 0:
  fmt.Println("zero2")
case i == 1:
  fmt.Println("one")
case i == 2:
  fmt.Println("two")
default:
  fmt.Println("no match")
}

この場合でも実行結果はzeroのみになる。このことからも最初のcaseに該当したら終了することがわかる。ここにfall throughを足してみる。

switch {
case i == 0:
  fmt.Println("zero")
  fallthrough
case i == 0:
  fmt.Println("zero2")
case i == 1:
  fmt.Println("one")
case i == 2:
  fmt.Println("two")
default:
  fmt.Println("no match")
}

すると出力結果が以下のようになる。

$ go run statement.go
zero
zero2

これはfall throughが設定されたcaseでは、switch文をbreakせず次のcaseに入るからである。念のため条件が違うi == 1の方も見てみる。

switch {
case i == 0:
  fmt.Println("zero")
  fallthrough
case i == 0:
  fmt.Println("zero2")
  fallthrough
case i == 1:
  fmt.Println("one")
case i == 2:
  fmt.Println("two")
default:
  fmt.Println("no match")
}

この場合の実行結果はこうなる。つまりcaseに書かれた条件を無視して次のcase処理を行うのがfall throughである。便利そうだが、動きに癖があるので使う場合は注意をしよう。

$ go run statement.go
zero
zero2
one

forの使い方

whileがないのでforでがんばる模様。

通常

func main() {
    sum := 0
    for i := 1; i <= 100; i++ {
        sum += i
    }
    fmt.Println("sum of 1..100 is", sum)
}

条件付きループ

func main() {
    var num int64
    rand.Seed(time.Now().Unix())
    for num != 5 {
        num = rand.Int63n(15)
        fmt.Println(num)
    }
}

無限ループ

func main() {
    var num int32
    sec := time.Now().Unix()
    rand.Seed(sec)

    for {
        fmt.Print("Writting inside the loop...")
        if num = rand.Int31n(10); num == 5 {
            fmt.Println("finish!")
            break
        }
        fmt.Println(num)
    }
}

defer / panic / reccover

defer

  • deferをつけた関数の実行を、他の関数の実行後に回す役割を持つ
  • deferをつける関数はいくつでもよく、つけた関数は後入れ先出し(LIFO)で実行される
  • 利用ケースとしてはファイルのクローズ処理など
package main

import "fmt"

func main() {
    for i := 1; i <= 4; i++ {
        defer fmt.Println("deferred", -i)
        fmt.Println("regular", i)
    }
}

結果↓

regular 1
regular 2
regular 3
regular 4
deferred -4
deferred -3
deferred -2
deferred -1

panic

  • 通常の処理(自分で書いているもの)は停止する
  • ただしdeferで後回しにした処理は実行される
package main

import "fmt"

func main() {
    g(0)
    fmt.Println("Program finished successfully!")
}

func g(i int) {
    if i > 3 {
        fmt.Println("Panicking!")
        panic("Panic in g() (major)")
    }
    defer fmt.Println("Defer in g()", i)
    fmt.Println("Printing in g()", i)
    g(i + 1)
}
Printing in g() 0
Printing in g() 1
Printing in g() 2
Printing in g() 3
Panicking!
Defer in g() 3
Defer in g() 2
Defer in g() 1
Defer in g() 0
panic: Panic in g() (major)

goroutine 1 [running]:
main.g(0x4)
        /Users/johndoe/go/src/helloworld/main.go:13 +0x22e
main.g(0x3)
        /Users/johndoe/go/src/helloworld/main.go:17 +0x17a
main.g(0x2)
        /Users/johndoe/go/src/helloworld/main.go:17 +0x17a
main.g(0x1)
        /Users/johndoe/go/src/helloworld/main.go:17 +0x17a
main.g(0x0)
        /Users/johndoe/go/src/helloworld/main.go:17 +0x17a
main.main()
        /Users/johndoe/go/src/helloworld/main.go:6 +0x2a
exit status 2

recover

  • パニックの後に制御を取り戻すことができる
  • recover関数はdeferの中でのみ利用できる
package main

import "fmt"

func main() {
    defer func() {
        if r := recover(); r != nil {
            fmt.Println("Recovered in main", r)
        }
    }()
    g(0)
    fmt.Println("Program finished successfully!")
}

func g(i int) {
    if i > 3 {
        fmt.Println("Panicking!")
        panic("Panic in g() (major)")
    }
    defer fmt.Println("Defer in g()", i)
    fmt.Println("Printing in g()", i)
    g(i + 1)
}

この場合は以下の出力となる。

Printing in g() 0
Printing in g() 1
Printing in g() 2
Printing in g() 3
Panicking!
Defer in g() 3
Defer in g() 2
Defer in g() 1
Defer in g() 0
Recovered in main Panic in g() (major)