思考一个问题, 当将一个对象赋值给一个interface时, 发生了什么, 对象变量与接口变量是引用的同一片内存地址吗?
关于 go interface的本质是什么, 网上有说法是一个拥有两个指针的struct, 一个指向运行时类型, 一个指向对象本身
我个人感觉这个说法太含糊了, 还是实际写代码实验吧
type IObject interface {
SetValue(value int)
GetValue() int
}
type Object struct {
Value int
}
func (obj Object) SetValue(value int) {
obj.Value = value
}
func (obj Object) GetValue() int {
return obj.Value
}
func main() {
var obj = Object{Value: 1}
var iObj IObject = obj // *对象赋值给接口*
obj.Value = 2 // 因为 receiver 是对象, 调用 obj.SetValue(2) 没有意义
fmt.Println(obj.GetValue()) // "2": 对象被修改了
fmt.Println(iObj.GetValue()) // "1": 接口变量并没有被修改
}
可以得出结论, 关键操作 *对象赋值给接口* 实际发生了值拷贝, 与Object对象变量间赋值的效果一样
并没有想象中像Java里一样, 接口是对象的引用
如果需要这种效果, 则需要将Object对象的指针赋值给接口
可以直接修改刚刚的代码, 将赋值语句改为指针赋值就行, 这时IObject变量被赋值的就行Object对象的指针
type IObject interface {
SetValue(value int)
GetValue() int
}
type Object struct {
Value int
}
func (obj Object) SetValue(value int) {
obj.Value = value
}
func (obj Object) GetValue() int {
return obj.Value
}
func main() {
var obj = Object{Value: 1}
var iObj IObject = &obj // *对象赋值给接口*, 现在赋的是指针, 之前赋的是对象
obj.Value = 2
fmt.Println(obj.GetValue()) // "2": 对象被修改了
fmt.Println(iObj.GetValue()) // "2": 接口变量也被修改了
iObj.SetValue(3) // 注: 这里调用SetValue是不会有任何效果的, 因为receiver是对象
fmt.Println(obj.GetValue()) // "2":
fmt.Println(iObj.GetValue()) // "2":
}
另外要注意: Go有个很奇怪的地方, 因为没有C++中的指针运算符, go语言中将指针运算符用取值运算符替代了, 也就是说, 不论变量是对象本身还是对象的指针都用取值运算符了, 这会带来哪些影响呢? 作为C++的程序员会感觉很困扰, 目前我总结了两个地方
1. 所有取成员的操作是笼统处理的, 这也是基本的, 不论变量是对象还是指针, 直接用取值操作符就能访问其成员, 成员包括: 字段和方法
2. 所有赋值和传参的地方需要敏感处理, 指针符指针, 对象符对象, 指针要赋值给对象就要取内容(与C++相同,用*运算符), 对象赋值给指针就要取地址(也与C++相同, 用&运算符), 函数传参也一样要遵循这个规则
那么仔细思考一下, receiver 算什么, 遵循哪一条?
经过试验, receiver 遵循的是第一条, 不算是函数传参, 上代码:
type IObject interface {
PrintValue()
}
type Object struct {
Value int
}
func (obj Object) PrintValue() { // 注: 这里的 receiver 是对象
fmt.Println(obj.Value)
}
func main() {
var obj = Object{Value: 5}
var iObj1 IObject = obj // Object 可以是 IObjedct
iObj1.PrintValue()
var iObj2 IObject = &obj // 神奇的地方: Object的指针 也可以是 IObject, 因为遵循的是第一条规则
iObj2.PrintValue()
}
神奇的地方: Object的指针 也可以是 IObject, 因为遵循的是第一条规则, Object指针调用方法PrintValue也是使用取值操作符, 但要注意, 这里的赋值时指针赋值, 而不是对象赋值
于是我们可以说: 如果以 对象receiver 的方式实现了一个接口, 那么这个 对象的变量 和 对象指针的变量 都能赋值给这个接口
但是, 如果 receiver 明确是一个 对象的指针 那么就只能讲对象的指针赋值给接口, 而不能讲 对象 赋值给接口了
type IObject interface {
PrintValue()
}
type Object struct {
Value int
}
func (obj *Object) PrintValue() { // 这时的 receiver 是 指针
fmt.Println(obj.Value)
}
func main() {
var obj = Object{Value: 5}
var iObj1 IObject = obj // 编译报错
iObj1.PrintValue()
var iObj2 IObject = &obj
iObj2.PrintValue()
}
下个小结论: receiver 尽量用指针, 赋值给接口时 也是用 对象指针