目录

Go 语言方法(Method)

在 Go 语言中,方法(Method)本质上是带有特殊接收者参数的函数。通过方法,我们可以将函数与自定义类型(尤其是结构体)绑定在一起,实现面向对象编程中的"对象行为"。本篇将详细介绍 Go 方法的定义、接收者的选择、方法集规则以及实际用法。

方法的定义

Go 中定义方法使用接收者函数语法,在 func 关键字和函数名之间加上接收者参数:

package main

import "fmt"

type Rectangle struct {
    Width  float64
    Height float64
}

// Area 是 Rectangle 的一个方法,r 是接收者
func (r Rectangle) Area() float64 {
    return r.Width * r.Height
}

func main() {
    rect := Rectangle{Width: 10, Height: 5}
    fmt.Println("面积:", rect.Area()) // 输出: 面积: 50
}

上面的代码中,(r Rectangle) 就是接收者声明,表示 Area 方法属于 Rectangle 类型。调用时使用 rect.Area() 语法。

值接收者 vs 指针接收者

Go 的方法有两种接收者:值接收者指针接收者,它们的核心区别在于是否操作原始值。

值接收者:拷贝,不修改原始值

使用值接收者时,方法接收到的是接收者的副本,对副本的修改不会影响原始值:

package main

import "fmt"

type Rectangle struct {
    Width  float64
    Height float64
}

func (r Rectangle) Scale(factor float64) {
    r.Width *= factor
    r.Height *= factor
}

func main() {
    rect := Rectangle{Width: 10, Height: 5}
    rect.Scale(2)
    fmt.Printf("缩放后: %+v\n", rect) // 输出: {Width:10 Height:5},原始值未变
}

指针接收者:引用,可修改原始值

使用指针接收者时,方法接收到的是指向原始值的指针,修改会反映到原始值上:

package main

import "fmt"

type Rectangle struct {
    Width  float64
    Height float64
}

func (r *Rectangle) Scale(factor float64) {
    r.Width *= factor
    r.Height *= factor
}

func main() {
    rect := Rectangle{Width: 10, Height: 5}
    rect.Scale(2)
    fmt.Printf("缩放后: %+v\n", rect) // 输出: {Width:20 Height:10},原始值已变
}

提示:Go 会自动处理值和指针之间的转换。即使 Scale 是指针接收者方法,你也可以用 rect.Scale(2) 调用(Go 自动取地址 (&rect).Scale(2))。

什么时候用值接收者,什么时候用指针接收者

选择接收者类型的参考原则:

场景 推荐
方法需要修改接收者的状态 指针接收者
接收者是大结构体,拷贝代价高 指针接收者(避免拷贝)
方法只读取数据,不修改 值接收者
接收者是基本类型(int、float64 等) 值接收者
需要保证并发安全(避免共享状态) 值接收者
不确定时 统一使用指针接收者

一致性原则:同一个类型的所有方法,应该统一使用值接收者或统一使用指针接收者,不要混用。

方法集(Method Set)

方法集决定了某个类型的变量可以调用哪些方法,这在接口实现中尤为重要。

规则

  • 类型 T 的方法集:只包含所有值接收者方法
  • 类型 *T 的方法集:包含所有方法(值接收者 + 指针接收者)
package main

import "fmt"

type MyInt int

func (m MyInt) Double() int {
    return int(m) * 2
}

func (m *MyInt) Triple() int {
    *m *= 3
    return int(*m)
}

func main() {
    var x MyInt = 5

    // x 是 MyInt 类型,方法集只有 Double
    fmt.Println(x.Double()) // 10

    // &x 是 *MyInt 类型,方法集包含 Double 和 Triple
    fmt.Println((&x).Triple()) // 15

    // Go 编译器会自动转换:x.Triple() 等价于 (&x).Triple()
    fmt.Println(x.Triple()) // 45
}

注意:在普通代码中,Go 编译器会自动进行值和指针的转换。方法集的真正影响体现在接口实现上——如果一个接口的方法包含指针接收者方法,那么只有 *T 类型才能满足该接口。

为自定义类型添加方法

Go 不允许为其他包定义的类型添加方法(类似其他语言的"猴子补丁"),但你可以定义自己的类型并为其添加方法:

