Dragon Arrow written by Tatsuya Nakaji, all rights reserved animated-dragon-image-0164

go言語 関数

updated on 2020-10-31

go言語 関数

関数とは


一連の処理をまとめたもの

  • 引数で受け取った値を基に処理を行い戻り値として結果を返す機能
    • 必ずしも引数や戻り値が無くてもよい
  • 引数:関数の入力となるものf(x)の場合x
  • 戻り値(返り値):関数の出力となるもの

関数の種類

  • 組み込み関数
    • 言語の機能として組み込まれている関数
  • ユーザ定義関数
    • ユーザが定義した関数


関数の呼び出し方

引数を指定して呼び出す

  • 引数は変数や式を指定もよい 
    • 例: sum := repeat("Test!", 2+1)
  • 引数が複数ある場合はカンマで区切って指定する 
    • 例: sum := repeat("Test!", 2+1)
  • 戻り値がある場合は変数に代入したり式中で使う 
    • 例: sum := repeat("Test!", 2+1)


サンプルコード

package main
import "fmt"

func main() {
   sum := repeat("Test!", 2+1)
   fmt.Print(sum) // Test!Test!Test!
}

func repeat(word string, reps int) string {
   var concat string
   for i := 0; i < reps; i++ {
       concat += word
   }
   return concat
}


組み込み関数


print/println

表示を行う

make

コンポジット型の初期化

new

指定した型のメモリの確保

len/cap

スライスなどの長さ/容量を返す

copy

スライスのコピーを行う

delete

マップから指定したキーのエントリを削除

complex

複素数型を作成

imag/real

複素数の虚部/実数部を取得

panic/revocer

パニックを起こす/回復する


関数の定義


func 関数名 (引数 型) {
    // do something
}


サンプルコード

package main
import "fmt"

func add(x, y int) int {
    return x + y
}
func swap(x, y int) (int, int) {
    return y, x
}
func main() {
    fmt.Println(add(10, 20))
    fmt.Println(swap(10, 20))
}

(コンソール出力)

30 

20 10


多値の受け取り方


カンマで区切って値を受け取る

x, y := swap(10, 20)

省略したい場合はブランク変数(_)を用いる

x, _ := swap(10, 20)
_, y := swap(10, 20)


ちなみに、勉強のためにswap関数を使っているが、以下のようにカンマくぐりを使えば、値の入れ替えは、一時変数や関数を使わずにできる

x, y := y, x 


関数の定義

名前付き戻り値

  • 関数内では引数と同様に扱われる

以下の例では、x2, y2が名前付き戻り値


サンプルコード

package main

func swap(x, y int) (x2, y2 int) {
    y2, x2 = x, y

    // return x2, y2
    return // 明示しない場合は戻り値用の変数の値が返される
}

func main() {
    x, y := swap(10, 20)
    println(x, y)
}

(コンソール出力)

20 10 


無名関数

名前のない関数で、別名クロージャと呼ばれる


以下の形式で使われることが多い

func (引数 型) {
    // do something
}(値)


サンプルコード

package main
import "fmt"

func main() {
    msg := "Hello, 世界"
    // 無名関数 引数なし
    func() {
        fmt.Println(msg)
    }()

    // 無名関数 引数あり
    func(word string) {
        fmt.Println(word)
    }("テスト")

    // 無名関数 代入
    f := func(src string) string { return "Hello, " + src }
    fmt.Println(f("テスト"))
}

(コンソール出力)

Hello, 世界 

テスト 

Hello, テスト


無名関数の注意点

定義と実行のタイミングを気をつける

関数外の変数(自由変数)を参照している場合

実行のタイミングでは値が変わっている可能性がある


package main
import "fmt"

func main() {
    fs := make([]func(), 4)
    for i := range fs {
        fs[i] = func() { fmt.Println(i) }
        fs[i]()
    }
    for _, f := range fs {
        f()
    }
}

(コンソール出力)

3


関数型


関数はファーストクラスオブジェクト

変数への代入

引数に渡す

戻り値で返す


package main
import "fmt"


func main() {
    fs := make([]func() string, 2)
    fs[0] = func() string { return "hoge" }
    fs[1] = func() string { return "fuga" }
    for _, f := range fs {
        fmt.Println(f())
        fmt.Printf("型 -> %T\n", f)
    }
}

(コンソール出力)

hoge 

型 -> func() string 

fuga 

型 -> func() string


値のコピー


Goには値の型として、値型参照型が存在する。

値型

  • 関数に渡される時に、新たに値のコピーを作る (全く別のメモリを使用するので、呼び出し元の値は変化しない)

参照型

  • 関数に渡される時に、参照値(ポインタ、つまりメモリ上の場所)を渡す (同じメモリを共有する形になるので、呼び出し元の値も変化する)


