go中的方法是一种作用于特定类型变量(也即接收器receiver)的函数。如果把特定类型理解为结构体或类时,接收器receiver类似于python中的self。

在go中,接收器receiver可以是任何类型和结构体;任何类型都可以拥有方法。

在面向对象编程范式中,类拥有的方法一般理解为类可以做的事情。go中的方法也是这个意思。在go中,方法的作用对象是接收器(也即类型实例),而函数没有作用对象。



1. 接收器 —— 方法的作用对象

  • 接收器格式
func (接收器变量 接收器类型) 方法名(参数列表) (返回参数) {
    函数体
}

接收器变量:建议使用接收器类型名的第一个小写字母,而不是self、this。例如Socket类型接收器变量应该命令为s,Connector类型接收器变量命名为c。

接收器类型:与参数类似,可以是指针类型和非指针类型。

方法名、参数列表、返回参数:格式与函数定义一致。

根据类型,接收器可分为指针接收器、非指针接收器。两种接收器会有不同的使用效果。

  • 指针类型接收器(常用)

指针类型的接收器由一个结构体指针组成,更接近面向对象中的this或self。

由于指针特性,调用方法时,修改接收器指针任意成员变量,方法结束后,修改都是有效记忆的。

package main

import "fmt"

// 定义结构体Property
type Property struct {
    value int               // 拥有一个整型的成员变量
}

// 定义修改字段值的方法
func (p *Property) SetValue(v int) {
    p.value = v             // 没有返回值,就没有返回类型
}

// 定义获取字段值的方法
func (p *Property) GetValue() int {
    return p.value
}

func main() {
    p := new(Property)   //实例化Property结构体,得到结构体指针类型*T
    p.SetValue(100)           // 调用Setvalue方法,修改属性值
    fmt.Println(p.GetValue()) // 调用Getvalue方法,获取属性值
}
  • 非指针类型接收器

当方法作用于非指针类型接收器时,go会在代码运行时把接收器的值复制一份。在非指针类型接收器的方法中,我们可以获取接收器的成员值,但修改后无效。

Point为小内存对象,在函数返回值复制时,可以极大提高代码运行效率。

package main

import (
    "fmt"
)

// 定义点结构体,包含x,y两个分量
type Point struct {
    X int
    Y int
}

// 为结构体Point定义一个Add方法,传入和传出都是Point结构体,实现多点连续相加,如p1.Add(p2).Add(p3)
func (p Point) Add(other Point) Point {
    return Point{p.X + other.X, p.Y + other.Y}
}

func main() {
    p1 := Point{1, 1}    // 初始化两个点p1,p2
    p2 := Point{2, 2}
    
    result := p1.Add(p2) // 两点相加
    
    fmt.Println(result)  // 输出结果
}

在计算机中,小内存对象由于值复制时的速度较快,所以适合使用非指针类型接收器;大内存对象因为值复制性能较差,适合使用指针类型接收器,在接收器和参数间传递时,不进行值复制,而只是传递指针。



2. 为结构体类型添加(绑定)方法

把背包作为“对象”,把物品放入背包的过程作为“方法”。

  • 面向过程编程实现

面向过程没有“方法”,只能通过结构体和函数,使用函数入参和函数调用,模拟“方法”。

type Bag struct {
    items []int
}

// 定义函数Insert,把一个物品放入背包
func Insert(b *Bag, itemid int) {//背包指针*Bag,物品ID(itemid)
    b.items = append(b.items, itemid)// 使用append把物品ID加到Bag的items成员变量,模拟向背包加东西
}

func main() {
    bag := new(Bag)  // 创建Bag实例bag,得到结构体指针类型*Bag
    Insert(bag, 1001) // 调用Insert函数,入参bag,1001
}

Insert()函数把*Bag类型的参数放在首位,*Bag结构体为操作对象。

  • 面向对象编程实现

把背包和放入物品到背包中的过程,使用结构体和方法实现。

type Bag struct {
    items []int
}

// 为Bag类型添加Insert方法
func (b *Bag) Insert(itemid int) {
    b.items = append(b.items, itemid)
}

func main() {
    b := new(Bag)   // 实例化结构体Bag,得到*Bag类型的b
    b.Insert(1001)  // 对操作对象b,调用方法Insert
}

定义方法Insert,作用对象是接收器(b *Bag)。每个方法只能有一个接收器,func (b *Bag) Insert(itemid int)中,(b *Bag)是方法Insert的接收器,为结构体添加方法。



3. 为任何类型添加方法

go可以为任何类型添加方法。给类型添加方法,就像给结构体添加方法一样,因为结构体也是一种类型。

  • 为基本类型添加方法

go使用type关键字定义新的自定义类型,之后就可以给自定义类型添加各种方法。

判断一个值是否为0,下面给出面向过程和面向对象两种方式:

if v == 0 {
    express // v等于0  
}

if v.IsZero() {   // 把v作为整型对象,可以增加一个IsZero方法
    express // v等于0  
}
package main

import "fmt"

// 自定义类型MyInt
type MyInt int

// 为Myint类型添加IsZero方法,(m MyInt)为非指针接收器,数值类型没有必要使用指针接收器
func (m MyInt) IsZero() bool {
	return m == 0
}

// 为MyInt类型添加Add()方法
func (m MyInt) Add(other int) int {
	return other + int(m)    // 把m从MyInt类型转换成int类型
}

func main() {
	var b MyInt
	fmt.Println(b.IsZero()) // 调用b的IsZero方法,由于使用非指针接收器,b的值会被复制到IsZero方法进行判断

	b = 1
	fmt.Println(b.Add(2))   // 调用b的Add方法
}

// true
// 3