• 【Go语言入门系列】(七)如何使用Go的方法?


    【Go语言入门系列】前面的文章:

    本文介绍Go语言的方法的使用。

    1. 声明

    如果你用过面向对象的语言,比如Java,那你肯定对类、对象、成员变量、方法等很熟悉。

    简单地来说,类是对一类事物的抽象,成员变量是该事物的属性,方法是该事物具有的行为,对象则是该事物所对应的具体个体。

    比如说,狗(类),名字(属性),叫(方法),哮天犬(对象)。

    但是Go语言中并没有类,自然也没有面向对象中的成员变量和成员方法。但是Go语言中有类似的概念——结构体,结构体中的字段可以看做类中成员属性。

    Go中也有类似于面向对象中方法的概念,也叫方法(method),这种方法其实是一种特殊的函数(function)——带有接收者(receiver)的函数。

    方法的声明方式如下:

    func (接受者) funcName(参数们) (返回值们)
    

    可以看出方法的声明方式和函数的声明方式差不多,但是多了一个接收者,该接收者是一个结构体类型。下面是一个实例:

    package main
    
    import "fmt"
    
    type dog struct {
    	name string
    }
    
    func (d dog) say() {//方法
    	fmt.Println(d.name + " 汪汪汪。。。方法")
    }
    
    func main() {
    	d := dog{"哮天犬"}
    	d.watchDoor()
    }
    

    运行:

    哮天犬 汪汪汪。。。方法
    

    say()是一个方法,d是接收者,是一个结构体类型参数,方法里可以访问接收者的字段:

    fmt.Println(d.name + " 汪汪汪。。。方法")
    

    通过.可以调用方法:

    d.say()
    

    2. 方法和函数

    方法method是具有接收者receiver的特殊函数function。下面的例子展示了methodfunction之间的区别。

    package main
    
    import "fmt"
    
    type dog struct {
    	name string
    }
    
    func (d dog) say() {
    	fmt.Println(d.name + " 汪汪汪。。。方法")
    }
    
    func say(d dog) {
    	fmt.Println(d.name + " 汪汪汪。。。函数")
    }
    
    func main() {
    	d := dog{"哮天犬"}
    	d.watchDoor()
    	watchDoor(d)
    }
    

    运行:

    哮天犬 汪汪汪。。。方法
    哮天犬 汪汪汪。。。函数
    

    你可能会问,在这个例子中,既然方法和函数的运行结果一样,那使用方法岂不是多此一举,为何不继续使用函数?

    换一个场景:现在有狗、猫、兔子等动物,他们都会叫,只是叫声不同:

    package main
    
    import "fmt"
    
    type dog struct {
    	name string
    }
    
    type cat struct {
    	name string
    }
    
    type rabbit struct {
    	name string
    }
    
    func dogSay(d dog) {
    	fmt.Println(d.name + " 汪汪汪。。。函数")
    }
    
    func catSay(c cat)  {
    	fmt.Println(c.name + " 喵喵喵。。。函数")
    }
    
    func rabbitSay(r rabbit) {
    	fmt.Println(r.name + " 吱吱吱。。。函数")
    }
    func main() {
    	d := dog{"哮天犬"}
    	c := cat{"加菲猫"}
    	r := rabbit{"玉兔"}
    	dogSay(d)
    	catSay(c)
    	rabbitSay(r)
    }
    

    运行:

    哮天犬 汪汪汪。。。函数
    加菲猫 喵喵喵。。。函数
    玉兔 吱吱吱。。。函数
    

    上面的三个函数有什么不妥之处呢?

    首先,这三个函数都是用来表示这一行为,一般来说函数名都会叫say(),但因为不同的动物,函数名不能相同,为了做区别而做出了改变。

    其次,这个行为应该属于动物,二者在概念上不能分开。比如,说话这个行为是每个人都具有的,但是说话并不能离开人而独自存在。

    此时,方法method的优点就体现了出来:

    package main
    
    import "fmt"
    
    type dog struct {
    	name string
    }
    
    type cat struct {
    	name string
    }
    
    type rabbit struct {
    	name string
    }
    
    func (d dog) say() {
    	fmt.Println(d.name + " 汪汪汪。。。方法")
    }
    
    func (c cat) say()  {
    	fmt.Println(c.name + " 喵喵喵。。。方法")
    }
    
    func (r rabbit) say() {
    	fmt.Println(r.name + " 吱吱吱。。。方法")
    }
    
    func main() {
    	d := dog{"哮天犬"}
    	c := cat{"加菲猫"}
    	r := rabbit{"玉兔"}
    
    	d.say() //调用
    	c.say()
    	r.say()
    }
    

    运行:

    哮天犬 汪汪汪。。。方法
    加菲猫 喵喵喵。。。方法
    玉兔 吱吱吱。。。方法
    

    三个方法的方法名都一样,每个方法都有一个接受者receiver,这个receiver使方法在概念上属于结构体,就像结构体的字段一样,但是没有写在结构体内。

    从这三个方法中可以看出:只要方法的接收者不同,即使方法名相同,方法也不相同

    3. 指针和接收者

    接收者可以使用指针,和函数的参数使用指针一样(参考Go语言入门系列(六)之再探函数),接收者使用指针传的是引用,不使用指针传的是值拷贝。看下面一个例子:

    package main
    
    import "fmt"
    
    type dog struct {
    	name string
    }
    
    func (d *dog) rename(name string) {
    	d.name = name
    	fmt.Println("方法内:" + d.name)
    }
    
    func (d dog) rename1(name string)  {
    	d.name = name
    	fmt.Println("方法内:" + d.name)
    }
    

    renamerename1都是改变名字的方法,一个传引用,一个传值。只有rename能真正改变名字。

    func main() {
    	d := dog{"哮天犬"}
    	d.rename("小黑黑")
    	fmt.Println(d.name)
    }
    

    运行:

    方法内:小黑黑
    小黑黑
    

    rename把“哮天犬”改为了“小黑黑”。

    func main() {
    	d := dog{"哮天犬"}
    	d.rename1("小红红")
    	fmt.Println(d.name)
    }
    

    运行:

    方法内:小红红
    哮天犬
    

    rename1只在方法内改变了名字,并没有真正改变“哮天犬”。因为rename1接收的是d的一个拷贝。

    方法的指针接收者可以进行重定向,什么意思呢?下面用四段代码来说明。

    如果函数的参数是一个指针参数,那么该函数就必须接收一个指针才行,如果是值则报错

    package main
    
    import "fmt"
    
    func double(x *int) {
    	*x = *x * 2
    }
    
    func main() {
    	i := 2
    	double(&i) //编译正确
    	double(i) //报错
    	fmt.Println(i)
    }
    

    而如果方法的接收者是一个指针,那么该方法被调用时,接收者既可以是指针,又可以是值

    package main
    
    import "fmt"
    
    func (d *dog) rename(name string) {
    	d.name = name
    	fmt.Println("方法内:" + d.name)
    }
    
    func main() {
    	d := dog{"哮天犬"}
    	d.rename("小黑黑") //接收者是值,编译正确
    	//(&d).rename("小黑黑") //接收者是指针,编译正确
    	fmt.Println(d.name)
    }
    

    对于指针接收者来说,d.rename("小黑黑")被解释为(&d).rename("小黑黑"),如此一来,我们就不需要在意调用方法的接收者是否为指针类型,因为Go会进行“重定向”。

    同理,反过来也可以。

    如果函数的参数是值,而不是指针,那么该函数必须接受值,否则会报错

    package main
    
    import "fmt"
    
    func double(x int) {
    	x = x * 2
    }
    
    func main() {
    	i := 2
        p := &i
    	double(*p) //参数是值,编译正确
    	//double(p) //参数是指针,报错
    	fmt.Println(i)
    }
    

    而如果方法的接收者是一个值,那么该方法被调用时,接收者既可以是值,又可以是指针

    package main
    
    import "fmt"
    
    func (d dog) rename1(name string)  {
    	d.name = name
    	fmt.Println("方法内:" + d.name)
    }
    
    func main() {
    	d := dog{"哮天犬"}
    	p := &d
    	p.rename1("小红红") //接收者是指针,编译正确
    	//(*p).rename1("小红红") //接收者是值,编译正确
    	fmt.Println(d.name)
    }
    

    对于值接收者来说,p.rename1("小红红")被解释为(*p).rename1("小红红"),如此一来,我们就不需要在意调用方法的接收者是否为值,因为Go会进行“重定向”。

    作者简介

    我是行小观,我会在公众号『行人观学』中持续更新Java、Go、数据结构和算法、计算机基础等相关文章。


    本文章属于系列文章「Go语言入门系列」,本系列从Go语言基础开始介绍,适合从零开始的初学者。


    欢迎关注,我们一起踏上行程。

    如有错误,还请指正。

  • 相关阅读:
    代码对比软件——code compare
    IAR调试和keil调试的一点小区别
    谷访问歌助手
    X86架构的寄存器
    ant-design-pro
    js 闭包 作用域
    《三体》总结
    如何通过SQL注入盗取数据库信息
    《看见》总结
    《欲望的演化》总结
  • 原文地址:https://www.cnblogs.com/xingrenguanxue/p/13591846.html
Copyright © 2020-2023  润新知