方法是为特定类型的定义的,方法和函数类似,只能由该类型调用的函数。
定义:定义一个方法必须要添加一个接收者函数,接收者必须是自定义的类型-调用:调用方法通过自定义类型的对象.方法名进行调用,在调用过程中对象传递给方法的接收者(值类型,拷贝)指针接收者:当使用结构体指针调用接收者方法时,go编译器会自动将指针对象解引用。当使用结构体对象调用方法时,go编译器会自动将值对象取引用为指针调用方法。该使用值接收者还是指针接收者,取决于是否现需要修改原始结构体,若不需要修改则使用值,若需要修改则使用指针。若存在指针接收者,则所有方法是用指针接收者。对于接收者为指针类型的方法,需要注意在运行时若接收者为nil会发生错误。那么需要初始化。
8.方法
方法值/方法表达式: 方法也可以赋值给变量,存储在数组,切片,映射中,也可以作为参数传递给函数,也可以作为返回值进行返回。方法有两种,一种使用对象/对象指针调用的方法值。另外一种是有类型/类型调用的方法表达式。
- 方法值
在方法表达式赋值时若方法接收者为值类型,则在赋值时会将值类型拷贝(若调用为指针则自动解引用拷贝)
8.1方法定义和获取
- 示例
定义一个类型
type Dog struct{
Name string
}
定义一个方法来调用,在call函数名前加括号定义类型和变量值,如下
func (dog Dog)Call(){
fmt.Printf("%s:call",dog.Name)
}
这样意思是只有dog类型的Dog才能调用
而后在调用的时候,直接调用
func main(){
dog := Dog{"大白"}
dog.Call()
}
在Call方法func (dog Dog)Call(){fmt.Printf("%s:call",dog.Name)}
中,在调用的时候,外面(dog Dog)
中的dog会传递给Dog.调用call方法
如下:
package main
import "fmt"
type Dog struct{
Name string
}
func (dog Dog)Call(){
fmt.Printf("%s:call",dog.Name)
}
func main(){
dog := Dog{"大白"}
dog.Call()
}
运行
[root@linuxea.com /opt/Golang/work5]# go run ffa.go
大白:call
8.2方法修改
我们修改上面定义的方法
dog.Name = "雪子"
dog.Call()
如下
package main
import "fmt"
type Dog struct{
Name string
}
func (dog Dog)Call(){
fmt.Printf("%s:calln",dog.Name)
}
func main(){
dog := Dog{"大白"}
dog.Call()
dog.Name = "雪子"
dog.Call()
}
运行
[root@linuxea.com /opt/Golang/work5]# go run ffa.go
大白:call
雪子:call
- 包外调用
如果要在包外调用,就需要写一个函数能够在包外调用。我们先看普通的调用,如下:
func (dog Dog) Setname(name string){
dog.Name = name
}
func main(){
dog.Name = "雪子"
dog.Call()
dog.Setname("ss")
}
运行
[root@linuxea.com /opt/Golang/work5]# go run ffa.go
大白:call
雪子:call
雪子:call
你会发现这样的修改是没有作用, 这是因为结构体是值类型,调用方法的时候,只是复制了一份,前面和后面的dog是没有关系的。
如果要进行修改,就要修改成指针的接收方法。
func (dog *Dog) PsetName(name string) {
dog.Name = name
}
这样一来调用的时候就需要获取他的指针调用,取地址,如下
(&dog).PsetName("Psetname")
dog.Call()
这里也可以直接使用
dog.PsetName("Psetname")
,因为PsetName函数中的方法*Dog是指针,会自动取引用。这是语法糖会自动进行转换。
- 自动取引用和解应用只会发生在接收者,接收者参数才会,而函数参数是不会的,函数需要手动解引用
如下:
package main
import "fmt"
type Dog struct{
Name string
}
func (dog Dog)Call(){
fmt.Printf("%s:calln",dog.Name)
}
func (dog Dog) Setname(name string){
dog.Name = name
}
func (dog *Dog) PsetName(name string) {
dog.Name = name
}
func main(){
dog := Dog{"大白"}
dog.Call()
dog.Name = "雪子"
dog.Call()
dog.Setname("ss")
dog.Call()
(*&dog).PsetName("Psetname")
dog.Call()
}
运行
[root@linuxea.com /opt/Golang/work5]# go run ffa.go
大白:call
雪子:call
雪子:call
Psetname:call
如果指针要调用值就可以使用*pog
,如下
pdog := &Dog{"pdog"}
(*pdog).Call()
这里也可以直接使用
pdog.Call()
,调用者是一个指针类型,方法接收者是值类型,会进行自动解引用。 而(*pdog).Call()
是解引用
- 自动取引用和解应用只会发生在接收者,接收者参数才会,而函数参数是不会的,函数需要手动解引用
运行
[root@linuxea.com /opt/Golang/work5]# go run ffa.go
大白:call
雪子:call
雪子:call
Psetname:call
pdog:call
在或者这样
pdog.PsetName("pdog.PsetName")
(*pdog).Call()
代码块如下:
package main
import "fmt"
type Dog struct{
Name string
}
func (dog Dog)Call(){
fmt.Printf("%s:calln",dog.Name)
}
func (dog Dog) Setname(name string){
dog.Name = name
}
func (dog *Dog) PsetName(name string) {
dog.Name = name
}
func main(){
dog := Dog{"大白"}
dog.Call()
dog.Name = "雪子"
dog.Call()
dog.Setname("ss")
dog.Call()
(*&dog).PsetName("Psetname")
dog.Call()
pdog := &Dog{"pdog"}
(*pdog).Call()
}
运行
[root@linuxea.com /opt/Golang/work5]# go run ffa.go
大白:call
雪子:call
雪子:call
Psetname:call
pdog:call
pdog.PsetName:call
8.3命名方法嵌入
以user和address为例
package main
import "fmt"
type Address struct {
Region string
Street string
No string
Name string
}
type User struct {
ID int
Name string
Addr Address
}
如下:
func (addr Address)Addr()string{
return fmt.Sprintf("%s-%s-%s-%s",addr.Region,addr.Street,addr.No,addr.Name)
}
user方法中,addr调用的是addr的方法,也就是上面写的这个方法
func (user User)User()string{
return fmt.Sprintf("%d-%s-%s",user.ID,user.Name,user.Addr.Addr())
}
而后我们调用
func main(){
var u User = User{
ID:1,
Name:"mark",
Addr: Address{
Region:"北京市",
Street:"烤鸭",
No:"123",
Name:"mark2",
},
}
fmt.Println(u)
}
代码块如下:
package main
import "fmt"
type Address struct {
Region string
Street string
No string
Name string
}
type User struct {
ID int
Name string
Addr Address
}
func (addr Address)Addr()string{
return fmt.Sprintf("%s-%s-%s-%s",addr.Region,addr.Street,addr.No,addr.Name)
}
func (user User)User()string{
return fmt.Sprintf("%d-%s-%s",user.ID,user.Name,user.Addr.Addr())
}
func main(){
var u User = User{
ID:1,
Name:"mark",
Addr: Address{
Region:"北京市",
Street:"烤鸭",
No:"123",
Name:"mark2",
},
}
fmt.Println(u)
}
运行
[root@linuxea.com /opt/Golang/work5]# go run mmqr.go
{1 mark {北京市 烤鸭 123 mark2}}
- 单独调用属性的方法
fmt.Println(u.User())
fmt.Println(u.Addr.Addr())
如下:
[root@linuxea.com /opt/Golang/work5]# go run mmqr.go
{1 mark {北京市 烤鸭 123 mark2}}
1-mark-北京市-烤鸭-123-mark2
北京市-烤鸭-123-mark2
8.3.1.String函数
我们还可以将两种方法命名为String,在打印方法的时候,go会自动调用string方法返回字符串进行打印,如下:
这个string方法是
fmt.Println
中调用的,print发现有这方法就进行调用,如果没有会将结构体属性并结成大括号的方式打印
func (addr Address)String()string{
return fmt.Sprintf("%s-%s-%s-%s",addr.Region,addr.Street,addr.No,addr.Name)
}
func (user User)String()string{
return fmt.Sprintf("%d-%s-%s",user.ID,user.Name,user.Addr)
}
代码块如下:
package main
import "fmt"
type Address struct {
Region string
Street string
No string
Name string
}
type User struct {
ID int
Name string
Addr Address
}
func (addr Address)String()string{
return fmt.Sprintf("%s-%s-%s-%s",addr.Region,addr.Street,addr.No,addr.Name)
}
func (user User)String()string{
return fmt.Sprintf("%d-%s-%s",user.ID,user.Name,user.Addr)
}
func main(){
var u User = User{
ID:1,
Name:"mark",
Addr: Address{
Region:"北京市",
Street:"烤鸭",
No:"123",
Name:"mark2",
},
}
fmt.Println(u)
fmt.Println(u.Addr)
}
如下:
[root@linuxea.com /opt/Golang/work5]# go run mmqr1.go
1-mark-北京市-烤鸭-123-mark2
北京市-烤鸭-123-mark2
8.4匿名方法嵌入
若结构体匿名嵌入带有方法的结构体时,则在外部结构体可以调用嵌入结构体的方法,并且在调用时只有嵌入的字段会传递给嵌入结构体方法的接收者。
当被嵌入结构体与嵌入结构体据有相同名称的方法时,则使用对象.方法名调用被嵌入结构体方法。若想要调用嵌入结构体方法,则使用对象.嵌入结构体名.方法
定义结构体中有id和name,分别定义get和set,set使用指针类型
- 定义结构体User
type User struct{
id int
name string
}
getid
func (user User)Getid() int {
return user.id
}
getname
func (user User)Getname() string{
return user.name
}
setid,使用指针接收者
func (user *User) SetID(id int){
user.id = id
}
setname,使用指针接收者
func (user *User) Setname(name string){
user.name = name
}
- 而后我们在定义一个结构体嵌入User
type Employee struct{
User
Salary float64
}
而后在main调用
func main(){
var mm Employee =Employee{
User :{
id:1,
name:"mark",
},
Salary:10000.1,
}
}
8.4.1通过方法获取
如果此时获取user对象,就可以使用getname
fmt.Println(mm.User.Getname())
也可以写成这样
fmt.Println(mm.Getname())
代码块如下:
package main
import "fmt"
type User struct{
id int
name string
}
func (user User)Getid() int {
return user.id
}
func (user User)Getname() string{
return user.name
}
func (user *User) SetID(id int){
user.id = id
}
func (user *User) Setname(name string){
user.name = name
}
type Employee struct{
User
Salary float64
}
func main(){
var mm Employee =Employee{
User:User{
id:1,
name:"mark",
},
Salary:10000.1,
}
fmt.Println(mm.User.Getname())
fmt.Println(mm.Getname())
}
如下:
[root@linuxea.com /opt/Golang/work5]# go run nnqr3.go
mark
8.4.2通过方法修改
这里的方法在上面已经定义并且使用指针
mm.Setname("marksugar")
fmt.Println(mm.User.Getname())
代码块
package main
import "fmt"
type User struct{
id int
name string
}
func (user User)Getid() int {
return user.id
}
func (user User)Getname() string{
return user.name
}
func (user *User) SetID(id int){
user.id = id
}
func (user *User) Setname(name string){
user.name = name
}
type Employee struct{
User
Salary float64
}
func main(){
var mm Employee =Employee{
User:User{
id:1,
name:"mark",
},
Salary:10000.1,
}
fmt.Println(mm.User.Getname())
mm.Setname("marksugar")
fmt.Println(mm.Getname())
}
mm.Getname()与mm.User.Getname()是一样的,getname自动解引用,这里是函数前的参数-接收者
- 值或者指针调用,在go中中山自动转换,这种方式就是解引用和取引用,并且解引用和取引用只会发生在接收者这块
运行
[root@linuxea.com /opt/Golang/work5]# go run nnqr3.go
mark
marksugar
- 演变下
假设Employee结构体中加上name,如下
type Employee struct{
User
Salary float64
name string
}
使用Getname调用是否为空?
func main(){
var mm Employee =Employee{
User:User{
id:1,
name:"mark",
},
Salary:10000.1,
}
fmt.Println(mm.User.Getname())
mm.Setname("marksugar")
fmt.Println(mm.Getname())
fmt.Println(mm.name)
}
运行
[root@linuxea.com /opt/Golang/work5]# go run nnqr3.go
mark
marksugar
: 只有在使用mm.name调用的时候才会是空的,使用Getname方法调用的时候,实际上调用的是User本身的name。但是当使用Getname的时候,Employee是空的,就会找到User中的name
我们在定义一个Employee的方法,名称也是Getname,如下:
func (employee Employee)Getname()string{
return employee.name
}
并且初始化name:"justin"
var mm Employee =Employee{
User:User{
id:1,
name:"mark",
},
Salary:10000.1,
name:"justin",
}
而后在进行访问
fmt.Println(mm.User.Getname())
fmt.Println(mm.Getname())
mm.Setname("marksugar")
fmt.Println(mm.Getname())
fmt.Println(mm.name)
代码块如下:
package main
import "fmt"
type User struct{
id int
name string
}
func (user User)Getid() int {
return user.id
}
func (user User)Getname() string{
return user.name
}
func (user *User) SetID(id int){
user.id = id
}
func (user *User) Setname(name string){
user.name = name
}
type Employee struct{
User
Salary float64
name string
}
func (employee Employee)Getname()string{
return employee.name
}
func main(){
var mm Employee =Employee{
User:User{
id:1,
name:"mark",
},
Salary:10000.1,
name:"justin",
}
fmt.Println(mm.User.Getname())
fmt.Println(mm.Getname())
mm.Setname("marksugar")
fmt.Println(mm.Getname())
fmt.Println(mm.name)
}
运行
[root@linuxea.com /opt/Golang/work5]# go run nnqr3.go
mark
justin
justin
justin
如上结果中,mm.User.Getname()
是调用的是User的Getname
,而mm.Getname()
调用的是Employee的Getname
,而Setname
也是User的Setname
,打印mm.Getname()
就只会是justin
了
那我们在写一个方法是Employee的Setname
func (employee *Employee) Setname(){
employee.name = name
}
其他不变,代码块如下:
package main
import "fmt"
type User struct{
id int
name string
}
func (user User)Getid() int {
return user.id
}
func (user User)Getname() string{
return user.name
}
func (user *User) SetID(id int){
user.id = id
}
func (user *User) Setname(name string){
user.name = name
}
type Employee struct{
User
Salary float64
name string
}
func (employee Employee)Getname()string{
return employee.name
}
func (employee *Employee) Setname(name string){
employee.name = name
}
func main(){
var mm Employee =Employee{
User:User{
id:1,
name:"mark",
},
Salary:10000.1,
name:"justin",
}
fmt.Println(mm.User.Getname())
fmt.Println(mm.Getname())
mm.Setname("marksugar")
fmt.Println(mm.Getname())
fmt.Println(mm.name)
}
运行
[root@linuxea.com /opt/Golang/work5]# go run nnqr3.go
mark
justin
marksugar
marksugar
那么现在,mm.Getname()
获取的就是Employee的Getname
,mm.Setname("marksugar")
修改的也是Employee的Setname的name
这便是匿名结构体方法的调用。
8.5方法值和方法表达式
方法值/方法表达式: 方法也可以赋值给变量,存储在数组,切片,映射中,也可以作为参数传递给函数,也可以作为返回值进行返回。方法有两种,一种使用对象/对象指针调用的方法值。另外一种是有类型/类型调用的方法表达式。
8.5.1方法值
- 示例
我们定义一个dog的结构体
type Dog struct{
name string
}
在定义一个Call方法
func (dog Dog) Call(){
fmt.Printf("%s:wangwangn",dog.name)
}
在定义一个setname修改
func (dog *Dog) Setname(name string){
dog.name = name
}
我们可将dog方法赋值给一个变量
func main(){
dog := Dog{"二狗"}
gg := dog.Call
fmt.Printf("%T:n",gg)
gg()
}
运行
[root@linuxea.com /opt/Golang/work5]# go run dog.go
func():
二狗:wangwang
我们使用setname修改
dog.Setname("三狗")
如下:
func main(){
dog := Dog{"二狗"}
gg := dog.Call
dog.Setname("三狗")
fmt.Printf("%T:n",gg)
gg()
dog.Setname("三狗")
gg1 := dog.Call
gg1()
}
这里必须重新赋值
dog.Call
到gg1dog.Setname("三狗") gg1 := dog.Call gg1()
因为在
gg := dog.Call
中赋值后dog.Call
已经复制到gg
中,gg
是值类型,setname
后如果直接使用gg()
是使用之前的,并且不会发生改变的。这里需要使用dog.Call
重新赋值给gg1
而后调用
- 如果多次调用修改
gg
或者gg1
,是不会影响到方法值的。值接收者方法会将对象进行拷贝
运行
[root@linuxea.com /opt/Golang/work5]# go run dog.go
func():
二狗:wangwang
三狗:wangwang
8.5.2方法值指针
如果不是用指针,是这样
package main
import "fmt"
type Dog struct{
name string
}
func (dog Dog) Call(){
fmt.Printf("%s:wangwangn",dog.name)
}
func (dog *Dog) Setname(name string){
dog.name = name
}
func main(){
dog := Dog{"二狗"}
gg := dog.Call
fmt.Printf("%T:n",gg)
gg()
dog.Setname("三狗")
dog.Call()
gg()
}
运行
[root@linuxea.com /opt/Golang/work5]# go run dog.go
func():
二狗:wangwang
三狗:wangwang
二狗:wangwang
但是,如果是指针方法,就会不一样了,如下:
func (dog *Dog) Call(){
fmt.Printf("%s:wangwangn",dog.name)
}
func (dog *Dog) Setname(name string){
dog.name = name
}
- 指针就会自动取引用
调用的方式我们可以这样
func main(){
dog := Dog{"二狗"}
gg := dog.Call
fmt.Printf("%T:n",gg)
gg()
dog.Setname("三狗")
dog.Call()
gg()
}
代码块
package main
import "fmt"
type Dog struct{
name string
}
func (dog *Dog) Call(){
fmt.Printf("%s:wangwangn",dog.name)
}
func (dog *Dog) Setname(name string){
dog.name = name
}
func main(){
dog := Dog{"二狗"}
gg := dog.Call
fmt.Printf("%T:n",gg)
gg()
dog.Setname("三狗")
dog.Call()
gg()
}
运行
[root@linuxea.com /opt/Golang/work5]# go run dog.go
func():
二狗:wangwang
三狗:wangwang
三狗:wangwang
由此可见,我们使用了指针后,gg
的值也变成了三狗:wangwang
.见上述代码
8.5.3方法表达式
还是以上述的示例,如下
package main
import "fmt"
type Dog struct {
name string
}
func (dog Dog) Call(){
fmt.Printf("%s:旺旺n",dog.name)
}
func (dog *Dog) Setname(name string){
dog.name = name
}
方法表达式就是用结构体的名字去访问对象,如下
func main(){
m1 := Dog.Call
m2 := (*Dog).Setname
fmt.Printf("%Tn",m1)
fmt.Printf("%Tn",m2)
}
运行
[root@linuxea.com /opt/Golang/work5]# go run ffbds.go
func(main.Dog)
func(*main.Dog, string)
对于方法表达式来说,调用的时候,必须传递一个对象进去
dog := Dog("小灰")
m1(dog)
运行
[root@linuxea.com /opt/Golang/work5]# go run ffbds.go
func(main.Dog)
func(*main.Dog, string)
小灰:旺旺
- setname
使用setname修改名称。我们赋值m2,如下:
m2 = (*Dog).Setname
m2(&dog,"小黑")
m1(dog)
如下
package main
import "fmt"
type Dog struct {
name string
}
func (dog Dog) Call(){
fmt.Printf("%s:旺旺n",dog.name)
}
func (dog *Dog) Setname(name string){
dog.name = name
}
func main(){
m1 := Dog.Call
m2 := (*Dog).Setname
fmt.Printf("%Tn",m1)
fmt.Printf("%Tn",m2)
dog := Dog{"小灰"}
m1(dog)
m2 = (*Dog).Setname
m2(&dog,"小黑")
m1(dog)
}
运行
[root@linuxea.com /opt/Golang/work5]# go run ffbds.go
func(main.Dog)
func(*main.Dog, string)
小灰:旺旺
小黑:旺旺
值对象可以将指针的方法赋值给一个变量,但定义的是指针接收者就不能用值对象调用。这是因为 go会自动生成一个根据值接收者方法会生成一个指针接收者方法。如下
package main
import "fmt"
type Dog struct {
name string
}
func (dog Dog) Call(){
fmt.Printf("%s:在叫n",dog.name)
}
func main(){
m1 := (*Dog).Call
dog := Dog{"小黄"}
m1(&dog)
}
也可以写成这样
(*Dog).Call(&Dog{"小黄"})
运行
[root@linuxea.com /opt/Golang/work5]# go run ff1.go
小黄:在叫