Golang 方法定义与使用

本文深入介绍了Go语言中的方法定义,包括方法与接收者的关系,方法与普通函数的区别,以及值类型和指针类型接收者的影响。通过示例解释了方法的调用机制,强调了方法调用时接收者作为参数传递的特点,并讨论了在什么情况下选择值类型或指针类型接收者。此外,还提到了匿名嵌入结构体时调用方法的规则。
摘要由CSDN通过智能技术生成

方法介绍


在某些情况下,我们要需要声明(定义)方法。比如Person结构体:除了有一些字段外(年龄,姓名..),Person结构体还有一些行为比如:可以说话、跑步、通过学习、还可以做算术题。这时就要用方法才能完成。

Golang中的方法是作用在指定的数据类型上的(即:和指定的数据类型绑定),因此自定义类型(通过type声明的),都可以有方法,而不仅仅是struct。

方法是和具体数据类型绑定的

方法只能通过指定的变量绑定。

package main

import "fmt"

type user struct {
	age  int
	name string
}

func (u *user) getInfo() {
	fmt.Println(u.name, u.age)
}

func (*user) test() {
	fmt.Println("user结构体的方法test()")
}

func main() {
	var u user
	u.age = 10
	u.name = "lucas"

	u.getInfo()
	u.test()
}

func (u *user) getInfo() {

 fmt.Println(u.name, u.age)

}

  • 这个表示那个user变量调用我,那么这个u就是其副本,和函数传参类似
  • 如果需要使用u那么就定义u *user,如果不需要使用就定义*user

方法和函数区别


1)调用方式不一样

  • 函数的调用方式:函数名(实参列表)
  • 方法的调用方式:变量.方法名(实参列表)

2)对于普通函数,接收者为值类型时,不能将指针类型的数据直接传递,反之亦然(如果是值类型,那么只能传入值类型,如果是指针类型只能传指针类型。 )

3)对于方法(如struct的方法),接收者为值类型时,可以直接用指针类型的变量调用方法,反过来同样也可以

最终决定拷贝地址还是值拷贝还是取决于接收者的类型为值类型还是指针类型

 总结:

1)不管调用形式如何,真正决定是值拷贝还是地址拷贝,看这个方法是和哪个类型绑定.

2)如果是和值类型,比如φPerson),则是值拷贝,如果和指针类型,比如是(p*Person)则是地址拷贝。I

方法的调用和传参机制原理  重要!!!!!


说明:方法的调用和传参机制和函数基本一样,不一样的地方是方法调用时,会将调用方法的变量,当做实参也传递给方法。(其实在调用方法的时候也在进行值拷贝)

和函数不同的地方:在变量调用方法的时候,该变量本身也会作为一个参数传递到方法当中,如果变量是值类型就是值拷贝,如果是引用类型就是地址拷贝。

也就是如果接受变量不是指针类型,是数据类型的话,那么就是值拷贝,会重新生成一个值变量,其实也就是值拷贝。方法调用完之后栈就销毁了。

上面就是在getsum栈里面也有一个结构体,这个结构体就是值拷贝过来的。

值拷贝情况下效率低下。

