golangの日記

Go言語を中心にプログラミングについてのブログ

Go言語(golang) 関数

golang.png



関数の定義と実行


func 関数名(引数名 引数の型, 引数名 引数の型) 戻り値の型 {

}

関数名は、先頭の文字が小文字の場合は、変数などと同じで
パッケージ化して import で呼び出しても使うことができない(privateになる)


package main

// 引数や戻り値は省略できる
func hello() {
    // 何らかの処理をここに書く

    println("hello world")
}

func main() {
    // 関数名に () をつけて実行する
    hello() // hello world
}




目次



リターン(return)


途中で関数から抜けたり、後述の戻り値を返す。

package main

func hello() {

    // 関数から戻る
    return

    // 実行されない
    println("hello world")
}

func main() {
    hello()
}



引数


関数に値を渡す

package main

import "fmt"

// 引数名に続けて型を書く。複数渡す場合はカンマ区切り
func Hello(name string, age int) {
    fmt.Println("Hello - name:", name, ", age:", age)
}

func main() {
    Hello("Tanaka", 31) // name: Tanaka , age: 31
}


同じ型の引数が複数あれば以下のように書くことができる

func Hello(a, b, c string, d, e int, f bool) {

}



可変長引数


数が不定の引数。型名の前に ... をつける
受け取った関数内では、スライスとして使うことができる

package main

import "fmt"

func Hello(s string, a ...int) {
    // 関数内ではスライス
    fmt.Printf("%s: length: %d, %#v\n", s, len(a), a)
}

func main() {
    // いくつでも渡すことができる
    Hello("hello", 10, 20, 30) // hello: length: 3, []int{10, 20, 30}

    // 可変長引数は渡さなくてもよい
    Hello("hello") // hello: length: 0, []int(nil)
}

以下はエラー

// 最後が可変長引数でなければならない
func foo(a ...int, b string) {
}

// 可変長引数を複数受け取ることはできない
func bar(a ...string, b ...int) {
}



戻り値


関数を実行すると何らかの値を返す

package main

func hello() string {
    return "hello world"
}

func main() {
    // 実行すると値が返ってくる
    s := hello()

    println(s) // hello world
}



複数の戻り値を返す


複数の場合は () で囲って , 区切りで指定する。
指定した戻り値は、型通りの値を必ず返さなくてはならない。

package main

func hello() (string, int, bool) {
    return "hello", 100, true
}

func main() {
    s, i, b := hello()
    println(s, i, b) // hello 100 true

    // 必要ない値は _ で破棄できる
    s, _, _ = hello()
}



戻り値の変数名を指定する


return するときに何も書かなくても strerr が返される。
変数を var で定義した場合と同じで値はゼロ値になっている
(ゼロ値は string型 は "" 空文字、error型は nil)

package main

func hello() (str string, err error) {
    // varで変数を定義したようなもの
    // var str string
    // var err error

    str = "hello"
    return
}

func main() {
    str, err := hello()
    println(str, err == nil)
}



無名関数


関数名をつけずに変数に代入する

package main

func main() {
    f := func() {
        println("hello")
    }

    f() // hello
}



即時関数


その名の通り即時実行する関数

package main

func main() {
    s := func(n int) int {
        return n * 2
    }(10)

    println(s) // 20
}

即時関数で recover を使ってパニックを処理する例

package main

func main() {
    defer func() {
        if err := recover(); err != nil {
            println(err.(string)) // panicking!
        }
    }()

    panic("panicking!")
}



コールバック関数


引数に関数を渡す。(e-words: コールバック関数)

package main

func each(a []string, f func(int, string)) {
    for i, v := range a {
        // 引数で渡された関数を実行する
        f(i, v)
    }
}

func main() {
    a := []string{"foo", "bar", "baz"}
    each(a, func(i int, v string) {
        println(i, v)
        // 0 foo
        // 1 bar
        // 2 baz
    })
}

typeで定義しておけば以下のように書ける

type Callback func(int, string)

func each(a []string, f Callback) {
    for i, v := range a {
        f(i, v)
    }
}



再帰的定義


Wikipedia 再帰的定義

関数内でその関数自身を呼び出す。
無限ループと同じで止めるための処理を書いておかないと無限に繰り返される。

package main

func Recurse(n int) {
    // nが10以上のときに止めるための処理
    if n >= 10 {
        return
    }

    println(n)

    // 自分自身を呼び出す
    Recurse(n + 1)
}

func main() {
    Recurse(0)
    // 0
    // 1
    // 2
    // 3
    // 4
    // 5
    // 6
    // 7
    // 8
    // 9
}


再帰的定義の例

デスクトップにあるフォルダを辿ってファイルを表示するプログラム
フォルダは無限に存在するわけではないのでエラー以外は止める処理は必要ない

package main

import (
    "io/ioutil"
    "path/filepath"
)

func Walk(dirname string) error {
    list, err := ioutil.ReadDir(dirname)
    // エラーの場合は処理を止める
    if err != nil {
        return err
    }

    for _, v := range list {
        path := filepath.Join(dirname, v.Name())
        if v.IsDir() {
            // 自分自身を実行
            Walk(path)
        } else {
            println("file:", path)
        }
    }
    return nil
}

func main() {
    // デスクトップのパス
    err := Walk(`C:\Users\[user name]\Desktop`)
    if err != nil {
        println(err.Error())
    }
}



カリー化


Wikipedia カリー化

関数 F の引数で渡した x を保持できる

package main

func F(x int) func(int) int {
    return func(y int) int {
        return x * y
    }
}

func main() {
    a := F(3)(3)
    println(a) // 9



    g := F(10)

    a = g(10)
    println(a) // 100

    a = g(20)
    println(a) // 200
}
package main

func increment(n int) func() int {
    return func() int {
        n++
        return n
    }
}

func main() {
    i := increment(0)

    println(i()) // 1
    println(i()) // 2
    println(i()) // 3
}



defer (デファー)


defer は、先延ばし/保留/延期の意味で、関数から抜けるときまで実行を先延ばしにする。
以下は 1 -> 4 -> 3 -> 6 -> 2 の順で出力される。

package main

func f() {
    defer println(3) // return で関数を抜けても最後に実行される

    println(4)

    return

    println(5) // return で関数を抜けるので実行されない
}

func main() {
    println(1)

    defer println(2) // 6の後に実行される

    f()

    println(6)
}

// 1
// 4
// 3
// 6
// 2


defer を複数使った場合の実行順序。
以下は 3 -> 2 -> 1 の順で出力される。つまり、後の記述が先に実行される

package main

func main() {
    defer println(1)
    defer println(2)
    defer println(3)
}

// 3
// 2
// 1


deferを使った例

ファイルを開くと必ず fp.Close() で閉じますが、他の処理を書いていると
クローズを書き忘れることがあります。なので defer を使って閉じるようにする。

package main

import (
    "log"
    "os"
)

func main() {
    fp, err := os.Open("file.txt")
    if err != nil {
        log.Fatal(err)
    }
    defer fp.Close()

    // 何らかの処理が続く
}


deferrecover でパニックを捕捉する

package main

import "log"

func main() {
    // 関数を使えば複数行の処理を defer できる
    defer func() {
        if err := recover(); err != nil {
            log.Fatal(err)
        }
    }()

    panic("panicking!")
}