twelvet

当前位置:首页 > 技术分享 > Go

Go

Go基础:函数声明之方法接受者(函数名之前括号中的内容)

2022-11-15 08:48:09 TwelveT 210
什么是方法?方法与函数的区别是,函数不属于任何类型,方法属于特定的类型。普通类型的方法接受者Go语言中的​​方法(Method)​​是一种作用于特定类型变量的函数。这种特定类型变量叫做​​接收者(Receiver)​​。接收者的概念就类似于其他语言中的​​this​​或者​​self​​。方法的定义格式如下:func(...

什么是方法?

方法与函数的区别是,函数不属于任何类型,方法属于特定的类型。

普通类型的方法接受者

Go语言中的 ​​方法(Method)​​ 是一种作用于特定类型变量的函数。这种特定类型变量叫做 ​​接收者(Receiver)​​。
接收者的概念就类似于其他语言中的​​this​​或者 ​​self​​。

方法的定义格式如下:
func (接收者变量 接收者类型) 方法名(参数列表) (返回参数) {
    函数体
}
其中,接收者变量:接收者中的参数变量名在命名时,官方建议使用接收者类型名称首字母的小写,而不是​​self​​、​​this​​之类的命名。例如,​​Person​​类型的接收者变量应该命名为 ​​p​​,​​Connector​​类型的接收者变量应该命名为​​c​​等。
接收者类型:接收者类型和参数类似,可以是指针类型和非指针类型。
方法名、参数列表、返回参数:具体格式与函数定义相同。

举个栗子,func ( x DDDD) FOO(var type) ( ret type , err Errot),这里来说一下 x DDDD是什么意思。

在go语言中,没有类的概念但是可以给类型(结构体,自定义类型)定义方法。

所谓方法就是定义了接受者的函数,方法和函数只差了一个,那就是方法在 func 和标识符之间多了一个参数。接受者定义在func关键字和函数名之间:
type Person struct {
    name string
    age int
}
func (p Person) say() {
    fmt.Printf("I'm %s,%d years old\n",p.name,p.age)
}
有了对方法及接受者的简单认识之后,接下来主要谈一下接受者的类型问题。

接收者有两种,一种是值接收者,一种是指针接收者。顾名思义,值接收者,是接收者的类型是一个值,是一个副本,方法内部无法对其真正的接收者做更改;指针接收者,接收者的类型是一个指针,是接收者的引用,对这个引用的修改之间影响真正的接收者。

情况一:接受者是struct
package main
import "fmt"
type Person struct {
    name string
    age  int
}
func (p Person) say() {
    fmt.Printf("I'm %s,%d years old\n", p.name, p.age)
}
func (p Person) older() {
    p.age = p.age + 1
}
func main() {
    var p1 Person = Person{"zhansan", 16}
    p1.older()
    p1.say()
    //output: I'm zhangsan,16 years old
    var p2 *Person = &Person{"lisi", 17}
    p2.older()
    p2.say()
    //output: I'm lisi,17 years old
}
对于p1的调用,读者应该不会有什么疑问。
对于p2的调用可能存在这样的疑问,p2明明是个指针,为什么再调用了older方法之后,打印结果还是17 years old?
方法的接受者是Person而调用者是*Person ,其实在p2调用时存在一个转换p2.older() -> *p2.older(); p2.say() -> *p2.say()
*p2是什么想必读者也是明白的(就一个p2指向Person实例)。那么疑问也就自然的解开了,方法执行时的接受者实际上还是一个值而非引用。

情况二:接受者是指针
package main
import "fmt"
type Person struct {
    name string
    age  int
}
func (p *Person) say() {
    fmt.Printf("I'm %s,%d years old\n", p.name, p.age)
}
func (p *Person) older() {
    p.age = p.age + 1
}
func main() {
    var p1 Person = Person{"zhansan", 16}
    p1.older()
    p1.say()
    //output: I'm zhangsan,17 years old
    var p2 *Person = &Person{"lisi", 17}
    p2.older()
    p2.say()
    //output: I'm lisi,18 years old
}
p1的调用中也存在一个转换,
p1.older -> *p1.older
p1.say() -> *p1.say()

在举一个清晰的调用例子:

我们参照着来写一下 Go 的方法定义。

首先,我们是先要定义一个类型,比如就是 user 好了,然后我们再定义方法。
type user struct {
    name  string
    email string
}
func (u user) notify() {
    fmt.Println("Email is %d", u.email)
}
func (u *user) changeEmail(email string) {
    u.email = email
}
我们定义了两个方法,一个是 notify,它是值接收者方法;还有一个是 changeEmail,它是指针接收者方法。可以看到,值接收者方法,接收者是一个副本,无法修改;指针接收者是引用,可以修改。

我们再来看一下调用。
daryl := {"daryl", "daryl@oldexample.com"}
daryl.changeEmail("daryl@example.com")
daryl.notify()
看看,是不是很熟悉!对,就像 php 代码一样,有没有!daryl 就是对象,name 和 email 就是属性,notify 和 changeEmail 就是它的方法。只是,不同的是,我们没有将它放到 class 中,而是用另外一种方式让它们结合了,有了关系!

再举一个例子:
type T struct {
    Name string
}
func (t T) M1() {
    t.Name = “name1”
}
func (t *T) M2() {
    t.Name = “name2”
}
M1() 的接收者是值类型 T, M2() 的接收者是值类型 *T , 两个方法内都是改变Name值。

下面声明一个 T 类型的变量,并调用 M1() 和 M2() 。
t1 := T{“t1”}
fmt.Println(“M1调用前:”, t1.Name)
t1.M1()
fmt.Println(“M1调用后:”, t1.Name)
fmt.Println(“M2调用前:”, t1.Name)
t1.M2()
fmt.Println(“M2调用后:”, t1.Name)
M1调用前: t1
M1调用后: t1
M2调用前: t1
M2调用后: name2
先来约定一下:接收者可以看作是函数的第一个参数,即这样的: func M1(t T), func M2(t *T)。 go不是面向对象的语言,所以用那种看起来像面向对象的语法来理解可能有偏差。

当调用 t1.M1() 时相当于 M1(t1) ,实参和行参都是类型 T,可以接受。此时在M1()中的t只是t1的值拷贝,所以M1()的修改影响不到t1。

当调用 t1.M2() => M2(t1),这是将 T 类型传给了 *T 类型,go可能会取 t1 的地址传进去: M2(&t1)。所以 M2() 的修改可以影响 t1 。
 

免责声明

本站提供的一切软件、教程和内容信息仅限用于学习和研究目的;不得将上述内容用于商业或者非法用途,否则,一切后果请用户自负。本站信息来自网络收集整理,版权争议与本站无关。您必须在下载后的24个小时之内,从您的电脑或手机中彻底删除上述内容。如果您喜欢该程序和内容,请支持正版,购买注册,得到更好的正版服务。我们非常重视版权问题,如有侵权请邮件与我们联系处理。敬请谅解!

标签:接收者  类型  方法  调用  接受者  

文章评论