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 方法。
学习参考: