19《Go语言入门》interface和interface{}、interface和pointer

这是我纯手写的《Go语言入门》,手把手教你入门Go。源码+文章,看了你就会,此处应有掌声!
文章中所有的代码我都放到了github.com/GanZhiXiong/go_learning这个仓库中。在看文章的时候,对照仓库中代码学习效果更佳!

目录

  • interface{}
    • interface{} 类型不是任意类型
    • []interface{}是元素是interface{}的slice
  • interface和pointer
  • 支持

interface{}

上一章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{}可以向函数传递任意类型的变量,但对于函数内部,该变量仍为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{}的slice

[]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)
}

interface和pointer

在Go中我们经常会看到某个类型实现接口的下面两种写法:
19《Go语言入门》interface和interface{}、interface和pointer_第1张图片

  • 足以证明实现接口时的接受者类型可以是结构体或结构体指针
  • 但是这两种写法 是不能同时存在的,同时存在则会报错: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() 。

支持

  • 我会持续编写【软件开发相关】的文章,保持每周至少一篇文章。
  • 如果你也是【软件工程师】,【关注❤️我】,一定会对你有所帮助。
  • 如果这篇文章对你有所帮助,那就麻烦,【点赞】
  • 您的支持将给与我更大的动力,谢谢。

你可能感兴趣的:(#,Go语言入门,go,golang,go语言)