断言(assertion)
wiki是这么说的
换言之,断言(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 表示其他类型,显然它们之间发生了转换,那这就设计到类型转换了,我们先讲解一下类型转换的知识