面试题答案
一键面试Go接口内部数据结构支持方法调用原理
- 接口的动态类型确定
- 在Go语言中,接口类型的变量可以存储任何实现了该接口的类型的值。接口变量内部实际上包含两个部分:一个是类型(
type
),另一个是值(data
)。当给接口变量赋值时,会确定其动态类型。例如:
这里type Animal interface { Speak() string } type Dog struct{} func (d Dog) Speak() string { return "Woof" } var a Animal d := Dog{} a = d
a
是接口类型变量,当a = d
时,a
的动态类型就被确定为Dog
,a
内部存储的type
是Dog
的类型信息,data
就是d
的值的副本。 - 在Go语言中,接口类型的变量可以存储任何实现了该接口的类型的值。接口变量内部实际上包含两个部分:一个是类型(
- 方法集查找
- Go语言中每个类型都有对应的方法集。对于结构体类型,方法集与接收者类型密切相关。当通过接口调用方法时,会根据接口存储的动态类型在该类型的方法集中查找对应的方法。
- 例如,对于上述的
Dog
类型,其方法集就是Speak
方法。当通过接口a
调用Speak
方法时,会在Dog
类型的方法集中查找Speak
方法的实现。
值接收者和指针接收者场景下接口内部数据结构对方法调用的影响机制
- 值接收者
- 当方法使用值接收者时,例如:
type Rectangle struct { width, height int } func (r Rectangle) Area() int { return r.width * r.height } type Shape interface { Area() int } var s Shape rect := Rectangle{width: 5, height: 3} s = rect
- 此时接口变量
s
存储的data
是rect
的副本。当调用s.Area()
时,是在这个副本上调用Area
方法。这意味着在Area
方法内部对接收者的修改不会影响原始的rect
。例如,如果在Area
方法中尝试修改r.width
,不会改变rect.width
。
- 指针接收者
- 当方法使用指针接收者时,例如:
type Counter struct { value int } func (c *Counter) Increment() { c.value++ } type CounterInterface interface { Increment() } var ci CounterInterface counter := &Counter{value: 0} ci = counter
-
这里接口变量
ci
存储的data
是counter
指针。当调用ci.Increment()
时,是通过这个指针找到实际的Counter
实例并调用Increment
方法。这使得在Increment
方法内部对c.value
的修改会反映到原始的counter
实例上。如果没有使用指针接收者,在Increment
方法中对c.value
的修改将只作用于副本,无法更新原始实例。 -
总结来说,值接收者适用于方法不需要修改接收者状态的情况,而指针接收者适用于需要修改接收者状态的情况。在接口调用中,这种区别通过接口内部存储的值或指针来实现对方法调用行为的影响。