方法


  • 方法:作用在接收者上的函数(如果没有接受者的话,你要去调用一个函数,其实是去调用某一个包的函数,通过包名和函数名字来调用这个函数。如果是一个方法要先获得接受者的实例,然后通过实例来调用方法
        func     (recv receiver_type)         methodName  (parameter_list)   (return_value_list)
  • 使用场景
很多场景下,函数需要的上下文可以保存在receiver属性中,通过定义 receiver 的方法,该方法可以直接访问 receiver 属性,减少参数传递需求
// StartTLS starts TLS on a server from NewUnstartedServer.
func (s *Server) StartTLS() {

    if s.URL != “” {
    panic(“Server already started”) 
  }

    if s.client == nil {
      s.client = &http.Client{Transport: &http.Transport{}
    }
}

函数要入参数, 一个函数可能需要很多的入参,那么函数的参数列表就会非常的长了。但是将函数转化为一个具体接受者的方法都时候,所有参数就可以直接放在接受者里面就行了。

上面url client都不需要以入参的方式传递进来,在调用的时候直接通过接受者的属性拿出来就行了。

方法的好处是第一个可以缩短入参的复杂度,第二个就是可以为某一个对象赋予它具体的行为能力,使得我的程序更加面向对象。

(1)方法可以直接拿到接受者的属性,访问,修改

(2)方法可以赋予接收者行为

不同于函数的方法


在 Go 语言中,方法和函数是两个概念,但又非常相似,不同点在于方法必须要有一个接收者,这个接收者是一个类型,这样方法就和这个类型绑定在一起,称为这个类型的方法。

在下面的示例中,type Age uint 表示定义一个新类型 Age,该类型等价于 uint,可以理解为类型 uint 的重命名。其中 type 是 Go 语言关键字,表示定义一个类型。

type Age uint

func (age Age) String(){
    fmt.Println("the age is",age)
}

示例中方法 String() 就是类型 Age 的方法,类型 Age 是方法 String() 的接收者。

和函数不同,定义方法时会在关键字 func 和方法名 String 之间加一个接收者 (age Age) ,接收者使用小括号包围。

接收者的定义和普通变量、函数参数等一样,前面是变量名,后面是接收者类型。

现在方法 String() 就和类型 Age 绑定在一起了,String() 是类型 Age 的方法。

定义了接收者的方法后,就可以通过点操作符调用方法,如下面的代码所示:

func main() {
    age:=Age(25)
    age.String()
}

运行这段代码,可以看到如下输出:

the age is 25

接收者就是函数和方法的最大不同,此外,上面所讲到的函数具备的能力,方法也都具备。

提示:因为 25 也是 unit 类型,unit 类型等价于我定义的 Age 类型,所以 25 可以强制转换为 Age 类型。

值类型接收者和指针类型接收者


方法的接收者除了可以是值类型,也可以是指针类型。

定义的方法的接收者类型是指针,所以我们对指针的修改是有效的,如果不是指针,修改就没有效果,如下所示: 

func (age *Age) Modify(){
    *age = Age(30)
}

调用一次 Modify 方法后,再调用 String 方法查看结果,会发现已经变成了 30,说明基于指针的修改有效,如下所示:

age:=Age(25)
age.String()
age.Modify()
age.String()

提示:在调用方法的时候,传递的接收者本质上都是副本,只不过一个是这个值副本,一个是指向这个值指针的副本。

指针具有指向原有值的特性,所以修改了指针指向的值,也就修改了原有的值。

我们可以简单地理解为值接收者使用的是值的副本来调用方法,而指针接收者使用实际的值来调用方法。

示例中调用指针接收者方法的时候,使用的是一个值类型的变量,并不是一个指针类型,其实这里使用指针变量调用也是可以的,如下面的代码所示:

(&age).Modify()

这就是 Go 语言编译器帮我们自动做的事情:

  • 如果使用一个值类型变量调用指针类型接收者的方法Go 语言编译器会自动帮我们取指针调用,以满足指针接收者的要求。(&变量)

  • 同样的原理,如果使用一个指针类型变量调用值类型接收者的方法,Go 语言编译器会自动帮我们解引用调用,以满足值类型接收者的要求。(*变量)

总之,方法的调用者,既可以是值也可以是指针,不用太关注这些,Go 语言会帮我们自动转义,大大提高开发效率,同时避免因不小心造成的 Bug。

不管是使用值类型接收者,还是指针类型接收者,要先确定你的需求: 在对类型进行操作的时候是要改变当前接收者的值,还是要创建一个新值进行返回?这些就可以决定使用哪种接收者。

方法接受类型 


有些函数是专门给特定的结构体对象使用的,比如对包外不可见的结构体调用可见方法来修改里面属性和获取里面属性,既然是对某个特定结构体的函数,那么有没有这种函数供特定结构体来调用呢? 也就是让函数的作用域是针对某种结构体。
所以这个时候就有了方法,方法是为特定类型定义的,只能由该类型调用的函数。
定义
方法是添加了接收者的函数,接收者必须是自定义的类型(方法就是在函数名称之前加了一个接收者,接受者就是调用者,只能由谁来调用)
type privateStruct struct {
	privateAttr string
	PublicAttr string
}

func NewPrivateStruct(attr1 string,attr2 string) *privateStruct {
	return &privateStruct{attr1,attr2}
}


//因为返回的是指针类型,所以调用者/接收者 是指针类型结构体,这里返回就是接受者即谁来调用的
//方法 privateStruct结构体指针接受者方法
func (p *privateStruct) GetPrivateAttr() string{
	return p.privateAttr
}

上面接收者也就是谁可以来调用这个函数,也就是哪种类型的来调用,上面是由privateStruct指针类型才可以去调用。

调用就是结构体对象方法的名称()。

 a := test.NewPrivateStruct("private","public")
 fmt.Println(a.GetPrivateAttr())

函数调用的时候参数之间的传递相当于一个赋值的过程,一个结构体A赋值给结构体B,再去修改B对A是没有影响的。同理方法接受者不是结构体指针,而是结构体,那么要修改结构体里面的成员变量是不会成功的,对外面传递进来的结构体的成员是不会产生影响的(两个内存当中的数据,互不影响)。但是能够获取,因为传递值的时候复制过去了,返回的时候也是返回复制的值。

接受者只是指定了那种类型可以调用该函数,但是还是需要值传递的,下面是另外一种值调用(接受者是值类型,但是调用的时候是指针类型如何去调用)。

和上面是类似的,也不能修改成员变量。这样写是go的语法糖🍬,严格意义上是上面这种写法。

这种语法🍬只限于结构体变量在调用方法的时候。

和调用的是指针类型还是值类型没有关系,只和接受者是有关系的。能不能修改决定于接受者,不是决定于调用者。

匿名嵌入


若结构体匿名嵌入带有方法的结构体时,则在外部结构体可以调用嵌入结构体的方法,并且在调用时只有嵌入的字段会传递给嵌入结构体方法的接收者。

当被嵌入结构体与嵌入结构体具有相同名称的方法时,则使用对象.方法名调用被嵌入结构体方法。若想要调用嵌入结构体方法,则使用对象.嵌入结构体名.方法。

  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值