コンテンツにスキップ

Go/関数

出典: フリー教科書『ウィキブックス(Wikibooks)』
< Go


関数の基礎

[編集]

関数とは

[編集]

関数は、特定の処理をまとめた再利用可能なコードブロックです。Goの関数は、プログラムを構造化し、コードの重複を避け、保守性を高めるための重要な要素です。

関数の定義と呼び出し

[編集]

Goでは、funcキーワードを使用して関数を定義します。

package main

import "fmt"

func greeting(name string) string {
	return "Hello, " + name + "!"
}

func main() {
	// 関数の呼び出し
	message := greeting("Gopher")
	fmt.Println(message) // 出力: Hello, Gopher!
}

パラメータと引数

[編集]

パラメータ(仮引数)は関数定義時の変数、引数(実引数;アーギュメント)は関数呼び出し時に渡す値です。

// x, yはパラメータ
func add(x int, y int) int {
	return x + y
}

sum := add(5, 3) // 5, 3は引数

同じ型のパラメータが続く場合、型の省略が可能です:

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

戻り値の基本

[編集]

関数は処理結果を戻り値として返すことができます。

func square(n int) int {
	return n * n
}

複数の引数と戻り値

[編集]

複数の引数を持つ関数

[編集]

Goの関数は複数の引数を受け取ることができます。

func calculate(x int, y int, operator string) int {
	switch operator {
	case "+":
		return x + y
	case "-":
		return x - y
	default:
		return 0
	}
}

複数の戻り値を返す関数

[編集]

Goは複数の値を同時に返すことができる特徴があります。

package main

import "fmt"

// divide 関数の実装
func divide(x, y int) (int, error) {
	if x == 0 && y == 0 {
		return 0, fmt.Errorf("division zero by zero")
	}
	if y == 0 {
		return 0, fmt.Errorf("division by zero")
	}
	return x / y, nil
}

func main() {
	ary := [5][2]int{
		{10, 2},         // 正常な除算
		{10, 0},         // 除数がゼロ
		{0, 0},          // 0 ÷ 0 のケース
		{-10, 2},        // 負の数の除算
		{1000000000, 2}, // 大きな数での除算
	}
	for _, pair := range ary {
		fmt.Print(pair[0], " / ", pair[1])
		result, err := divide(pair[0], pair[1])
		if err != nil {
			fmt.Println("\t=> Error:", err)
		} else {
			fmt.Println("\t= ", result)
		}
	}
}
実行結果
10 / 2	=  5
10 / 0	=> Error: division by zero
0 / 0	=> Error: division zero by zero
-10 / 2	=  -5 
1000000000 / 2	=  500000000

名前付き戻り値

[編集]

Goでは戻り値に名前をつけることができます。これにより、関数内で戻り値の変数がゼロ値で初期化され、空のreturn文が必要で省略できません。

func split(sum int) (x, y int) {
	x = sum * 4 / 9
	y = sum - x
	return // 空のreturn文(blank return)
}

名前付き戻り値は以下の場合に特に有用です:

  • 戻り値の意味が明確になる
  • 複数の戻り場所がある関数で、戻り値の一貫性を保つ
  • デファーされた関数で戻り値を操作する
名前付き戻り値で割り算を実装
package main

import "fmt"

// divide 関数の実装
func divide(x, y int) (quotient int, err error) {
	if x == 0 && y == 0 {
		err = fmt.Errorf("division zero by zero")
	} else if y == 0 {
		err = fmt.Errorf("division by zero")
	} else {
		quotient = x / y
	}
	return
}

func main() {
	ary := [5][2]int{
		{10, 2},         // 正常な除算
		{10, 0},         // 除数がゼロ
		{0, 0},          // 0 ÷ 0 のケース
		{-10, 2},        // 負の数の除算
		{1000000000, 2}, // 大きな数での除算
	}
	for _, pair := range ary {
		fmt.Print(pair[0], " / ", pair[1])
		result, err := divide(pair[0], pair[1])
		if err != nil {
			fmt.Println("\t=> Error:", err)
		} else {
			fmt.Println("\t= ", result)
		}
	}
}

関数の型

[編集]

関数の型宣言

[編集]

関数も型として扱うことができます。

package main

import "fmt"

type Operation func(x, y int) int

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

func multiply(x, y int) int {
	return x * y
}

func main() {
	var op Operation = add
	fmt.Println(op(5, 3)) // 出力: 8

	op = multiply
	fmt.Println(op(5, 3)) // 出力: 15
}

関数を変数に代入

[編集]

関数は変数に代入して使用することができます。

package main

import "fmt"

func main() {
	square := func(n int) int {
		return n * n
	}

	fmt.Println(square(5)) // 出力: 25
}

関数を引数として渡す

[編集]

関数呼び出しを他の関数の引数として渡すことができます。

package main

import "fmt"

func applyOperation(x, y int, operation func(int, int) int) int {
	return operation(x, y)
}

