1、为什么在常见的代码里,接口变量var test someInterface一般被赋予指针值,而非对象?
首先假设定义一个接口
type BaseType interface {
Opt(a int, b int) string
}
我们常见的实现方式时在指针上实现这个接口
type PType struct {
Name string
}
func (thiz *PType) Opt(a int, b int) string {
fmt.Printf("PType函数内对象地址-->%p\n", thiz)
return strconv.Itoa(a + b)
}
常见的使用方式是
var base_p interfaceType.BaseType = new(interfaceType.PType)
那么,
1、Go里面对接口类型变量(如var base_p interfaceType.BaseType)只能被赋予指针值(如new(interfaceType.PType))吗?
2、Go里面接口只能在指针上实现(如func (thiz *PType) Opt(a int, b int) string)吗?
两个问题的答案都是否定的。看看下面的例子:
package interfaceType
import (
"fmt"
"strconv"
)
type BaseType interface {
Opt(a int, b int) string
}
type PType struct {
Name string
}
func (thiz *PType) Opt(a int, b int) string {
fmt.Printf("PType函数内对象地址-->%p\n", thiz)
return strconv.Itoa(a + b)
}
type OType struct {
Id string
}
func (this OType) Opt(a int, b int) string {
fmt.Printf("OType函数内对象地址-->%p\n", &this)
return strconv.Itoa(b - a)
}
package main
import "fmt"
import "./interfaceType"
func main() {
var base_p interfaceType.BaseType = new(interfaceType.PType)
fmt.Printf("PType对象地址-->%p\n", base_p)
fmt.Println(base_p.Opt(1, 2))
var base_o interfaceType.BaseType = interfaceType.OType{}
fmt.Printf("OType对象地址-->%p\n", &base_o)
fmt.Println(base_o.Opt(3, 4))
}
运行结果:
PType对象地址-->0xc000010200
PType函数内对象地址-->0xc000010200
3
OType对象地址-->0xc000010220
OType函数内对象地址-->0xc000010240
1
这说明:接口变量可以被赋予指针值,也可以被赋予对象;到底该赋予那种值,取决于接口是在指针还是在结构上实现的。
之所以通常在指针上实现是因为:Go是彻底的值传递的(与C/C++一致)。分解下上面的两种实现指针的方式:
指针实现:
首先看 base_p.Opt(1, 2)
此时Opt函数是在base_p上调用的。为什么不能使用(*base_p).Opt(1,2)呢?
因为(*base_p)得到的是一个type PType struct,而在type PType struct上并没有实现Opt(a int, b int) string,所以编译器会报
invalid indirect of base_p (type interfaceType.BaseType)
错误。
base_p.Opt(1, 2)对应的函数是
func (thiz *PType) Opt(a int, b int) string {
fmt.Printf("PType函数内对象地址-->%p\n", thiz)
return strconv.Itoa(a + b)
}
很显然,这个函数在执行之前会发生形参赋值
thiz = base_p
a = 1
b = 2
这时,打印语句fmt.Printf("PType函数内对象地址-->%p\n", thiz)
打印的是thiz的值,即thiz指向的对象的值,显然就是base_p指向的对象的值,也就是new(interfaceType.PType)
所以,两次打印语句
fmt.Printf("PType对象地址-->%p\n", base_p)
fmt.Printf("PType函数内对象地址-->%p\n", thiz)
打印的值是一致的!
同理,分析直接在类型上实现:
base_o.Opt(3, 4)对应的函数是
func (this OType) Opt(a int, b int) string {
fmt.Printf("OType函数内对象地址-->%p\n", &this)
return strconv.Itoa(b - a)
}
同样在函数执行之前会发生形参赋值
this = base_o
a = 3
b =4
很显然,由于Go是彻底的值传递,所以在执行this = base_o时,已经发生了拷贝赋值。所以两次打印语句
fmt.Printf("OType对象地址-->%p\n", &base_o)
fmt.Printf("OType函数内对象地址-->%p\n", &this)
打印的结果是不一样的。
所以综上,直接在类型上实现接口有两点坏处:
1、发生对象的拷贝赋值,浪费内存(但是这还不是最严重的问题)
2、如果在所实现的接口函数内有涉及对象成员参数的操作,这些操作将对调用接口函数的对象无效(这一点在C++里面不存在)!