关于Go语言中结构体方法和接口¶
做Go项目的时候,遇到Go语言中的结构体方法和interface方法的实现,发现两者之间看起来很像,但用起来却不一样,经过大量的资料查阅和思考,整理和记录了关于结构体方法和interface的一些理解,希望能帮助一些人理解这些概念和用法。以下是具体的例子和例子中表现的问题
一、结构体方法¶
首先定义一个名为Person的结构体,我们暂时可把它视为Person类
type Person struct {
name string
}
func (p Person) GetName() string {
return p.name
}
func (p Person) SetName(name string) {
p.name = name
}
func main() {
var Tom Person // 声明一个Person
Tom.SetName("Tom")
fmt.Println(Tom.GetName())
}
silvers@HP-ZHAN:~/Desktop$ go run Go_Understandable.go
silvers@HP-ZHAN:~/Desktop$
func (p *Person) SetName(name string) {
p.name = name
}
silvers@HP-ZHAN:~/Desktop$ go run Go_Understandable.go
Tom
silvers@HP-ZHAN:~/Desktop$
func (p Person) SetName() { p.name = name }
这种分离式的‘成员方法’访问成员变量必然会产生变量作用域的问题,注意到实际调用结构体方法的时候,我们先声明了一个结构体变量,然后直接使用‘.’操作符调用结构体方法:
var Tom Person // 声明一个Person
Tom.SetName("Tom")
SetName(Tom, "Tom")
换成这样就好理解了,Tom是实参,而SetName方法的修改的是形参p的name属性,这种值传递的操作不会影响到实参Tom,也就是说Tom传递到SetName方法的时候,SetName拷贝了一份Tom的内容到p变量,所以修改的操作之修改了拷贝的内容,并不会影响到实参Tom,所以我们第一次打印的时候什么也没有打印出来。
细心的朋友可能会发现了一个问题:第二次的打印的时候,我们调用SetName的形式没有变,还是:
var Tom Person // 声明一个Person
Tom.SetName("Tom")
SetName(&Tom, "Tom")
所以可以正确的使用。反过来,有人可能会问,如果定义的是
func (p Person) SetName() { p.name = name }
但是调用的时候使用指针不就可以了?如我们这样调用:
var Tom *Person = &Person{}
Tom.SetName("Tom")
fmt.Println(Tom.GetName())
silvers@HP-ZHAN:~/Desktop$ go run Go_Understandable.go
silvers@HP-ZHAN:~/Desktop$
二、关于interface¶
下面我们看一下go里面的interface 首先我们新定义一个interface
type Student interface {
GetClass() string
SetClass(class string)
}
type Person struct {
name string
class string
}
func (p Person) GetClass() string {
return p.class
}
func (p *Person) SetClass(class string) {
p.class = class
}
func main(){
var Tom Student = Person{}
Tom.SetClass("12")
fmt.Println(Tom.GetClass())
}
silvers@HP-ZHAN:~/Desktop$ go run Go_Understandable.go
# command-line-arguments
./Go_Understandable.go:42: cannot use Person literal (type Person) as type Student in assignment:
Person does not implement Student (SetClass method has pointer receiver)
silvers@HP-ZHAN:~/Desktop$
func main(){
var p = Person{}
var Tom = p
Tom.SetClass("12")
fmt.Println(Tom.GetClass())
}
silvers@HP-ZHAN:~/Desktop$ go run Go_Understandable.go
12
silvers@HP-ZHAN:~/Desktop$
func main(){
var p = &Person{}
var Tom Student = p
Tom.SetClass("12")
fmt.Println(Tom.GetClass())
}
silvers@HP-ZHAN:~/Desktop$ go run Go_Understandable.go
12
silvers@HP-ZHAN:~/Desktop$
var Tom Student = Person{}
实际上进行了一次值的拷贝,这条语句执行完后,内存里现在存在两份初始化后的Person,一个是赋值操作之前创建的Person类型的匿名变量,另一个是赋值操作拷贝的Person。调用的时候,按照第一节的类比,SetClass方法的参数列表的应该是这样的SetClass(&Tom, "12"),问题就出现在这里了:针对于拷贝的Tom(内容是Person),我们对它的任何操作都不会影响到我们最原始的Person(赋值前创建的Person类型的匿名变量),这和上一节谈到的“结构体方法通过设定接收者是指针类型还是普通结构体类型来控制结构体成员变量的写入权限”这一设计逻辑冲突,所以编译器在这里就报错了。
而使用:
var p = &Person{}
var Tom Student = p
这种情况,实际上也进行了一次拷贝操作,只是这次拷贝的内容是指向Person结构体初始化后的地址(也就是说,*Tom和*p的结果是一样的),所以,对于Tom的操作同样可以作用于p,符合“结构体方法通过设定接收者是指针类型还是普通结构体类型来控制结构体成员变量的写入权限”这一设计逻辑,因此这种用法可行。