乐趣区

关于golang:Go语言入门系列七如何使用Go的方法

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

  • 【Go 语言入门系列】(四)之 map 的应用
  • 【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 语言根底开始介绍,适宜从零开始的初学者。


欢送关注,咱们一起踏上行程。

如有谬误,还请斧正。

退出移动版