这是我纯手写的《Go语言入门》,手把手教你入门Go。源码+文章,看了你就会,此处应有掌声!
文章中所有的代码我都放到了github.com/GanZhiXiong/go_learning这个仓库中。在看文章的时候,对照仓库中代码学习效果更佳!
上一章18《Go语言入门》非侵入式接口(interface)我们讲了接口,其实接口分为空接口和非空接口,上一章讲的是非空接口。
接口是一组方法签名(声明了一组方法的集合)。
空接口就是不带任何方法的interface{}。
interface{}类似Java中的Object类型。
任何变量都可以赋值给interface{}类型的变量。
示例:
func TestEmptyInterface(t *testing.T) {
var a interface{} = 1
t.Log(a, reflect.TypeOf(a))
a = 1.34
t.Log(a, reflect.TypeOf(a))
a = "hello"
t.Log(a, reflect.TypeOf(a))
a = [2]int{1, 2}
t.Log(a, reflect.TypeOf(a))
}
=== RUN TestEmptyInterface
19_empty_interface_test.go:28: 1 int
19_empty_interface_test.go:31: 1.34 float64
19_empty_interface_test.go:34: hello string
19_empty_interface_test.go:37: [1 2] [2]int
--- PASS: TestEmptyInterface (0.00s)
PASS
interface{}可以向函数传递任意类型的变量,但对于函数内部,该变量仍为interface{}类型。
例如:
func echoArray(a interface{}) {
fmt.Printf("%T\n", a)
println(reflect.TypeOf(a))
// 下面代码a会报错:Cannot range over 'a' (type interface{})
// interface{}可以向函数传递任意类型的变量,但对于函数内部,该变量仍为interface{}类型,而不是[]int类型。
//for _, v := range a{
// fmt.Print(v, " ")
//}
}
func TestEmptyInterface1(t *testing.T) {
a := []int{1, 2, 3, 4}
t.Log(reflect.TypeOf(a))
echoArray(a)
}
那么如何解决呢?
使用断言(断言我会在后面章节细讲)对类型转换下即可:
// 使用断言实现类型转换,将interface{}转换为slice类型。
b, ok := a.([]int)
if ok {
for _, i := range b {
fmt.Println(i)
}
}
[]interface{}不是interface{}
func TestEmptyInterfaceSlice(t *testing.T) {
var dataSlice = []int{1, 2}
t.Log(dataSlice)
/*
错误的写法
*/
// Cannot use 'dataSlice' (type []int) as type []interface{}
//var interfaceSlice []interface{} = dataSlice
//t.Log(interfaceSlice)
/*
正确的写法
*/
var interfaceSlice = make([]interface{}, len(dataSlice))
for i, value := range dataSlice {
interfaceSlice[i] = value
}
t.Log(interfaceSlice)
}
Method redeclared '*Cat.Say'
:type Animal interface {
Say()
}
type Cat struct {
}
// Method redeclared '*Cat.Say'
//func (c Cat) Say() {
// fmt.Println("meow")
//}
func (c *Cat) Say() {
fmt.Println("meow")
}
type Dog struct {
}
func (d Dog) Say() {
fmt.Println("wang wang")
}
先看示例:
func TestInterfacePointer(t *testing.T) {
/*
cat
*/
c := Cat{}
c.Say()
c1 := &Cat{}
c1.Say()
// Cannot use 'Cat{}' (type Cat) as type Animal Type
// does not implement 'Animal' as 'Say' method has a pointer receiver
//var c2 Animal = Cat{}
//c2.Say()
var c3 Animal = &Cat{}
c3.Say()
/*
dog
*/
d := Dog{}
d.Say()
d1 := &Dog{}
d1.Say()
var d2 Animal = Dog{}
d2.Say()
var d3 Animal = &Dog{}
d3.Say()
}
=== RUN TestInterfacePointer
meow
meow
meow
wang wang
wang wang
wang wang
wang wang
--- PASS: TestInterfacePointer (0.00s)
PASS
通过结果,我们可以得出结论:
结构体实现接口 | 结构体指针实现接口 | |
---|---|---|
结构体初始化变量 | 通过 | 不通过 |
结构体指针初始化变量 | 通过 | 通过 |
问:那么为什么当指针实现接口时,结构体初始化变量会编译不通过呢?
答:因为所有的参数都是通过值传递的,每次调用函数时,传入的数据都会被复制。
这就可以解释为什么 *Cat 的方法不能被 Cat 类型的值调用了。
任何一个 Cat 类型的值可能会有很多 *Cat 类型的指针指向它,如果我们尝试通过 Cat 类型的值来调用 *Cat 的方法,根本就不知道对应的是哪个指针。
相反,如果 Dog 类型上有一个方法,通过 *Dog来调用这个方法可以确切的找到该指针对应的 Dog 类型的值,从而调用上面的方法。
运行时,Go 会自动帮我们做这些,所以我们不需要像 C语言中那样使用类似如下的语句 d->Speak() 。