go方法(40)
方法是为特定类型的定义的,方法和函数类似,只能由该类型调用的函数。
定义:定义一个方法必须要添加一个接收者函数,接收者必须是自定义的类型-调用:调用方法通过自定义类型的对象.方法名进行调用,在调用过程中对象传递给方法的接收者(值类型,拷贝)指针接收者:当使用结构体指针调用接收者方法时,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
到gg1
dog.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 小黄:在叫