go interface 注意点

思考一个问题, 当将一个对象赋值给一个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 尽量用指针, 赋值给接口时 也是用 对象指针

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值