前言
Go 中的方法和函数写法其实差不多,但是它们最基本的区别是,方法必须绑定在对象实例上,而且会隐式的将对象实例作为方法的第一实参。
方法
定义
func (recevier type) methodName(参数列表)(返回值列表){}
其中接收者类可以是命名类型或者结构体类型的一个值或者一个指针
所有给定类型的方法组成这个类型的方法集
调用
调用方式有两种,隐式传递接收者 和 显式传递接收者 ,如下:
type Country struct {
name string //国家名称
totalPeople int //总人数(百万)
}
func (c Country) PrintString() {
fmt.Printf("%s,the total population is %d million\n", c.name, c.totalPeople)
}
func main() {
c := Country{"test", 1}
c.PrintString()//隐式传递接收者
Country.PrintString(c) //显式传递接收者
}
值方法和指针方法
我们先写一段简单的代码,创建了一个命名类型 Country ,并在其方法集中添加了几个方法。
import "fmt"
type Country struct {
name string //国家名称
totalPeople int //总人数(单位:百万)
}
func (c Country) String() string {
return fmt.Sprintf("this country is %s,the total population of this country is %d million",
c.name, c.totalPeople)
}
func (c Country) SetName1(newName string) {
c.name = newName
}
func (c *Country) SetName(newName string) {
c.name = newName
}
func main() {
c := Country{"中国", 1413}
fmt.Println(c)
c.SetName1("九州")
fmt.Println(c)
c.SetName("中华人民共和国")
fmt.Println(c)
}
一一一一一一一一一一一一一一一一一一一
{中国 1413}
{中国 1413}
{中华人民共和国 1413}
打印结果后我们发现,使用 SetName1 去为 name 赋值的时候,name值并未改变。但是使用 SetName 的时候却改变了
其实这就是值方法和指针方法的区别
首先,我们知道,方法的第一个隐式实参实际上就是我们的接收者,在上面的例子里面,我们所有方法的第一实参实际上是结构体 Country 的对象
在 Go 中,结构体的传递是值传递,即结构体作为参数传递时,实际上是复制了一份原对象的副本传到方法里面去使用
所以,我们在方法中赋值的对象只是一个副本对象,原对象中的值是不会改变的
但是在 SetName 方法不同点在于,它的参数是指针类型。即它接受的是一个指针,不管这个指针复制几份副本,其字符串值是不会改变的,它所指向的对象也是不会改变的,所以我们对其属性的修改,是直接修改到它所指向的对象上的
值类型调用指针方法传递的是指针,指针类型调用值方法传递的是对象副本
type Country struct {
name string //国家名称
totalPeople int //总人数(百万)
}
func (c Country) PrintValue() {
fmt.Printf("this pointer is %p \n", &c)
}
func (c *Country) PrintPointer() {
fmt.Printf("this pointer is %p \n", c)
}
func main() {
c := Country{"中国", 1413}
c1 := &c
fmt.Printf("this pointer is %p \n", c1)
fmt.Println("-----------------")
c.PrintValue()
c.PrintPointer()
fmt.Println("-----------------")
c1.PrintValue()
c1.PrintPointer()
}
一一一一一一一一一一一一一一一一一一一
this pointer is 0xc000008078
-----------------
this pointer is 0xc000008090
this pointer is 0xc000008078
-----------------
this pointer is 0xc0000080a8
this pointer is 0xc000008078
首先,我们创建了值对象 c,并创建了其指针类型的对象 c1。
通过打印结果可以看出,当我们使用值类型 c 去调用值方法 PrintValue 时,传递的第一实参和自身 c 不是一个对象(不同的内存地址),传递的是对象副本。而当调用指针方法 PrintPointer 时,传递的第一实参是自身(同一内存地址)。
当我们使用指针类型 c1 去调用值方法 PrintValue 时,传递的第一实参和自身 c1 不是一个对象(不同的内存地址),传递时将该指针指向的对象做了复制,实际上传递的是对象副本。而当调用指针方法 PrintPointer 时,传递的第一实参就是当前指针类型的对象。
值类型只包含方法集中的值方法,指针类型包含方法集中的值方法和指针方法
严格来说,以上面的代码为例,我们创建的值对象 c的方法集合中,只会包含该类型的所有值方法。而其指针类型的方法集合包含所有值方法和指针方法。
上面的例子中我们之所以能够使用值类型直接调用指针方法,是因为 Go 本身为我们做了转译。
即:
c.PrintPointer()
会转译为
(&c).PrintPointer()
嵌入字段
如下代码:
其中类型 Province 中的 Country 字段声明是一个嵌入字段,即匿名字段。
Province 类型的对象,可以使用 Country 类型中的所有属性和方法,如果当前类型和嵌入字段之间存在重名的属性或者方法,当前类型会屏蔽嵌入字段中的的属性或者方法(需要特别注意的一点是:方法和属性重名也会出现屏蔽现象)。但是我们可以使用 .Country 的形式去调用嵌入字段的重名方法或属性。
在 City 类型对象中存在多层嵌入, Province 是 City 的嵌入字段,而 Country 又是 Province 的嵌入字段,City 类型的对象,可以使用 Province 和 Country 中的所有属性和方法,同样也存在屏蔽现象。
如果同层级的嵌入字段中存在重复的属性或者方法名,在调用该属性或者方法时,编译器会报错 ambiguous selector
代码示例:
type Country struct {
name string //国家名称
totalPeople int //总人数(百万)
}
func (c *Country) SetName(name string) {
c.name = name
}
func (c *Country) SetPeople(totalPeople int) {
c.totalPeople = totalPeople
}
func (c Country) PrintCountry() {
fmt.Printf("country is %s\n", c.name)
}
func (c Country) PrintString() {
fmt.Printf("%s,the total population is %d million\n", c.name, c.totalPeople)
}
type Province struct {
name string //省市名称
totalPeople int //总人数(百万)
Country
}
func (p *Province) SetName(name string) {
p.name = name
}
func (p *Province) SetPeople(totalPeople int) {
p.totalPeople = totalPeople
}
func (p Province) PrintProvince() {
fmt.Printf("province is %s\n", p.name)
}
func (p Province) PrintString() {
fmt.Printf("%s/%s,the total population is %d million\n", p.Country.name, p.name, p.totalPeople)
}
type City struct {
name string //城镇名称
totalPeople int //总人数(百万)
Province
}
func (c *City) SetName(name string) {
c.name = name
}
func (c *City) SetPeople(totalPeople int) {
c.totalPeople = totalPeople
}
func (c City) PrintString() {
//获取多层嵌入字段的 name 属性
fmt.Printf("%s/%s/%s,the total population is %d million\n", c.Country.name, c.Province.name, c.name, c.totalPeople)
}
func main() {
country := Country{"中国", 1413}
country.PrintString()
fmt.Println("----------------------------")
province := Province{"山东", 102, country}
province.PrintString()
fmt.Println("----------------------------")
city := City{"青岛", 10, province}
city.PrintCountry() //调用嵌入字段的嵌入字段中的方法
city.PrintProvince() //调用嵌入字段中的方法
city.PrintString() //方法中使用了嵌入字段的同名属性
}
一一一一一一一一一一一一一一一一一一一一一一一一一一一一
中国,the total population is 1413 million
----------------------------
中国/山东,the total population is 102 million
----------------------------
country is 中国
province is 山东
中国/山东/青岛,the total population is 10 million