Go 言語の関数

Go 言語の関数

Go では関数 (function) を次のように定義します。

func 関数名(引数) 戻り値の型 {
	ステートメント
	return 戻り値
}

引数を受け取らない場合は「引数」は省略できます。また、戻り値が無い場合は、「戻り値の型」や return 文は省略できます。

次の例では、 add という名前の関数を作成しています。

package main

import "fmt"

func main() {
	z := add(1, 3)
	fmt.Println(z) // 4
}

func add(x int, y int) int {
	return x + y
}

add 関数は int 型の引数を二つ受け取り、 受け取った値を足し算してそれを、 int 型の戻り値として返しています。

これまで、おまじないのように書いてきた main 関数は、引数なし、戻り値なしの関数なので、 func main() { ... という型になっています。

Go の関数名

Go の関数名の命名には主に次のルールがあります。

  • 名前の先頭はアルファベットで始める
  • Go のキーワードと同じ名前は付けられない
  • 関数名に記号は使用できない

関数名の先頭は大文字・小文字どちらでも構いませんが、関数を定義したパッケージの外部から呼び出す関数名は大文字で始める必要があります。

Go では名前をつけない関数も作成できます。詳しくは「Go 言語の無名関数とクロージャ」をご覧ください。

Go の関数で複数の戻り値を返す

Go の関数では、複数の戻り値を呼び出し元に返すことができます。

複数の戻り値を返す時には、まず関数の「戻り値の型」を (型1, 型2) のように、 () の中に戻り値の型をカンマで区切って並べます。

戻り値を複数返す箇所では、 return に続き複数の値をカンマで区切って返します。

呼び出し元で戻り値を複数受け取る場合は、戻り値を受け取る変数をカンマで区切って並べます。

次の例の add 関数では、 int の値を三つ返しています。

package main

import "fmt"

func main() {
	m, n, z := add(1, 3) // 戻り値を三つ受け取る
	fmt.Println(m, n, z) // 1 3 4
}

func add(x int, y int) (int, int, int) {
	return x, y, x + y // 値を三つ返す
}

Go の関数の戻り値を無視する

戻り値が複数の関数を呼ぶ場合は、基本的に全ての戻り値を受け取る必要があります。

戻り値がひとつだけの関数を呼ぶ場合」、戻り値は必ずしも受け取る必要がありません。

ところが、必ずしも戻り値の全てが役に立つとか、興味があるとも限らないですよね。

Go では複数の戻り値の中で不要な戻り値はアンダースコア _ で受け取ることで 無駄な変数を宣言せずに済ませることができます。

例えば、上の例で add 関数が返す戻り値の中で、 足し算の結果 (三つ目の戻り値) だけを受け取りたい場合は、次のようにします。

	_, _, z := add(1, 3)

Go で可変長引数の関数を作る方法

Go では可変長の引数を受け取る関数を作るのは簡単です。

Go で可変長の引数を受け取る関数を定義するには、引数を ...型 という形で宣言します。

こうすることで、呼び出した時にその引数には、その型のスライスとして値が渡されます。

Go の「スライス」というのは、動的な配列のようなものです。詳しくは「Go のスライス」を参照してください。

次の例では、 add関数は可変長引数を、int のスライスとして受け取っています。

package main

import "fmt"

func main() {
	z := add(1, 3, 5, 7)
	fmt.Println(z)
}

func add(x int, params ...int) int {
	fmt.Printf("* %v : %T\n", params, params)
	for i, j := range params {
		fmt.Printf("* [i,j]=[%d,%d]\n", i, j)
		x += j
	}
	return x
}
main.go

実行結果は次のようになります。

go run main.go
* [3 5 7] : []int
* [i,j]=[0,3]
* [i,j]=[1,5]
* [i,j]=[2,7]
16

ここでは for-range ループでスライスの中身を取り出しています。 for-range ループについては Goの for range ループを参照してください。

Go 言語の関数は値渡し

このセクションを理解するには、ポインターについて理解している必要があります。 ポインターについては「Go 言語のポインター」を参照してください。

Go では 数値型 (int、float64 等)、文字列型、構造体 (struct) などの型では、基本的に値渡し (call by value) になります。

つまり、関数を呼び出す時の引数には値がコピーされ、呼び出された関数ではコピーされた値を使います。このため、 関数側で値を書き換えても、元の変数には影響しません。

package main

import "fmt"

func main() {
	x := 0
	fmt.Println("x = ", x) // x = 0
	foo(x)
	fmt.Println("x = ", x) // x = 0  (変わらない)
}

func foo(x int) {
	x++ // 1 を足しているが...
}

もし、関数内で呼び出し元の変数を書き換えたいなら、引数を参照で渡す必要があります。

package main

import "fmt"

func main() {
	x := 0
	fmt.Println("x = ", x) // x = 0
	foo(&x) // x の参照を渡している
	fmt.Println("x = ", x) // x = 1
}

func foo(x *int) {
	*x++
}

一方、マップ (map)スライス (slice) の場合は、参照渡し (call by reference) になります。

package main

import "fmt"

func main() {
	a := []int{1, 2, 3}
	fmt.Println("a = ", a) // a =  [1 2 3]
	foo(a)
	fmt.Println("a = ", a) // a =  [99 2 3]
}

func foo(a []int) {
	a[0] = 99
}

以上、ここでは Go の関数の基礎について説明しました。 無名関数やクロージャなどの少し進んだトピックについては「Go 言語の無名関数とクロージャ」をご覧ください。

ここまでお読みいただき、誠にありがとうございます。SNS 等でこの記事をシェアしていただけますと、大変励みになります。どうぞよろしくお願いします。

© 2024 Go 言語入門