Go接口

接口 Demo

package main

import "fmt"

// Person 接口类型
type Person interface {
	getName() string // 不带参数、返回值为 string 的方法
} // 任何实现了方法 getName() 的类型,就说它实现了接口 Person

type student struct {
	Name string
	sex  string
}

var stu = student{"Runsen", "男"}

func (stu student) getName() string {
	return stu.Name
} // student 类型实现了 Person 接口

type teacher struct {
	Name string
	sex  string
}

var tea = teacher{"Runsen", "男"}

func (tea teacher) getName() string {
	return tea.Name
} // teacher 类型实现了 Person 接口

func main() {
	fmt.Println(stu.getName())
	fmt.Println(tea.getName())
}

接口内部实现源码

// https://github.com/golang/go/blob/master/src/runtime/runtime2.go#L203
// iface 结构体表示非空接口:
type iface struct {
	tab  *itab
	data unsafe.Pointer
}

// https://github.com/golang/go/blob/master/src/runtime/runtime2.go#L837:1
// 非空接口的类型信息
type itab struct {
	inter *interfacetype // 接口定义的类型信息
	_type *_type // 接口实际指向值的类型信息
	hash  uint32 // copy of _type.hash. Used for type switches.
	_     [4]byte
	fun   [1]uintptr // variable sized. fun[0]==0 means _type does not implement inter.接口方法实现列表,即函数地址列表,按字典序排序
}

// https://github.com/golang/go/blob/master/src/runtime/type.go#L359
// 接口的方法声明 
type imethod struct {
	name nameOff // 方法名
	ityp typeOff  // 描述方法参数返回值等细节
}

// 非空接口类型,接口定义,包路径等。
type interfacetype struct {
	typ     _type
	pkgpath name
	mhdr    []imethod // 接口方法声明列表,按字典序排序
}

非空接口(iface)本身除了可以容纳满足其接口的对象之外,还需要保存其接口的方法,因此除了data字段,iface通过tab字段描述非空接口的细节,包括接口方法定义,接口方法实现地址,接口所指类型等。

iface是非空接口的实现,而不是类型定义,iface的真正类型为interfacetype,其第一个字段仍然为描述其自身类型的_type字段。

从内部实现来看,接口本身也是一种结构类型,只是编译器会对其做很多的限制。

  • 不能有自己的字段
  • 不能定义自己的方法
  • 只能声明方法,不能实现
  • 可嵌入其他接口类型

指针方法值方法

接口实现的两种方法,上面的 Demo 使用的是 值接收者(Value Receiver) 来实现接口。
下面使用 指针接收者(Pointer Receiver) 来实现接口。

首先了解下 Go 中的指针:

指针是存储变量内存地址的变量,表达了这个变量在内存存储的位置。就像我们常说:指针指向了变量。

Go 语言的取地址符是 &,放到一个变量前使用就会返回相应变量的内存地址。

package main

import "fmt"

func main() {
   var a int = 1
   fmt.Printf("变量的地址: %x\n", &a) // 变量的地址: c0000140b8
}

在 Go 中指针的操作

package main

import "fmt"

func main() {
   var a int= 1  /* 声明实际变量 */
   var ip *int        /* 声明指针变量 */

   ip = &a  /* 指针变量的存储地址 */

   fmt.Printf("a 变量的地址是: %x\n", &a  ) //a 变量的地址是: c0000140b8

   /* 指针变量的存储地址 */
   fmt.Printf("ip 变量储存的指针地址: %x\n", ip ) //ip 变量储存的指针地址: c0000140b8

   /* 使用指针访问值 */
   fmt.Printf("*ip 变量的值: %d\n", *ip ) //*ip 变量的值: 1
}

在函数接收者,可以使用指针方法和值方法。

区别在于:

用指针类型作为接收者,可以在函数/方法内部修改这个接收者的数据,
而用值类型作为接收者,在函数/方法内部不能修改原接收者的数据。

Demo

package main
import "fmt"

type user struct {
  name string
  age  int
}
// 值的
func (u user) PrintName() {
  fmt.Println(u.name)
}
// 指针的
func (u *user) PrintAge() {
  fmt.Println(u.age)
}
func main() {     
  var Runsen *user // 润森是指针类型的
  Runsen = &user{"Runsen", 20}  // 正常输出无误
  Runsen.PrintName()  // zhangsan
  Runsen.PrintAge() // 10

  var maoli user // 毛利是值类型
  maoli = user{"Maoli", 20} // 正常输出无误
  maoli.PrintName()  // lisi
  maoli.PrintAge() // 20

// 以值的方式调用 一个定义在指针类型下的方法时,会隐式的转换值到指针,例中 maoli.PrintAge() 就是这样
// 以指针的方式调用 一个定义在值类型下的方法时,会隐式的转换指针到值,例中 Runsen.PrintName() 就是这样
// 相同:无论定义在哪种类型下,都可以访问到。
// 区别:只有定义在指针类型下的方法可以修改原变量的内容
}

指针类型的接收者之所以需要指针,就是因为它要改变原对象的值。

在上面的例子中,调用 user 的 SetName 方法时,编译器会帮你把 user 的指针传递给 SetName 方法。


学习参考:

posted @ 2020-09-28 16:28  CusterFun  阅读(150)  评论(0编辑  收藏  举报