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