package main

import (
    "fmt"
    "strings"
)

// 基于 string 定义自定义类型
type MyString string

// 为自定义类型添加方法
func (s MyString) Upper() MyString {
    return MyString(strings.ToUpper(string(s)))
}

func (s MyString) Repeat(n int) MyString {
    return MyString(strings.Repeat(string(s), n))
}

func main() {
    s := MyString("hello go")
    fmt.Println(s.Upper())        // HELLO GO
    fmt.Println(s.Repeat(3))      // hello gohello gohello go
}

方法的组合使用

多个方法可以组合调用,形成链式或流水线式的操作:

package main

import (
    "fmt"
    "math"
)

type Rectangle struct {
    Width  float64
    Height float64
}

func (r Rectangle) Area() float64 {
    return r.Width * r.Height
}

func (r *Rectangle) Scale(factor float64) {
    r.Width *= factor
    r.Height *= factor
}

func (r Rectangle) IsSquare() bool {
    return math.Abs(r.Width-r.Height) < 1e-9
}

func (r *Rectangle) Resize(width, height float64) {
    r.Width = width
    r.Height = height
}

func main() {
    rect := Rectangle{Width: 4, Height: 4}
    fmt.Println("是正方形?", rect.IsSquare()) // true
    fmt.Println("面积:", rect.Area())          // 16

    rect.Scale(3)
    fmt.Println("缩放后面积:", rect.Area())    // 144

    rect.Resize(10, 5)
    fmt.Println("调整后是正方形?", rect.IsSquare()) // false
    fmt.Println("最终面积:", rect.Area())            // 50
}

完整示例:Rectangle 综合演示

下面是一个完整的示例,综合展示前面介绍的知识点:

package main

import (
    "fmt"
    "math"
)

type Rectangle struct {
    Width  float64
    Height float64
}

// 值接收者 - 计算面积
func (r Rectangle) Area() float64 {
    return r.Width * r.Height
}

// 值接收者 - 计算周长
func (r Rectangle) Perimeter() float64 {
    return 2 * (r.Width + r.Height)
}

// 值接收者 - 判断是否为正方形
func (r Rectangle) IsSquare() bool {
    return math.Abs(r.Width-r.Height) < 1e-9
}

// 指针接收者 - 等比缩放
func (r *Rectangle) Scale(factor float64) {
    r.Width *= factor
    r.Height *= factor
}

// 指针接收者 - 重新设置尺寸
func (r *Rectangle) Resize(width, height float64) {
    r.Width = width
    r.Height = height
}

// 指针接收者 - 旋转(交换宽高)
func (r *Rectangle) Rotate() {
    r.Width, r.Height = r.Height, r.Width
}

// 值接收者 - 返回描述字符串
func (r Rectangle) String() string {
    return fmt.Sprintf("Rectangle(%.1f x %.1f)", r.Width, r.Height)
}

func main() {
    rect := Rectangle{Width: 10, Height: 5}

    fmt.Println("矩形:", rect)
    fmt.Println("面积:", rect.Area())
    fmt.Println("周长:", rect.Perimeter())
    fmt.Println("是正方形?", rect.IsSquare())

    rect.Scale(2)
    fmt.Println("\n缩放 2 倍后:", rect)
    fmt.Println("面积:", rect.Area())

    rect.Rotate()
    fmt.Println("\n旋转后:", rect)

    rect.Resize(8, 8)
    fmt.Println("\n调整为 8x8:", rect)
    fmt.Println("是正方形?", rect.IsSquare())
}

运行输出:

矩形: Rectangle(10.0 x 5.0)
面积: 50
周长: 30
是正方形? false

缩放 2 倍后: Rectangle(20.0 x 10.0)
面积: 200

旋转后: Rectangle(10.0 x 20.0)

调整为 8x8: Rectangle(8.0 x 8.0)
是正方形? true

总结

本篇介绍了 Go 语言方法的核心概念:方法通过接收者函数与类型绑定;值接收者操作副本,指针接收者操作原始值;方法集规则决定了类型能调用哪些方法,也是接口实现的基础。在实际开发中,建议遵循一致性原则,合理选择接收者类型,让代码既清晰又高效。