値型

値型
1数値
2構造体
3配列


参照型

参照型
1インターフェース
2チャンネル
3マップ
4スライス

サンプル

package main
import "fmt"

const alternative = -999

type Strct struct {
    a, b int
}

func changeInt(aryNumber int) {
    aryNumber = alternative
}

func changeArray(aryVar [3]int) {
    aryVar[0] = alternative
}

func changeSlice(sliceVar []int) {
    sliceVar[0] = alternative
}

func changeMap(mapVar map[string]int) {
    mapVar["a"] = alternative
}

func changeStruct(strctVar Strct) {
    strctVar.a = alternative
}

func main() {
    fmt.Print("Int:\t値型\t")
    intVar := 1
    fmt.Printf("Before %v => ", intVar)
    changeInt(intVar)
    fmt.Printf("After %v\n", intVar) // 値渡しなので元の値は変わらない
    
    fmt.Print("Array:\t値型\t")
    arrayVar := [3]int{0, 1, 2}
    fmt.Printf("Before %v => ", arrayVar)
    changeArray(arrayVar)
    fmt.Printf("After %v\n", arrayVar) // 値渡しなので元の値は変わらない
    
    fmt.Print("Struct:\t値型\t")
    strctVar := Strct{a: 0, b: 1}
    fmt.Printf("Before %v => ", strctVar)
    changeStruct(strctVar)
    fmt.Printf("After %v\n", strctVar) // 値渡しなので元の値は変わらない

    fmt.Print("Slice:\t参照型\t")
    sliceVar := []int{0, 1, 2}
    fmt.Printf("Before %v => ", sliceVar)
    changeSlice(sliceVar)
    fmt.Printf("After %v\n", sliceVar) // 参照渡しなので元の値も変わる

    fmt.Print("Map:\t参照型\t")
    mapVar := map[string]int{"a": 0, "b": 1, "c": 2}
    fmt.Printf("Before %v => ", mapVar)
    changeMap(mapVar)
    fmt.Printf("After %v\n", mapVar) // 参照渡しなので元の値も変わる
}


ポインタ


  • 変数の格納先を表す値
  • 値で渡される型の値に対して破壊的な操作を加える際に利用する
  • 破壊的な操作 = 関数の外でも、グローバルに影響が出る


参照型(内部でポインタが用いられているデータ型)

  • インターフェース
  • スライス
  • マップ
  • チャネル

これらの型は、破壊的な操作になるため、ポインタを用いる必要がないことが多い



func f(xp *int intのポインタ型) {

*xp = 100 *でポインタの指す先に値を入れる

}

func main() {

    var x int

    f(&x) &でポインタを取得

    println(x) // 初期値の0ではなく、100が出力される

}


ポインタを使った偶奇判定

package main
import "fmt"
func main() {
    for i := 1; i <= 100; i++ {
        judge(&i) // iのポインタ(メモリ上の格納場所)を引数に渡す
    }
}

func judge(ip *int) {
    if *ip%2 == 0 {
        fmt.Printf("%d-偶数\n", *ip)
    } else {
        fmt.Printf("%d-奇数\n", *ip)
    }
}

(コンソール出力)

1-奇数 

2-偶数 

...

100-偶数


ポインタを使った値の入れ替え

package main


func main() {
    n, m := 10, 20
    swap(&n, &m)
    println(n, m) // 20 10
}


func swap(np, mp *int) {
    *np, *mp = *mp, *np
}

(コンソール出力)

20 10


package main

import "fmt"

func main() {
  a := 0
  b := 0
  sampleFunc(a, &b)
  fmt.Printf("a: %d\n", a) // 値渡し
  fmt.Printf("b: %d\n", b) //参照渡し
}


func sampleFunc(a int, b *int) {
  a = a + 1 
  *b = *b + 1
}

(コンソール出力)

a: 0 

b: 1


package main
import "strconv"

type Profile struct {
    Name string
    Age  int
}

func (p Profile) Set(name string, age int) {
    p.Name = name
    p.Age = age
}

func (p *Profile) Pointer_Set(name string, age int) {
    p.Name = name
    p.Age = age
}

func (p Profile) String() string {
    return "Name: " + p.Name + ", Age: " + strconv.Itoa(p.Age)
}

func (p Profile) Print() {
    s := p.String()
    println(s)
}

func main() {
    var p Profile
    p.Set("Endo", 40)
    p.Print() // Name: (空文字), Age: 0
    
    p.Pointer_Set("Tanaka", 40)
    p.Print() // Name: Tanaka, Age: 31
}

(コンソール出力)

Name: , Age: 0 

Name: Tanaka, Age: 40