一. interface与struct的调用区别
struct特性:
说明struct,在调用方法的时候,值类型既可以调用值接收者的方法,也可以调用指针接收者的方法;指针类型既可以调用指针接收者的方法,也可以调用值接收者的方法
interface不行,必须严格按照特性,比如
例子一. 不管方法的接收者是什么类型,该类型的值和指针都可以调用,不必严格符合接收者的类型。
package main
import "fmt"
type Person struct {
age int
}
func (p Person) howOld() int {
return p.age
}
func (p *Person) growUp() {
p.age += 1
}
func main() {
// qcrao 是值类型
qcrao := Person{age: 18}
// 值类型 调用接收者也是值类型的方法
fmt.Println(qcrao.howOld())
// 值类型 调用接收者是指针类型的方法
qcrao.growUp()
fmt.Println(qcrao.howOld())
// ----------------------
// stefno 是指针类型
stefno := &Person{age: 100}
// 指针类型 调用接收者是值类型的方法
fmt.Println(stefno.howOld())
// 指针类型 调用接收者也是指针类型的方法
stefno.growUp()
fmt.Println(stefno.howOld())
}
输出结果:
18
19
100
101
- | 值接收者 | 指针接收者 |
---|---|---|
值类型调用者 | 方法会使用调用者的一个副本,类似于“传值” | 使用值的引用来调用方法,上例中,qcrao.growUp() 实际上是 (&qcrao).growUp() |
指针类型调用者 | 指针被解引用为值,上例中,stefno.howOld() 实际上是 (*stefno).howOld() | 实际上也是“传值”,方法里的操作会影响到调用者,类似于指针传参,拷贝了一份指针 |
例子二. 如果实现了接收者是值类型的方法,会隐含地也实现了接收者是指针类型的方法
当实现了一个接收者是指针类型的方法,如果此时自动生成一个接收者是值类型的方法,原本期望对接收者的改变(通过指针实现),现在无法实现,因为值类型会产生一个拷贝,不会真正影响调用者
通俗的话理解:
在下面的例子中func (p *Gopher) debug()
这种实现有了之后,不会自动生成func (p Gopher) debug()
,但是如果实现了func (p Gopher) code()
,会隐式实现func (p *Gopher) code()
, 因为自动生成的话会影响调用者。
package main
import "fmt"
type coder interface {
code()
debug()
}
type Gopher struct {
language string
}
func (p Gopher) code() {
fmt.Printf("I am coding %s language\n", p.language)
}
func (p *Gopher) debug() {
fmt.Printf("I am debuging %s language pointer\n", p.language)
}
func main() {
var c coder = &Gopher{"Go"}
c.code()
c.debug()
var gopher Gopher = Gopher{"Go"}
gopher.code()
/*下面两者等同*/
(&gopher).debug()
gopher.debug()
}
注意23行,如果不写&,那么会出现错误
二. iface 和 eface
1. 两者区别是什么
iface 和 eface 都是 Go 中描述接口的底层结构体,区别在于 iface 描述的接口包含方法,而 eface 则是不包含任何方法的空接口:interface{}
查看源码文件
/usr/local/Cellar/go@1.17/1.17.10/libexec/src/runtime/runtime2.go
type iface struct {
tab *itab
data unsafe.Pointer
}
type eface struct {
_type *_type
data unsafe.Pointer
}
其中iface中的itab是指向
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.
}
_type 字段描述了实体的类型,包括内存对齐方式,大小等;
inter 字段则描述了接口的类型。
fun 字段放置和接口方法对应的具体数据类型的方法地址,实现接口调用方法的动态分派,一般在每次给接口赋值发生转换时会更新此表,或者直接拿缓存的 itab。
2. 查看汇编代码
go tool compile -S test03.go
三. 接口判空以及打印
1. 接口值的零值是指动态类型和动态值都为 nil。当仅且当这两部分的值都为 nil 的情况下,这个接口值就才会被认为 接口值 == nil
package main
import "fmt"
type Coder interface {
code()
}
type Gopher struct {
name string
}
func (g Gopher) code() {
fmt.Printf("%s is coding\n", g.name)
}
func main() {
var c Coder
fmt.Println(c == nil)
fmt.Printf("c: %T, %v\n", c, c)
var g *Gopher
fmt.Println(g == nil)
fmt.Printf("g: %T, %v\n", g, g)
fmt.Println("====================")
/*接口值的零值是指动态类型和动态值都为 nil。当仅且当这两部分的值都为 nil 的情况下,这个接口值就才会被认为 接口值 == nil*/
c = g
fmt.Println(c == nil)
fmt.Printf("c: %T, %v\n", c, c)
}
输出结果
true
c: <nil>, <nil>
true
g: *main.Gopher, <nil>
====================
false
c: *main.Gopher, <nil>
2. 结构题隐式转换的判断
package main
import "fmt"
type MyError struct {}
func (i MyError) Error() string {
return "MyError"
}
func main() {
err := Process()
fmt.Println(err)
fmt.Println(err == nil)
}
func Process() error {
var err *MyError = nil
return err
}
打印结果
<nil>
false
3. 如何打印出接口的动态类型和值?
package main
import (
"fmt"
"unsafe"
)
type iface struct {
itab, data uintptr
}
func main() {
var a interface{} = nil
var b interface{} = (*int)(nil)
x := 5
var c interface{} = (*int)(&x)
ia := *(*iface)(unsafe.Pointer(&a))
ib := *(*iface)(unsafe.Pointer(&b))
ic := *(*iface)(unsafe.Pointer(&c))
fmt.Println(ia, ib, ic)
fmt.Println(*(*int)(unsafe.Pointer(ic.data)))
}
输出结果
{0 0} {17361600 0} {17361600 824634322600}
5
4. 内置方法结构体的隐式调用转换会失效!
package main
import "fmt"
type Student struct {
Name string
Age int
}
func (s *Student) String() string {
return fmt.Sprintf("[Name: %s], [Age: %d]", s.Name, s.Age)
}
func (p *Student) debug() {
fmt.Printf("I am debuging language pointer\n")
}
func main() {
var s Student = Student{
Name: "qcrao",
Age: 18,
}
s.debug()
fmt.Println(s)
}
这里我们看到,String()没有被调用到.对于内置类型,函数内部会用穷举法,得出它的真实类型,然后转换为字符串打印。而对于自定义类型,首先确定该类型是否实现了 String() 方法,如果实现了,则直接打印输出 String() 方法的结果;否则,会通过反射来遍历对象的成员进行打印。
输出结果
I am debuging language pointer
{qcrao 18}