断言(assertion)

wiki是这么说的

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

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

接口 (interface)

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

  1. type Animal interface {
  2. }

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

  1. type Animal interface {
  2. Speak(msg string)
  3. }

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

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

如我们这里有一个 Animal 接口

  1. type Animal interface {
  2. Speak(msg string)
  3. }

然后有两个 struct

  1. type cat struct {
  2. }
  3. type dog struct {
  4. }

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

  1. func (c cat) Speak(msg string) {
  2. fmt.Println("cat say:" + msg)
  3. }
  4. func (c dog) Speak(msg string) {
  5. fmt.Println("dog say:" + msg)
  6. }

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

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

  1. type Animal interface {
  2. Speak(msg string)
  3. }
  4. type cat struct {
  5. }
  6. type dog struct {
  7. }
  8. func (c cat) Speak(msg string) {
  9. fmt.Println("cat say:" + msg)
  10. }
  11. func (c dog) Speak(msg string) {
  12. fmt.Println("dog say:" + msg)
  13. }
  14. func main() {
  15. // 声明一个 animal 变量
  16. var animal Animal
  17. // 把 cat{} 赋值给 animal,因为 cat struct 实现了 animal interface
  18. animal = cat{}
  19. animal.Speak("Hello,i'm cat")
  20. animal = dog{}
  21. animal.Speak("Hello,i'm dog")
  22. }

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

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

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

反射(reflection)

wiki

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

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

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

  1. var a int

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

然后我们对其赋值

  1. a = 1

Value 就变成 1 了

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

  1. a := 1

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

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

我们来看源码

  1. type Type interface {
  2. //
  3. }
  4. type Value struct {
  5. //
  6. }

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

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

  1. func TypeOf(i interface{}) Type {
  2. //
  3. }
  4. func ValueOf(i interface{}) Value {
  5. //
  6. }

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

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

我们先来看一下空接口

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

结果

  1. <nil>
  2. <invalid reflect.Value>

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

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

先实现接口

  1. type Animal interface {
  2. Speak(msg string)
  3. }
  4. type dog struct {
  5. age int
  6. }
  7. func (d dog) Speak(msg string) {
  8. fmt.Println("cat dog:" + msg)
  9. }

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

  1. func main() {
  2. var animal Animal
  3. d := dog{20}
  4. fmt.Println(reflect.TypeOf(d))
  5. fmt.Println(reflect.ValueOf(d))
  6. animal = d
  7. fmt.Println(reflect.TypeOf(animal))
  8. fmt.Println(reflect.ValueOf(animal))
  9. }

结果是

  1. main.dog
  2. {20}
  3. main.dog
  4. {20}

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

  1. fmt.Println(animal.age)

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

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

  1. type Animal interface {
  2. Speak(msg string)
  3. }
  4. type dog struct {
  5. age int
  6. }
  7. type cat struct {
  8. age int
  9. }
  10. // cat 和 dog 都实现了了 Animal 接口
  11. func (c cat) Speak(msg string) {
  12. fmt.Println("cat say:" + msg)
  13. }
  14. func (d dog) Speak(msg string) {
  15. fmt.Println("cat dog:" + msg)
  16. }
  17. func main() {
  18. var animal Animal
  19. c := cat{}
  20. d := dog{}
  21. // c 赋给 animal
  22. animal = c
  23. // d 又赋给 animal
  24. // 这是不会报错的,因为 c 和 d 都实现了 animal
  25. // 但是如果 interface 是任意类型,那么到这一步
  26. // animal 就变成 cat 类型了,而 cat 类型是无法转换成 dog 类型的
  27. // 可以直接 试试 c = d ,是会报 error: cannot use d (type dog) as type cat in assignment 的
  28. animal = d
  29. }

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

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