Goで可変個引数関数を使用する方法
序章
可変個引数関数は、単一の引数として0、1、またはそれ以上の値を受け入れる関数です。 可変個引数関数は一般的なケースではありませんが、コードをよりクリーンで読みやすくするために使用できます。
可変個引数関数は、見た目よりも一般的です。 最も一般的なものは、fmtパッケージのPrintln
関数です。
func Println(a ...interface{}) (n int, err error)
楕円のセット(...
)が前に付いたパラメーターを持つ関数は、可変個引数関数と見なされます。 省略記号は、提供されるパラメーターが0、1、またはそれ以上の値であることを意味します。 fmt.Println
パッケージの場合、パラメーターa
が可変個引数であると記載されています。
fmt.Println
関数を使用して、0、1、またはそれ以上の値を渡すプログラムを作成しましょう。
print.go
package main import "fmt" func main() { fmt.Println() fmt.Println("one") fmt.Println("one", "two") fmt.Println("one", "two", "three") }
fmt.Println
を初めて呼び出すときは、引数を渡しません。 2回目にfmt.Println
を呼び出すときは、one
の値を持つ単一の引数のみを渡します。 次に、one
とtwo
を渡し、最後にone
、two
、three
を渡します。
次のコマンドでプログラムを実行してみましょう。
go run print.go
次の出力が表示されます。
Output one one two one two three
出力の最初の行は空白です。 これは、fmt.Println
が最初に呼び出されたときに引数を渡さなかったためです。 one
の値が2回目に出力されました。 次にone
とtwo
、最後にone
、two
、three
です。
可変個引数関数を呼び出す方法を見てきたので、独自の可変個引数関数を定義する方法を見てみましょう。
可変個引数関数の定義
引数の前に省略記号(...
)を使用すると、可変個引数関数を定義できます。 名前が関数に送信されたときに人々に挨拶するプログラムを作成しましょう。
hello.go
package main import "fmt" func main() { sayHello() sayHello("Sammy") sayHello("Sammy", "Jessica", "Drew", "Jamie") } func sayHello(names ...string) { for _, n := range names { fmt.Printf("Hello %s\n", n) } }
names
という単一のパラメーターのみを受け取るsayHello
関数を作成しました。 データ型...string
の前に省略記号(...
)を付けるため、パラメーターは可変個引数です。 これは、関数が0、1、または多くの引数を受け入れることができることをGoに通知します。
sayHello
関数は、names
パラメーターをスライスとして受け取ります。 データ型はstringであるため、names
パラメーターは、関数本体内の文字列のスライス([]string
)のように扱うことができます。 range 演算子を使用してループを作成し、文字列のスライスを反復処理できます。
プログラムを実行すると、次の出力が得られます。
OutputHello Sammy Hello Sammy Hello Jessica Hello Drew Hello Jamie
sayHello
を初めて呼び出したときに何も印刷されなかったことに注意してください。 これは、可変個引数パラメーターがstring
の空のslice
であったためです。 スライスをループしているため、反復処理するものはなく、fmt.Printf
が呼び出されることはありません。
値が送信されなかったことを検出するようにプログラムを変更してみましょう。
hello.go
package main import "fmt" func main() { sayHello() sayHello("Sammy") sayHello("Sammy", "Jessica", "Drew", "Jamie") } func sayHello(names ...string) { if len(names) == 0 { fmt.Println("nobody to greet") return } for _, n := range names { fmt.Printf("Hello %s\n", n) } }
ここで、 ifステートメントを使用して、値が渡されない場合、names
の長さは0
になり、nobody to greet
を出力します。
Outputnobody to greet Hello Sammy Hello Sammy Hello Jessica Hello Drew Hello Jamie
可変個引数パラメーターを使用すると、コードが読みやすくなります。 指定された区切り文字で単語を結合する関数を作成しましょう。 最初に可変個引数関数を使用せずにこのプログラムを作成して、次のように読み取る方法を示します。
join.go
package main import "fmt" func main() { var line string line = join(",", []string{"Sammy", "Jessica", "Drew", "Jamie"}) fmt.Println(line) line = join(",", []string{"Sammy", "Jessica"}) fmt.Println(line) line = join(",", []string{"Sammy"}) fmt.Println(line) } func join(del string, values []string) string { var line string for i, v := range values { line = line + v if i != len(values)-1 { line = line + del } } return line }
このプログラムでは、join
関数の区切り文字としてコンマ(,
)を渡します。 次に、結合する値のスライスを渡します。 出力は次のとおりです。
OutputSammy,Jessica,Drew,Jamie Sammy,Jessica Sammy
この関数はvalues
パラメーターとして文字列のスライスを受け取るため、join
関数を呼び出すときに、すべての単語をスライスでラップする必要がありました。 これにより、コードが読みにくくなる可能性があります。
ここで、同じ関数を記述しましょう。ただし、可変個引数関数を使用します。
join.go
package main import "fmt" func main() { var line string line = join(",", "Sammy", "Jessica", "Drew", "Jamie") fmt.Println(line) line = join(",", "Sammy", "Jessica") fmt.Println(line) line = join(",", "Sammy") fmt.Println(line) } func join(del string, values ...string) string { var line string for i, v := range values { line = line + v if i != len(values)-1 { line = line + del } } return line }
プログラムを実行すると、前のプログラムと同じ出力が得られることがわかります。
OutputSammy,Jessica,Drew,Jamie Sammy,Jessica Sammy
join
関数の両方のバージョンはプログラムでまったく同じことを行いますが、関数の可変個引数バージョンは、呼び出されたときにはるかに読みやすくなります。
可変引数引数の順序
関数には可変個引数パラメーターを1つだけ含めることができ、それは関数で定義された最後のパラメーターである必要があります。 可変個引数関数で最後のパラメーター以外の順序でパラメーターを定義すると、コンパイルエラーが発生します。
join.go
package main import "fmt" func main() { var line string line = join(",", "Sammy", "Jessica", "Drew", "Jamie") fmt.Println(line) line = join(",", "Sammy", "Jessica") fmt.Println(line) line = join(",", "Sammy") fmt.Println(line) } func join(values ...string, del string) string { var line string for i, v := range values { line = line + v if i != len(values)-1 { line = line + del } } return line }
今回は、join
関数の最初にvalues
パラメーターを配置します。 これにより、次のコンパイルエラーが発生します。
Output./join_error.go:18:11: syntax error: cannot use ... with non-final parameter values
可変個引数関数を定義する場合、最後のパラメーターのみを可変個引数にすることができます。
爆発的な議論
これまで、可変個引数関数に0、1、またはそれ以上の値を渡すことができることを見てきました。 ただし、値のスライスがあり、それらを可変個引数関数に送信したい場合があります。
前のセクションのjoin
関数を見て、何が起こるかを見てみましょう。
join.go
package main import "fmt" func main() { var line string names := []string{"Sammy", "Jessica", "Drew", "Jamie"} line = join(",", names) fmt.Println(line) } func join(del string, values ...string) string { var line string for i, v := range values { line = line + v if i != len(values)-1 { line = line + del } } return line }
このプログラムを実行すると、コンパイルエラーが発生します。
Output./join-error.go:10:14: cannot use names (type []string) as type string in argument to join
可変個引数関数はvalues ...string
のパラメーターを文字列のスライス[]string
に変換しますが、引数として文字列のスライスを渡すことはできません。 これは、コンパイラが文字列の個別の引数を予期しているためです。
これを回避するには、スライスに楕円のセット(...
)を付加し、可変個引数関数に渡される個別の引数に変換することで、スライスを展開できます。
join.go
package main import "fmt" func main() { var line string names := []string{"Sammy", "Jessica", "Drew", "Jamie"} line = join(",", names...) fmt.Println(line) } func join(del string, values ...string) string { var line string for i, v := range values { line = line + v if i != len(values)-1 { line = line + del } } return line }
今回、join
関数を呼び出すときに、省略記号(...
)を追加してnames
スライスを展開しました。
これにより、プログラムを期待どおりに実行できるようになります。
OutputSammy,Jessica,Drew,Jamie
分解するスライスだけでなく、0、1、またはそれ以上の引数を渡すことができることに注意することが重要です。 これまでに見たすべてのバリエーションを渡すコードは次のとおりです。
join.go
package main import "fmt" func main() { var line string line = join(",", []string{"Sammy", "Jessica", "Drew", "Jamie"}...) fmt.Println(line) line = join(",", "Sammy", "Jessica", "Drew", "Jamie") fmt.Println(line) line = join(",", "Sammy", "Jessica") fmt.Println(line) line = join(",", "Sammy") fmt.Println(line) } func join(del string, values ...string) string { var line string for i, v := range values { line = line + v if i != len(values)-1 { line = line + del } } return line }
OutputSammy,Jessica,Drew,Jamie Sammy,Jessica,Drew,Jamie Sammy,Jessica Sammy
これで、0、1、または多くの引数と、分解したスライスを可変個引数関数に渡す方法がわかりました。
結論
このチュートリアルでは、可変個引数関数によってコードがよりクリーンになる方法を見てきました。 常に使用する必要はありませんが、便利な場合があります。
- 関数に渡すためだけに一時的なスライスを作成していることがわかった場合。
- 入力パラメータの数が不明であるか、呼び出されたときに変化する場合。
- コードを読みやすくするため。
関数の作成と呼び出しの詳細については、Goで関数を定義して呼び出す方法を参照してください。