可变参数函数(Variadic Function)是指可以接受可变数量参数的函数。在Go中,通过在参数类型前添加...(三个点)来声明可变参数。可变参数在函数内部表现为切片,使得函数调用更加灵活,避免手动构造切片。
语法:func 函数名(固定参数..., 变参名 ...类型) 返回值。可变参数必须是函数的最后一个参数,内部类型是切片。
package main
import "fmt"
// 求和:接受任意个整数
func sum(nums ...int) int {
total := 0
for _, n := range nums {
total += n
}
return total
}
func main() {
fmt.Println(sum()) // 0个参数
fmt.Println(sum(1)) // 1
fmt.Println(sum(1, 2, 3)) // 6
fmt.Println(sum(10, 20, 30, 40)) // 100
}
内部nums是一个[]int切片,可以像普通切片一样遍历、取长度、甚至修改(但通常不应修改可变参数)。
可变参数函数可以接受从零个到任意多个实际参数。
func logMessages(msgs ...string) {
for _, msg := range msgs {
fmt.Println(msg)
}
}
logMessages() // 无输出
logMessages("start") // start
logMessages("info", "warning", "error")
可变参数必须放在所有普通参数之后,且只能有一个可变参数。
// 前缀 + 可变参数的内容
func printWithPrefix(prefix string, values ...interface{}) {
for _, v := range values {
fmt.Printf("%s: %v\n", prefix, v)
}
}
printWithPrefix("INFO", "message", 123, true)
interface{},允许传入任意类型。但调用方需要自行处理类型断言。如果已经有一个切片,想把它作为可变参数传入,需要在切片后面加上...操作符,表示将切片元素展开为独立参数。
nums := []int{1, 2, 3, 4}
result := sum(nums...) // 等价于 sum(1,2,3,4)
fmt.Println(result) // 10
// 注意:不能直接将切片传给可变参数而不解包,类型不匹配
// sum(nums) // 编译错误
可变参数是原始参数的副本(切片头的副本),但底层数组可能共享。修改切片内元素会影响原数组,但append操作不会影响外部调用者。
func modify(nums ...int) {
nums[0] = 999 // 会影响外部(如果外部传入的是数组元素解包,那要谨慎)
nums = append(nums, 100) // 不会影响外部切片长度
}
arr := []int{1,2,3}
modify(arr...)
fmt.Println(arr) // [999,2,3],因为底层数组被修改
使用空接口切片作为可变参数,可以实现类似fmt.Println的功能。
func printAll(items ...interface{}) {
for i, item := range items {
fmt.Printf("索引%d: %v\n", i, item)
}
}
printAll(1, "hello", 3.14, []int{4,5})
但注意,空接口可变参数会丢弃类型信息,使用时需要进行类型断言或反射。
// 切片参数
func sumSlice(nums []int) int { ... }
sumSlice([]int{1,2,3})
// 可变参数
func sumVariadic(nums ...int) int { ... }
sumVariadic(1,2,3)
调用时如果什么也不传,可变参数为nil;也可以显式传nil。但不建议依赖nil与非nil的差异。
func checkNil(args ...int) {
fmt.Printf("args is nil? %v\n", args == nil)
}
checkNil() // true
checkNil([]int{}...) // false,虽然展开空切片,但参数是非nil切片
调用可变参数函数时,Go会在内部创建切片并复制实参。对于性能敏感且调用频繁的场景,如果参数个数有上限,考虑提供多个固定参数的函数(如func Sum2(a,b int))以减少内存分配。
package main
import (
"fmt"
"time"
)
type LogLevel string
const (
INFO LogLevel = "INFO"
ERROR LogLevel = "ERROR"
DEBUG LogLevel = "DEBUG"
)
func log(level LogLevel, format string, args ...interface{}) {
timestamp := time.Now().Format("2006-01-02 15:04:05")
message := fmt.Sprintf(format, args...)
fmt.Printf("[%s] [%s] %s\n", timestamp, level, message)
}
func main() {
log(INFO, "应用启动,用户数: %d", 100)
log(ERROR, "数据库连接失败: %s", "timeout")
log(DEBUG, "调试值: %v", []int{1,2,3})
// 传递切片解包
params := []interface{}{"a", 42, true}
log(INFO, "多个参数: %v, %d, %t", params...)
}
...来解包切片。合理使用可变参数可以简化API设计。下一章将学习Go语言中重要的函数特性——延迟调用defer与错误处理。