断言(assertion)

wiki是这么说的

程式设计中,断言assertion )是一种放在程式中的一阶逻辑(如一个结果为真或是假的逻辑判断式)

换言之,断言(assertion) 就是一个逻辑判断表达式。

接口 (interface)

接口是 go 中的一种数据类型,使用 interface 关键字。接口是一种鸭子类型(duck typing),是一组方法的集合。

type Animal interface {
}

没有成员方法,故是一个空接口

type Animal interface {
	Speak(msg string)
}

Animal 接口有一个 Speak() 成员方法,故是一个有方法的接口。

实现了接口的所有方法就是实现了这个接口。

如我们这里有一个 Animal 接口

type Animal interface {
	Speak(msg string)
}

然后有两个 struct

type cat struct {
}
type dog struct {
}

然后实现 Animal 接口,也就是给这两个 struct 分别添加 Animal 接口的方法

func (c cat) Speak(msg string) {
	fmt.Println("cat say:" + msg)
}

func (c dog) Speak(msg string) {
	fmt.Println("dog say:" + msg)
}

注意,要实现某个接口必须实现接口的所有方法,包括方法个数,函数签名(signature functions),同时也可以是其他 type ,如 int ,string 等,不一定是 struct

实现了接口就可以使用了,这里给个例子

type Animal interface {
	Speak(msg string)
}

type cat struct {
}
type dog struct {
}

func (c cat) Speak(msg string) {
	fmt.Println("cat say:" + msg)
}

func (c dog) Speak(msg string) {
	fmt.Println("dog say:" + msg)
}

func main() {
    // 声明一个 animal 变量
	var animal Animal
	// 把 cat{} 赋值给 animal,因为 cat struct 实现了 animal interface
	animal = cat{}
	animal.Speak("Hello,i'm cat")
	
	animal = dog{}
	animal.Speak("Hello,i'm dog")
}

我们可以看见,只要实现了某个接口,那么这个接口就可以转换为所有实现了这个接口的 type

空接口没有任何成员方法,所以所有类型都实现了空接口,也就是说,空接口可以转换为所有类型,所有类型也可以转换为空接口

为了实现这一点,我们先引入一下反射 (reflection)的概念

反射(reflection)

wiki

计算机学中,反射(英语: reflection )是指计算机程序运行时( runtime)可以访问、检测和修改它本身状态或行为的一种能力。[1] 用比喻来说,反射就是程序在运行的时候能够 “观察” 并且修改自己的行为

换句话说,我们可以在操作某个对象时,运用反射来查看其具体类型,如看某个变量是不是 string 类型,struct 有哪些 filed 等

首先我们要知道所有变量都是于 Type 和 Value 组成的,如名字, Type 是变量类型,Value 是具体的值

var a int

这里我们用 var 关键字声明了一个 int 变量,那么其 Type 就是 int, Value 就是 nil

然后我们对其赋值

a = 1

Value 就变成 1 了

当然我们可以使用 := 语法糖声明变量的同时赋值

a := 1

这里会根据赋值的内容自动判断 Type, Type 就是 int, Value 就是 1

自然的,reflect 包就给我们提供了两个函数用来判断它们的值

我们来看源码

type Type interface {
//
}
type Value struct {
//
}

我们可以看到 reflect 包分别提供了一个 interface 类型和 struct 类型表示 Type 和 Value

那么同样的,有提供表示类型的,也有提供获取类型的方法,我们继续看

func TypeOf(i interface{}) Type {
//
}

func ValueOf(i interface{}) Value {
//
}

可以看到它们都可以接受一个空接口参数,然后返回对应的类型,至于为什么是空接口,前面我们已经说过了,所有类型都实现了空接口,所以可以用空接口接受任何类型的变量。

为什么实现接口就可以用接口表示其他类型呢?

我们先来看一下空接口

var a interface{}
fmt.Println(reflect.TypeOf(a))
fmt.Println(reflect.ValueOf(a))

结果

<nil>
<invalid reflect.Value>

我们先不深入探讨 interface 的底层原理,从这里我们可以看出来 interface 也是 Type-Value 类型存储的。至于那两个是什么玩意且不管

然后我们再看一下非空接口

先实现接口

type Animal interface {
	Speak(msg string)
}

type dog struct {
	age int
}
func (d dog) Speak(msg string) {
	fmt.Println("cat dog:" + msg)
}

然后我们实例一个 Animal, 把 dog 赋值给它,看一下它的 Key 和 Value 和 dog 有什么区别

func main() {
	var animal Animal
	d := dog{20}
	fmt.Println(reflect.TypeOf(d))
	fmt.Println(reflect.ValueOf(d))

	animal = d
	fmt.Println(reflect.TypeOf(animal))
	fmt.Println(reflect.ValueOf(animal))
}

结果是

main.dog
{20}
main.dog
{20}

我们惊讶的发现它们的 Key 和 Value 都是相同的,如果不深入底层,我们难以理解 interface 和普通的类型有什么区别,它是如何存储普通类型的,我们把 dog 赋给了 animal, 看起来它们的 Key 和 Value 是一样的,如果 animal 变成了 dog 类型的话,那么 animal 应该可以调用 dog 的 age filed 的,我们来试一下

fmt.Println(animal.age)

但是我们得到了一个 error animal.Age undefined (type Animal has no field or method Age)

看其他虽然它们 key 和 value 一样,但接口并没有真正变为其他类型,也是,如果接口变成了其他类型,那么这个接口就不能赋给其他实现了的类型了,因为那样的话就变成不同类型直接相互转换了,在 go 这种静态语言中显然是不可能的,从这里我们也可以下出一个结论,空接口并不是任何类型。如果这点没有理解,我们来看代码

type Animal interface {
	Speak(msg string)
}

type dog struct {
	age int
}

type cat struct {
	age int
}
// cat 和 dog 都实现了了 Animal 接口
func (c cat) Speak(msg string) {
	fmt.Println("cat say:" + msg)
}

func (d dog) Speak(msg string) {
	fmt.Println("cat dog:" + msg)
}

func main() {
	var animal Animal
	c := cat{}
	d := dog{}
   // c 赋给 animal
	animal = c
	// d 又赋给 animal
	// 这是不会报错的,因为 c 和 d 都实现了 animal 
	// 但是如果 interface 是任意类型,那么到这一步
	// animal 就变成 cat 类型了,而 cat 类型是无法转换成 dog 类型的
	// 可以直接 试试 c = d ,是会报 error: cannot use d (type dog) as type cat in assignment 的
	animal = d
}

我们接着解释为什么接口可以表示为其他类型

刚刚我们表明了 interface 不是任意类型,而又可以用 interface 表示其他类型,显然它们之间发生了转换,那这就设计到类型转换了,我们先讲解一下类型转换的知识