Golang method 方法详解

Posted Wallace JW

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Golang method 方法详解相关的知识,希望对你有一定的参考价值。

前言

作为一种面向对象编程语言,Golang 和其他的 OOP 语言在面向对象的实现上有较大的区别,他没有 class 的概念,而是通过 结构体(struct)-方法(method)-接口(interface) 的组合使用来实现面向对象的思想。在之前的文章 Golang 复合类型 中已经介绍过结构体,本文将介绍 method 的用法。

文章目录

基本语法

接收器

方法的使用和函数非常相似,都是通过包装一段代码块来实现某种可复用的功能,不同的是,函数的输入通过入参来定义,而对于方法,声明时需要在方法名之前声明一个带类型的参数作为方法的接收器(receiver),这个附加的参数会将该函数附加到这种类型上,从而成为一种专用于这种数据类型的方法,有点类似其他语言中 class 的成员函数。

注意:方法的接收器必须为命名类型,不能为基础类型,也不可以为接口,如果需要使用基础类型作为接收器,需要使用 type 重新定义一种类型,如:

type testint int  //  使用 type 定义一种 testint 类型
func (a testint) test(b int) int  // test 方法为 testint 类型的方法
  	c := int(a) + b		// 类型转换
	return c


func main() 
	var a testint = 2
	fmt.Println(a.test(3)) // 5

声明和调用

下面用一段代码来说明方法和函数在声明和调用上的区别:

type Point struct 
	X float64
	Y float64


type Path []Point

// 函数
func Distance(p, q Point) float64 
    return math.Hypot(q.X-p.X, q.Y-p.Y)


// Point 类型的方法
func (p Point) Distance(q Point) float64 
    return math.Hypot(q.X-p.X, q.Y-p.Y)


// Path 类型的方法
func (path Path) Distance() float64 
	sum := 0.0
	for i := range path 
		if i > 0 
			sum += path[i-1].Distance(path[i])
		
	
	return sum


p := Point1, 2
q := Point4, 6
perim := Path
	1, 1,
	5, 1,
	5, 4,
	1, 1,


fmt.Println(Distance(p, q)) // "5", function call
fmt.Println(p.Distance(q))  // "5", method call Point.Distance
fmt.Println(perim.Distance()) // "12", method call Path.Distance

事实上,方法也是一种函数,只是把接收器作为第一个入参而已。

fmt.Printf("%T\\n", Point.Distance)		//func(Point, Point) float64
fmt.Printf("%T\\n", p.Distance)			// func(Point) float64

选择器

形如 p.Distance(q) p.X 这种表达式叫做选择器(selector),选择器会根据对象 p 的类型来选择对应的方法或者结构体字段,比如说 p.Distance(q) 会调用求点距离的方法而不是求三角形周长的方法。

因此,某个结构体的方法不能和结构体的字段重名,其内部的方法也都必须有唯一的方法名,比如对于 Point 结构体,如果定义一个名为 X 的方法,则会编译错误。但是不同的类型可以有同样的方法名。

基于指针对象的方法

由于方法也是一种函数,所以我们很容易理解方法也是通过值传递的方式来拷贝参数值,因此,和函数中的处理一样,如果我们想要在方法内对一个参数进行改变,则必须使用指针对象来作为方法的接收器,如:

func (p *Point) ScaleBy(factor float64) 
	p.X *= factor
	p.Y *= factor


r := &Point1, 2
r.ScaleBy(2)
fmt.Println(*r) // "2, 4"

// 注意,由于临时变量的内存地址无法获取,所以不能使用下面这种写法
Point1, 2.ScaleBy(2) // compile error: can't take address of Point literal

这个方法的名字是 (*Point).ScaleBy,这是一个 (*Point) 类型的方法而不是 Point 类型的方法。在实际使用中,因为通常不会对一个结构体类型仅有只读操作,所以基本上都会用指针来作为方法的接收器。

一个很容易出错的点在于,我们需要使用一个类型的指针来作为接收器,而不能使用指针类型本身来作为接收器,举个例子:

func (p *Point) f1()   // 正确

type PP *Point
func (pp PP) f2()   // 编译错误

f2 这种写法会报编译错误

invalid receiver type PP (PP is a pointer type)

通过内嵌结构体扩展方法

在之前的文章中,介绍过内嵌结构体的使用,通过内嵌结构体,可以在外层结构体中直接使用内层结构体的字段,而上文我们又说了形如 p.Distance(q) p.X 都是对象 p 的选择器,所以我们自然会想到,外层结构体也可以使用内嵌结构体的方法,类似于其他 OOP 语言中的继承思想。如下:

import "image/color"

type Point struct 
	X float64
	Y float64 


type ColoredPoint struct 
	Point
	Color color.RGBA


red := color.RGBA255, 0, 0, 255
blue := color.RGBA0, 0, 255, 255
var p = ColoredPointPoint1, 1, red
var q = ColoredPointPoint5, 4, blue
fmt.Println(p.Distance(q.Point)) // "5"

可以看到,即使 ColoredPoint 类型没有声明 Distance 方法,也可以直接当作 Point 类型方法的接收器来使用。类比于其他的 OOP 语言,可以将 Point 看做基类,将 ColoredPoint 看做继承它的子类来理解。

但是必须强调以下两点:

  1. 继承只是一种理解方法,和继承思想最大的不同是,ColoredPoint 只是拥有 Point 成员并且可以使用它的字段和方法,但是 ColoredPoint 并不是 Point 类型,因此,ColoredPoint 虽然可以作为 Point 方法的接收器,但是并不能作为 Point 类型的参数使用,下列写法会报错。
p.Distance(q) // compile error: cannot use q (ColoredPoint) as Point
  1. 只有内嵌结构体可以实现上述用法,如果是将 Point 类型的对象作为 ColorPoint 的字段,则不成立,以下写法会报错。
type ColoredPoint struct 
	point Point
	Color color.RGBA


fmt.Println(p.Distance(q.Point))
// 报错
// p.Distance undefined (type ColoredPoint has no field or method Distance)
// q.Point undefined (type ColoredPoint has no field or method Point, but does have point)

方法值

跟函数一样,方法也可以作为方法值使用,这种情况下调用时就不再需要指定接收器,如下:

p := Point1, 2
q := Point4, 6
distanceFromP := p.Distance // method value
fmt.Println(distanceFromP(q)) // "5"

以上是关于Golang method 方法详解的主要内容,如果未能解决你的问题,请参考以下文章

Golang interface 接口详解

Golang interface 接口详解

golang 详解defer

Golang入门到项目实战 golang方法

Golang详解调度机制 抢占式调度

Golang中Channel详解