関数の作成

完了

Go では、アプリケーションの他の部分から呼び出せる一連のステートメントを関数としてグループ化できます。 多くのステートメントから成るプログラムを作成する代わりに、関数を使用してコードを整理し、読みやすくすることができます。 コードが読みやすくなれば保守性も向上します。

これまでは、fmt.Println() 関数を呼び出して、main() 関数にコードを記述してきました。 このセクションでは、カスタム関数を作成する方法について説明します。 また、Go の関数で使用できる他のいくつかの手法についても見ていきます。

main 関数

ここまでに対話してきたのは main() 関数です。 この関数はプログラムの開始点であるため、Go のすべての実行可能プログラムにはこの関数があります。 プログラムに存在できる main() 関数は 1 つだけです。 Go パッケージを作成している場合は、main() 関数を記述する必要はありません。 パッケージの作成方法については、今後のモジュールで説明します。

Go でのカスタム関数の作成の基本に進む前に、main() 関数の重要な側面の 1 つを見てみましょう。 ご存知かもしれませんが、main() 関数にはパラメーターが 1 つもなく、何も返しません。 ただしこれは、コマンドライン引数のように、ユーザーから値を読み取れないという意味ではありません。 Go でコマンドライン引数にアクセスする必要がある場合、os パッケージと、プログラムに渡されたすべての引数を保持する os.Args 変数を使用して行うことができます。

次のコードでは、コマンド ラインから 2 つの数値を読み取って合計します。

package main

import (
    "fmt"
    "os"
    "strconv"
)

func main() {
    number1, _ := strconv.Atoi(os.Args[1])
    number2, _ := strconv.Atoi(os.Args[2])
    fmt.Println("Sum:", number1+number2)
}

os.Args 変数は、プログラムに渡されたすべてのコマンドライン引数を保持します。 これらの値は string 型であるため、合計するには int に変換する必要があります。

プログラムを実行するには、次のコマンドを使用します。

go run main.go 3 5

出力は次のようになります。

Sum: 8

上記のコードをリファクターして最初のカスタム関数を作成する方法を見てみましょう。

カスタム関数

関数を作成するための構文は次のとおりです。

func name(parameters) (results) {
    body-content
}

func キーワードを使用して関数を定義した後、それに名前を割り当てていることに注意してください。 名前の後に、関数のパラメーターのリストを指定します。 パラメーターの個数は 0 個を含め任意です。 関数の戻り値の型も定義できますが、ここでも、個数は 0 個を含め任意です。 (複数の戻り値の使用については次のセクションで説明します。)これらの値をすべて定義したら、関数本体の内容を記述します。

この手法を練習するために、前のセクションのコードをリファクターし、カスタム関数で数値を合計するようにしてみましょう。 次のコードを使用します。

package main

import (
    "fmt"
    "os"
    "strconv"
)

func main() {
    sum := sum(os.Args[1], os.Args[2])
    fmt.Println("Sum:", sum)
}

func sum(number1 string, number2 string) int {
    int1, _ := strconv.Atoi(number1)
    int2, _ := strconv.Atoi(number2)
    return int1 + int2
}

このコードによって作成される sum 関数は、2 つの string 引数を取って int にキャストし、それらを合計した結果を返します。 戻り値の型を定義する場合、関数はその型の値を返す必要があります。

Go では、変数と同じように、関数の戻り値に名前を設定することもできます。 たとえば、sum 関数は次のようにリファクターできます。

func sum(number1 string, number2 string) (result int) {
    int1, _ := strconv.Atoi(number1)
    int2, _ := strconv.Atoi(number2)
    result = int1 + int2
    return
}

ここで、関数の結果値をかっこで囲む必要があることに注意してください。 関数内で変数を使用することもでき、最後に return 行のみを追加することができます。 Go により、これらの戻り変数の現在の値が返されます。 関数の最後に return キーワードを記述するというシンプルさは (特に、複数の戻り値がある場合に) 魅力的です。 この方法はお勧めしません。 関数が何を返すかが不明です。

