Golang的可变参实现跟C/C++的不太一样,编译器把可变参解析成了一个切片结构传给了函数,充分利用了Go内置的数据结构。而C/C++的实现则要结合x86平台调用约定和ABI手册了,可以看:Linux C variadic可变参数:va_list 在x86和x64下的区别与实现原理。
一个Go的简单例子:
func sum2(vals []int) int {
total := 0
for _, v := range vals {
total += v
}
return total
}
func sum(vals ...int) (int, int) {
total := 0
fmt.Println(reflect.TypeOf(vals)) // []int
for _, val := range vals {
total += val
}
return total, sum2(vals)
}
func main() {
re, re2 := sum(1, 2, 3)
fmt.Println(re, re2) // 6 6
fmt.Println(reflect.TypeOf(sum)) // func(...int) (int, int)
fmt.Println(reflect.TypeOf(sum2)) // func([]int) int
}
通过上面例子可以看出,虽然实现上Go编译器把可变参解析成了一个切片,但是函数的type还是不一样的,TypeOf也看了出来。
知道这个机制,那么最常用的格式化打印函数就很容易猜到实现原理了,可变参传递通过一个空接口类型的切片即可,然后通过parse格式化的fmt字符串一个一个对应解析出切片内元素的类型,转换成字符串输出即可。
// @file: io.go
func Printf(format string, a ...interface{}) (n int, err error) {
return Fprintf(os.Stdout, format, a...) // 调用 p.doPrintf(format, a)
}
func (p *pp) doPrintf(format string, a []interface{})
上面的interface{}
某种程度上弥补了Go泛型能力的缺失,任何类型都实现了空接口。这也有点类似Java的Object类是所有类的祖先,也有点像C的void*可以接受任何类型的指针。