Go 学习笔记(二十)空接口

本文原创地址:博客园骏马金龙Go 基础系列:空接口

空接口

空接口是指没有定义任何接口方法的接口。 没有定义任何接口方法,意味着 Go 中的任意对象都可以实现空接口 (因为没方法需要实现),任意对象都可以保存到空接口实例变量中

空接口的定义方式:

type empty_int interface {
}

通常会简写为type empty_int interface{}

更常见的,会直接使用interface{}作为一种类型,表示空接口。例如:

// 声明一个空接口实例
var i interface{}

再比如函数使用空接口类型参数:

func myfunc(i interface{})

在 Go 中很多地方都使用空接口类型的参数,用的最多的fmt中的 Print 类方法:

$ go doc fmt Println
func Println(a ...interface{}) (n int, err error)

空接口数据结构

可以定义一个空接口类型的 array、slice、map、strcut 等,这样它们就可以用来存放任意类型的对象,因为任意类型都实现了空接口。

例如,创建一个空接口的 slice:

package main

import "fmt"

func main() {
	any := make([]interface{}, 5)
	any[0] = 11
	any[1] = "hello world"
	any[2] = []int{11, 22, 33, 44}
	for _, value := range any {
		fmt.Println(value)
	}
}

输出结果:

11
hello world
[11 22 33 44]
<nil>
<nil>

显然,通过空接口类型,Go 也能像其它动态语言一样,在数据结构中存储任意类型的数据。

再比如,某个 struct 中,如果有一个字段想存储任意类型的数据,就可以将这个字段的类型设置为空接口:

type my_struct struct {
	anything interface{}
	anythings []interface{}
}

拷贝数据结构到空接口数据结构

前面解释了任意类型的对象都能赋值给空接口实例。

var any interface{}
any = "hello world"
any = 11

空接口是一种接口,它是一种指针类型的数据类型,虽然不严谨,但它确实保存了两个指针,一个是对象的类型 (或 iTable),一个是对象的值。所以上面的赋值过程是让空接口 any 保存各个数据对象的类型和对象的值。

换一种角度考虑,空接口有自己的内存布局方式:两个指针,占用两个机器字长。

Golang 给的一个经典的示例:将某个 slice 中的数据拷贝到空接口 slice 中将报错。

package main

import "fmt"

func main() {
	testSlice := []int{11,22,33,44}

	// 成功拷贝
	var newSlice []int
	newSlice = testSlice
	fmt.Println(newSlice)

	// 拷贝失败
	var any []interface{}
	any = testSlice
	fmt.Println(any)
}

这是因为每个空接口的内存布局都占用两个机器字长的内容。对于长度为 N 的空接口 slice 来说,它的每个元素都是以 2 机器字长为单元的连续空间,共占用N*2个机器字长的空间。

而普通的 slice,例如上面的 testSlice,它的每个元素是 int 类型的,int 类型的内存布局和空接口不一样。

这些对象的内存布局在编译期间就已经确定好了,所以没法直接将不同内存布局的数据结构进行拷贝。

要想完成期待的拷贝,可以使用 for-range 的方式,将 testSlice 中的每个元素赋值给空接口 slice 的空接口元素:也就是一个个的空接口实例。

var any []interface{}
for _,value := range testSlice{
	any = append(any,value)
}

这样,空接口 Slice 中的每个空接口实例都指向更底层的各个数据对象。而不是像前面错误的拷贝方式:每个空接口元素想要当作这些数据对象。

不仅空接口的 Slice 如此,其它包含空接口的数据结构,也都类似。

上一篇 Go 学习笔记(十九)Go 接口
Go 学习笔记(目录)
下一篇 Go 学习笔记(二十一)接口类型断言和 type-switch