複数の値を返す

Go では、関数は複数の値を返すことができます。 これらの値は、関数のパラメーターを定義するのと同じような方法で定義できます。 つまり、型と名前を指定するのですが、名前は省略可能です。

たとえば、2 つの数値の合計だけでなく乗算も行う関数を作成するとします。 関数のコードは次のようになります。

func calc(number1 string, number2 string) (sum int, mul int) {
    int1, _ := strconv.Atoi(number1)
    int2, _ := strconv.Atoi(number2)
    sum = int1 + int2
    mul = int1 * int2
    return
}

ここで、関数の結果を格納するために 2 つの変数が必要です。 (ない場合はコンパイルされません。)次のように表示されます。

package main

import (
    "fmt"
    "os"
    "strconv"
)

func main() {
    sum, mul := calc(os.Args[1], os.Args[2])
    fmt.Println("Sum:", sum)
    fmt.Println("Mul:", mul)
}

もう 1 つの興味深い Go の機能は、関数の戻り値のいずれかが不要になった場合に、_ 変数に代入することによってその戻り値を破棄できることです。 _ 変数は、Go で戻り値を無視するための慣用的な方法です。 これによってプログラムがコンパイル可能になります。 したがって、合計値のみが必要な場合は、次のコードを使用できます。

package main

import (
    "fmt"
    "os"
    "strconv"
)

func main() {
    sum, _ := calc(os.Args[1], os.Args[2])
    fmt.Println("Sum:", sum)
}

関数の戻り値の無視については、今後のモジュールでエラー処理について説明する際に詳しく扱います。

関数のパラメーター値 (ポインター) を変更する

値を関数に渡すとき、その関数でのすべての変更が呼び出し元に影響を与えるわけではありません。 Go は"値渡し" のプログラミング言語です。 関数に値を渡すたびに、Go がその値を受け取ってローカル コピー (メモリ内の新しい変数) を作成します。 関数内でその変数に変更が加えられても、関数に送信したものには影響しません。

たとえば、人の名前を更新する関数を作成するとします。 次のコードを実行したときの結果に注目してください。

package main

import "fmt"

func main() {
    firstName := "John"
    updateName(firstName)
    fmt.Println(firstName)
}

func updateName(name string) {
    name = "David"
}

関数で名前を "David" に変更したのに、出力は "John" のままです。出力が変わらないのは、updateName 関数での変更によって変更されるのはローカル コピーだけだからです。 Go が渡したのは変数の値であって変数自体ではありません。

updateName 関数で行う変更を main 関数の firstName 変数にも反映させるには、ポインターを使用する必要があります。 ポインターは、別の変数のメモリ アドレスを格納する変数です。 関数へのポインターを送信する場合は、値を渡すのではなく、メモリ アドレスを渡します。 したがって、その変数に変更を加えるたびに、呼び出し元にも反映されます。

Go には、ポインターを操作する 2 つの演算子があります。

  • & 演算子は、その後にあるオブジェクトのアドレスを取ります。
  • * 演算子はポインターを逆参照します。 ポインターに含まれているアドレスにあるオブジェクトへのアクセスを付与します。

ポインターの働きを明確にするために、前の例を修正してみましょう。

package main

import "fmt"

func main() {
    firstName := "John"
    updateName(&firstName)
    fmt.Println(firstName)
}

func updateName(name *string) {
    *name = "David"
}

上記のコードを実行します。 今度は John ではなく David と出力されます。

まず、ポインターを受け取ることを指示するために関数シグネチャを変更する必要があります。 これを行うには、パラメーターの型を string から *string に変更します。 (後者も文字列ですが、厳密には文字列へのポインターです。)以後、その変数に新しい値を代入するときは、変数の左側にスター (*) を追加して、その変数の値を生成する必要があります。 updateName 関数を呼び出すとき、送信するのは変数の値ではなくメモリ アドレスです。 変数の左側の & 記号は、変数のアドレスを示します。