func main() {
	sum := applyOperation(5, 3, func(x, y int) int {
		return x + y
	})
	fmt.Println(sum) // 出力: 8
}

クロージャ

[編集]

クロージャとは

[編集]

クロージャは、外部のスコープにある変数を参照できる関数です。

package main

import "fmt"

func counter() func() int {
	count := 0
	return func() int {
		count++
		return count
	}
}

func main() {
	c := counter()
	fmt.Println(c()) // 出力: 1
	fmt.Println(c()) // 出力: 2
	fmt.Println(c()) // 出力: 3
}

クロージャの実践的な使い方

[編集]

クロージャは状態を保持するために使用できます。

package main

import "fmt"

func fibonacci() func() int {
	prev, curr := 0, 1
	return func() int {
		result := prev
		prev, curr = curr, prev+curr
		return result
	}
}

func main() {
	fmt.Println(fibonacci()()) // 出力: 3
}

可変長引数

[編集]

可変長引数の基本

[編集]

可変長引数を使用すると、任意の数の引数を受け取ることができます。

package main

import "fmt"

func sum(numbers ...int) int {
	total := 0
	for _, num := range numbers {
		total += num
	}
	return total
}

func main() {
	fmt.Println(sum(1, 2, 3, 4, 5)) // 出力: 15
}

スライスの展開

[編集]

スライスを可変長引数として渡す場合は、`...`を使用して展開します。

package main

import "fmt"

func sum(numbers ...int) int {
	total := 0
	for _, num := range numbers {
		total += num
	}
	return total
}

func main() {
	numbers := []int{1, 2, 3, 4, 5}
	fmt.Println(sum(numbers...)) // 出力: 15
}

メソッド

[編集]

メソッドの定義

[編集]

メソッドは、特定の型に関連付けられた関数です。

package main

import "fmt"

type Rectangle struct {
	width, height float64
}

func (r Rectangle) Area() float64 {
	return r.width * r.height
}

func main() {
	r := Rectangle{width: 10, height: 5}
	fmt.Println(r.Area()) // 出力: 50
}

ポインタレシーバとバリューレシーバ

[編集]

ポインタレシーバを使用すると、メソッド内で構造体を変更できます。

package main

import "fmt"

type Rectangle struct {
	width, height float64
}

func (r Rectangle) Area() float64 {
	return r.width * r.height
}

func (r *Rectangle) Scale(factor float64) {
	r.width *= factor
	r.height *= factor
}

func main() {
	r := Rectangle{width: 10, height: 5}
	r.Scale(2)
	fmt.Println(r.Area()) // 出力: 200
}

defer文

[編集]

defer文の基本

[編集]

defer文は、関数の終了時に実行される処理を定義します。

func processFile(filename string) {
	f, err := os.Open(filename)
	if err != nil {
		return
	}
	defer f.Close() // 関数終了時にファイルを閉じる

	// ファイル処理
}

複数のdefer文の実行順序

[編集]

複数のdefer文は、LIFO(後入れ先出し)の順序で実行されます。

package main

import "fmt"

func main() {
	defer fmt.Println("1")
	defer fmt.Println("2")
	defer fmt.Println("3")
	// 出力順序: 3, 2, 1
}

関数のベストプラクティス

[編集]

関数の命名規則

[編集]
  • 明確で説明的な名前を使用する
  • キャメルケースを使用する(最初の文字が大文字の場合はエクスポート可能)
  • 動詞または動詞句で始める

適切な関数の粒度

[編集]
  • 単一責任の原則に従う
  • 適切な長さを保つ(通常20-30行程度)
  • 必要に応じて分割する

ドキュメンテーション

[編集]

関数にはわかりやすいコメントを付け、godocのフォーマットに従います。

// Calculate returns the result of a mathematical operation on two integers.
// The operator parameter should be one of "+", "-", "*", or "/".
func Calculate(x, y int, operator string) (int, error) {
	// 実装
}

演習問題

[編集]
  1. 以下の仕様を満たす関数を作成してください:
    • 整数のスライスを受け取り、偶数の要素のみを含む新しいスライスを返す
    • エラー処理は考慮しない
  2. 次の関数を名前付き戻り値を使用してリファクタリングしてください:
    func divide(x, y float64) (float64, error) {
    	if y == 0.0 {
    		return 0, fmt.Errorf("division by zero")
    	}
    	return x / y, nil
    }
    
  3. クロージャを使用して、フィボナッチ数列の最初のn項を生成する関数を実装してください。

まとめ

[編集]

本章では、Goにおける関数の基本的な概念から高度な使用方法まで学びました:

  • 関数の基本的な構文と使用方法
  • 複数の戻り値と名前付き戻り値
  • 関数型とクロージャ
  • メソッドの定義と使用
  • defer文による後処理の実装
  • ベストプラクティスとコーディング規約

これらの知識を活用することで、より効率的で保守性の高いGoプログラムを作成することができます。