1. 变长参数和变长参数函数类型
- 一个函数的最后一个参数可以是一个变长参数;
- 一个函数可以最多有一个变长参数;
- 一个变长参数的类型总为一个切片类型。
变长参数在声明的时候必须在它的(切片)类型的元素类型前面前置三个点 ...,以示这是一个变长参数。 两个变长函数类型的例子:
func (values ...int64) (sum int64)
func (sep string, tokens ...string) string
2. 变长参数函数的声明
变长函数声明和普通函数声明类似,只不过最后一个参数必须为变长参数。一个变长参数在函数体内将被视为一个切片。
func Sum(values ...int64) (sum int64) {
// values的类型为[]int64
sum = 0
for _, v := range values {
sum += v
}
return
}
func Concat(sep string, tokens ...string) (r string) {
// tokens的类型为[]string
for i, t := range tokens {
if i != 0 {
r += sep
}
r += t
}
return r
}
从上面的两个变长参数函数声明可以看出,如果一个变长参数的类型部分为 ...T,则此变长参数的类型实际为 []T。
事实上,fmt 标准库包中的 Print、Println 和 Printf 函数均为变长参数函数。它们的声明大致如下:
func Print(a ...interface{}) (n int, err error)
func Printf(format string, a ...interface{}) (n int, err error)
func Println(a ...interface{}) (n int, err error)
3. 变长参数函数调用
在变长参数函数调用中,可以使用两种风格的方式将实参传递给类型为 []T 的变长形参:
1.传递一个切片做为实参。此切片必须可以被赋值给类型为 []T 的值(或者说此切片可以被隐式转换为类型 []T)。此实参切片后必须跟随三个点 ...。
2.传递零个或者多个可以被隐式转换为 T 的实参(或者说这些实参可以赋值给类型为 T 的值)。 这些实参将被添加入一个匿名的在运行时刻创建的类型为 []T 的切片中,然后此切片将被传递给此函数调用。
注意,这两种风格的方式不可在同一个变长参数函数调用中混用。
下面这个例子展示了一些变长参数函数调用:
package main
import "fmt"
func Sum(values ...int64) (sum int64) {
sum = 0
for _, v := range values {
sum += v
}
return
}
func main() {
a0 := Sum()
a1 := Sum(2)
a3 := Sum(2, 3, 5)
// 上面三行和下面三行是等价的。
b0 := Sum([]int64{}...)
b1 := Sum([]int64{2}...)
b3 := Sum([]int64{2, 3, 5}...)
fmt.Println(a0, a1, a3) // 0 2 10
fmt.Println(b0, b1, b3) // 0 2 10
}
4. 模拟实现函数的可选参数与默认参数
来看下 Gin 中的 Run 方法使用:
func (engine *Engine) Run(addr ...string) (err error) {
defer func() { debugPrintError(err) }()
address := resolveAddress(addr)
debugPrint("Listening and serving HTTP on %s\n", address)
err = http.ListenAndServe(address, engine)
return
}
func resolveAddress(addr []string) string {
switch len(addr) {
case 0:
if port := os.Getenv("PORT"); port != "" {
debugPrint("Environment variable PORT="%s"", port)
return ":" + port
}
debugPrint("Environment variable PORT is undefined. Using port :8080 by default")
return ":8080"
case 1:
return addr[0]
default:
panic("too many parameters")
}
}
5. 实现功能选项模式(functional option)
功能选项模式使得我们在设计和实现带有配置选项的函数或方法时,更灵活优雅:
package main
import "fmt"
type CoffeeOptions struct {
sugar int
milk int
coffeePowder int
}
// 这个函数会接受上面结构体 CoffeeOptions 实例的指针 这个函数最终的目的都是为了修改实例
type CoffeeOption func(*CoffeeOptions)
// 下面这三个函数都会返回 CoffeeOption 函数 方便修改 CoffeeOptions 结构体
func CoffeeSugar(sugar int) CoffeeOption {
return func(opts *CoffeeOptions) {
opts.sugar = sugar
}
}
func CoffeeMilk(milk int) CoffeeOption {
return func(opts *CoffeeOptions) {
opts.milk = milk
}
}
func CoffeePowder(coffeePowder int) CoffeeOption {
return func(opts *CoffeeOptions) {
opts.coffeePowder = coffeePowder
}
}
func newDefaultCoffeeOptions() *CoffeeOptions {
return &CoffeeOptions{
sugar: 5,
milk: 0,
coffeePowder: 60,
}
}
func NewCoffee(opts ...CoffeeOption) *CoffeeOptions {
// 新建实例opt
defaultOptions := newDefaultCoffeeOptions()
// 根据所传的参数修改opt实例
for _, o := range opts {
o(defaultOptions)
}
return defaultOptions
}
func main() {
// 创建函数来修改实例
opts := NewCoffee(
CoffeeSugar(10),
CoffeeMilk(10),
CoffeePowder(80),
)
fmt.Printf("%#v", *opts)
// main.CoffeeOptions{sugar:10, milk:10, coffeePowder:80}
}
在 go-micro](github.com/micro/go-mi…) 中你可以看到很多 functional option 的应用,基本每个 micro 组件都使用了这种方式进行构造。