方法
什么是方法
Golang中,方法是一种定义在某个接收器上的函数,其中接收器位于关键字func
和方法名之间。 接收器其实很简单,只是接收器这个名字听起来比较晦涩难懂。在Golang中我们定义方法跟其他编程语言其实很类似,也是为了给某个数据类型来提供一些功能性方法,Golang采用的方式就是接收器。Golang中接收器既可以是结构体类型,也可以是非结构体类型。在方法内部我们可以访问接收器,这就像在Java
、JavaScript
等编程语言中我们可以在方法内部访问this
一样。
Golang中创建一个方法的语法如下:
这里创建了一个名为methodName
的方法,该方法的接收器数据类型为Type
。
方法举例
下面我们为某个结构体类型创建一个方法:
这里我们在结构体数据类型Employee
上创建了一个方法displaySalary
。在该方法内部我们可以通过接收器e Employee
来访问调用该方法的结构体变量,因此我们可以将接收器理解为该方法执行的上下文,类似于面向对象编程语言比如Java
中的this
。这里我们通过接收器e
来打印了一个员工的名字和薪资。创建完方法之后我们通过emp1.displaySalary()
来调用该方法。该程序打印结果如下:
方法 vs 函数
Golang中既然已经有函数了,为什么还需要方法呢?我们完全可以使用函数来实现前段代码的功能:
这里我们没采用方法,而是使用函数实现了相同的功能,打印结果为:Salary of Sam Adolf is $5000
。 因此我们肯定要问,为什么还要在Golang中使用方法呢?其实方法在Golang中还是很重要的,主要有以下几点原因:
Golang不是一种纯粹的面向对象编程语言,Golang并不支持
class
。我们可以通过方法来实现面向对象的特性,后面几章节我们会详细介绍Golang中的面向对象编程,现在你只需要知道Golang中面向对象编程需要借助方法即可。Golang中不同的数据类型可以定义相同的方法名称。假设两个结构体类型分别是
Square
和Circle
。我们可以分别在这两个结构体类型上定义Area
方法:```go
package main
import ( "fmt" "math" )
type Rectangle struct { length int width int }
type Circle struct { radius float64 }
func (r Rectangle) Area() int { return r.length * r.width }
func (c Circle) Area() float64 { return math.Pi c.radius c.radius }
func main() { r := Rectangle{ length: 10, width: 5, }
}
指针接收器 vs 值接收器
前面我们实现的方法都是基于值接收器,Golang中也可以基于指针接收器来实现方法。这两种不同接收器的区别在于,基于指针接收器实现的方法,我们可以通过指针来改变该指针指向的变量,而这可能对方法之外定义的变量产生直接影响。而基于值接收器实现的方法,在方法内部相当于对某个变量做了一个拷贝,因此不会对原变量产生直接影响。 这一点看起来比较晦涩,下面我们通过一个例子来理解。
该程序中,方法changeName
使用的是值接收器(e Employee)
,而方法changeAge
使用的是指针接收器(e *Employee)
。在changeName
方法内部对结构体变量的修改e.changeName("Michael Andrew")
不会影响之前定义好的结构体变量e
的属性值e.name
,但是在changeAge
方法内部通过指针对结构体变量的修改(&e).changeAge(51)
会直接反应到之前定义好的结构体变量e
的属性值e.age
上。该程序输出如下:
前面程序中我们使用(&e).changeAge(51)
来调用changeAge
方法,这是因为方法changeAge
的接收器类型是指针接收器,因此我们需要基于(&e)
来调用。不过Golang给我们提供了一种便利的语法糖,我们可以直接使用e.changeAge(51)
来调用,Golang会检测到changeAge
方法的接收器类型是指针接收器,因此会帮我们把e.changeAge(51)
转换为(&e).changeAge(51)
。
那么何时使用值接收器,何时又使用指针接收器呢?其实很简单,这本质上还是指针传递和值传递的问题。如果你不想通过指针来改变方法外部定义的变量,那么就使用值传递,否则就使用指针传递。但是用值传递会将变量整体拷贝一份,对于比较大的结构体变量开销比较大,这种情况下考虑到性能可以使用指针接收器。
结构体匿名属性的方法
对于结构体的匿名属性,定义在该匿名属性上的方法可以直接在所属的结构体变量上调用。看起来比较晦涩难懂,我们通过一个例子加以理解。
这里,我们通过p.fullAddress()
来调用定义在其匿名属性address
上的方法fullAddress()
,这等价于p.address.fullAddress()
。该程序输出如下:
方法调用语法糖
前面我们已经看到了,如果一个方法的接收器类型是指针接收器,那么我们可以直接通过变量来调用方法e.changeAge(51)
,Golang会帮我们转换成(&e).changeAge(51)
,这其实是Golang提供给我们的语法糖。
Golang还提供了另外一种语法糖,如果一个方法的接收器类型是值接收器,那么正常情况下我们调用该方法是通过e.methodName()
,其中e
是一个结构体变量。但是如果我们拿到的是指针,也可以通过指针来调用:
可以看出,Golang提供了非常方便的语法糖,Golang去判断具体调用的方法接收器类型是指针接收器还是值接收器,然后帮我们做转换。需要注意,这种语法糖仅适用于方法,不适用于函数,如果一个函数声明的入参类似是指针类型,那么仅能传递指针类型的参数,反之如果函数声明的入参类型是值类型,也仅能传递值类型的参数。
方法定义与类型声明必须同包
在Golang中,如果我们要给某个结构体类型定义一个方法,那么该方法的定义需要与类型的定义位于同一个包中。 前面的几个例子我们都是这么做的,因此没什么问题,否则就会报类似如下的错误cannot define new methods on non-local type xxx
。
给非结构体类型定义方法
在Golang中我们不仅可以给结构体类型定义方法,还可以给非结构体类型定义方法,但是仍然需要满足前面类型定义与方法定义同包的要求。 比如我们想给int
类型定义一个方法add
。下面的定义是不行的,会报错cannot define new methods on non-local type int
。
因此,我们需要定义一个别名类型myInt
,这样就可以在相同包内定义对应的方法:
该程序输出:Sum is 15
。
Last updated
Was this helpful?