Skip to content

methods.go

文件信息

  • 📄 原文件:05_methods.go
  • 🔤 语言:go

完整代码

go
package main

import (
	"fmt"
	"math"
)

// ============================================================
//                      方法(Methods)
// ============================================================
// 方法是绑定到特定类型的函数
// 语法: func (接收者 类型) 方法名(参数) 返回类型 { }
//
// 【与函数的区别】
// - 函数: func 函数名(参数) 返回类型
// - 方法: func (接收者 类型) 方法名(参数) 返回类型
//
// 【接收者】类似于其他语言的 this 或 self

func main05() {
	fmt.Println("\n==================== 05_methods ====================")
	fmt.Println("=== 方法基础 ===")

	// ----------------------------------------------------------
	// 值接收者方法
	// ----------------------------------------------------------
	r := Rectangle{Width: 10, Height: 5}

	// 调用方法:实例.方法名()
	fmt.Printf("矩形: %+v\n", r)
	fmt.Println("面积:", r.Area())
	fmt.Println("周长:", r.Perimeter())

	// ----------------------------------------------------------
	// 指针接收者方法
	// ----------------------------------------------------------
	fmt.Println("\n=== 指针接收者 ===")

	fmt.Printf("缩放前: %+v\n", r)
	r.Scale(2) // 修改原始值
	fmt.Printf("缩放后: %+v\n", r)

	// 【重要】Go 自动处理指针/值转换
	// r.Scale(2) 等价于 (&r).Scale(2)

	// ----------------------------------------------------------
	// 值接收者 vs 指针接收者
	// ----------------------------------------------------------
	fmt.Println("\n=== 值 vs 指针接收者对比 ===")

	c := Counter{count: 0}

	c.IncrementValue() // 值接收者,不会修改原值
	fmt.Println("IncrementValue 后:", c.count) // 0

	c.IncrementPointer() // 指针接收者,会修改原值
	fmt.Println("IncrementPointer 后:", c.count) // 1

	// ----------------------------------------------------------
	// 方法链
	// ----------------------------------------------------------
	fmt.Println("\n=== 方法链 ===")

	builder := &StringBuilder{}
	result := builder.
		Append("Hello").
		Append(", ").
		Append("World").
		Append("!").
		String()

	fmt.Println("方法链结果:", result)

	// ----------------------------------------------------------
	// 自定义类型的方法
	// ----------------------------------------------------------
	fmt.Println("\n=== 自定义类型方法 ===")

	var m MyInt = 10
	fmt.Println("MyInt:", m)
	fmt.Println("Double:", m.Double())
	fmt.Println("IsPositive:", m.IsPositive())

	var m2 MyInt = -5
	fmt.Println("MyInt:", m2, "IsPositive:", m2.IsPositive())

	// ----------------------------------------------------------
	// 切片类型的方法
	// ----------------------------------------------------------
	fmt.Println("\n=== 切片类型方法 ===")

	nums := IntSlice{3, 1, 4, 1, 5, 9, 2, 6}
	fmt.Println("原切片:", nums)
	fmt.Println("Sum:", nums.Sum())
	fmt.Println("Max:", nums.Max())
	fmt.Println("Min:", nums.Min())

	// ----------------------------------------------------------
	// 嵌入类型与方法提升
	// ----------------------------------------------------------
	fmt.Println("\n=== 嵌入类型(方法提升)===")

	e := Employee{
		Person: Person{Name: "张三", Age: 30},
		Title:  "工程师",
		Salary: 10000,
	}

	// 可以直接调用嵌入类型的方法
	e.Greet()               // Person 的方法
	fmt.Println(e.Describe()) // Employee 的方法

	// 也可以访问嵌入类型的字段
	fmt.Println("姓名:", e.Name) // 等价于 e.Person.Name

	// ----------------------------------------------------------
	// 方法覆盖
	// ----------------------------------------------------------
	fmt.Println("\n=== 方法覆盖 ===")

	admin := Admin{
		Employee: Employee{
			Person: Person{Name: "李四", Age: 35},
			Title:  "管理员",
			Salary: 20000,
		},
		Level: 5,
	}

	admin.Greet() // Admin 覆盖了 Greet 方法

	// 仍可以调用被覆盖的方法
	admin.Person.Greet()   // 调用 Person 的 Greet
	admin.Employee.Greet() // 调用 Person 的 Greet(通过 Employee)

	// ----------------------------------------------------------
	// 方法值与方法表达式
	// ----------------------------------------------------------
	fmt.Println("\n=== 方法值与方法表达式 ===")

	rect := Rectangle{Width: 5, Height: 3}

	// 方法值:绑定到特定实例
	areaFunc := rect.Area
	fmt.Println("方法值 areaFunc():", areaFunc())

	// 方法表达式:需要传递接收者
	areaExpr := Rectangle.Area
	fmt.Println("方法表达式 areaExpr(rect):", areaExpr(rect))

	// 指针方法表达式
	scaleExpr := (*Rectangle).Scale
	scaleExpr(&rect, 2)
	fmt.Printf("方法表达式 Scale 后: %+v\n", rect)

	// ----------------------------------------------------------
	// nil 接收者
	// ----------------------------------------------------------
	fmt.Println("\n=== nil 接收者 ===")

	var list *IntList = nil
	fmt.Println("nil 列表长度:", list.Len()) // 可以安全调用

	list = &IntList{data: []int{1, 2, 3}}
	fmt.Println("实际列表长度:", list.Len())
}

// ============================================================
//                      类型与方法定义
// ============================================================

// ----------------------------------------------------------
// 基本结构体与方法
// ----------------------------------------------------------

// Rectangle 矩形结构体
type Rectangle struct {
	Width  float64
	Height float64
}

// Area 计算面积(值接收者)
// 【值接收者】方法内部是副本,不会修改原值
// 【适用场景】
// - 不需要修改接收者
// - 接收者是小型结构体(复制成本低)
// - 需要值语义(如 time.Time)
func (r Rectangle) Area() float64 {
	return r.Width * r.Height
}

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

// Scale 缩放矩形(指针接收者)
// 【指针接收者】方法内部操作原始值
// 【适用场景】
// - 需要修改接收者
// - 接收者是大型结构体(避免复制)
// - 需要一致性(如果有一个方法需要指针,全部用指针)
func (r *Rectangle) Scale(factor float64) {
	r.Width *= factor
	r.Height *= factor
}

// ----------------------------------------------------------
// 值接收者 vs 指针接收者对比
// ----------------------------------------------------------

type Counter struct {
	count int
}

// IncrementValue 值接收者 - 不会修改原值
func (c Counter) IncrementValue() {
	c.count++ // 修改的是副本
}

// IncrementPointer 指针接收者 - 会修改原值
func (c *Counter) IncrementPointer() {
	c.count++ // 修改的是原值
}

// ----------------------------------------------------------
// 方法链(Fluent Interface)
// ----------------------------------------------------------

type StringBuilder struct {
	data []byte
}

// Append 追加字符串,返回指针以支持链式调用
func (sb *StringBuilder) Append(s string) *StringBuilder {
	sb.data = append(sb.data, s...)
	return sb // 返回自身指针
}

// String 获取结果
func (sb *StringBuilder) String() string {
	return string(sb.data)
}

// ----------------------------------------------------------
// 自定义类型的方法
// ----------------------------------------------------------
// 【重要】只能为当前包内定义的类型添加方法
// 不能为内置类型(int, string)或其他包的类型添加方法

// MyInt 自定义整数类型
type MyInt int

// Double 返回两倍值
func (m MyInt) Double() MyInt {
	return m * 2
}

// IsPositive 是否为正数
func (m MyInt) IsPositive() bool {
	return m > 0
}

// ----------------------------------------------------------
// 切片类型的方法
// ----------------------------------------------------------

type IntSlice []int

// Sum 求和
func (s IntSlice) Sum() int {
	total := 0
	for _, v := range s {
		total += v
	}
	return total
}

// Max 最大值
func (s IntSlice) Max() int {
	if len(s) == 0 {
		return 0
	}
	max := s[0]
	for _, v := range s[1:] {
		if v > max {
			max = v
		}
	}
	return max
}

// Min 最小值
func (s IntSlice) Min() int {
	if len(s) == 0 {
		return 0
	}
	min := s[0]
	for _, v := range s[1:] {
		if v < min {
			min = v
		}
	}
	return min
}

// ----------------------------------------------------------
// 嵌入类型与方法提升
// ----------------------------------------------------------

// Person 人员基类
type Person struct {
	Name string
	Age  int
}

// Greet 打招呼
func (p Person) Greet() {
	fmt.Printf("你好,我是%s,今年%d\n", p.Name, p.Age)
}

// Employee 员工(嵌入 Person)
type Employee struct {
	Person        // 嵌入类型(匿名字段)
	Title  string
	Salary float64
}

// Describe 描述员工
func (e Employee) Describe() string {
	return fmt.Sprintf("%s - %s (%.0f元/月)", e.Name, e.Title, e.Salary)
}

// Admin 管理员(嵌入 Employee)
type Admin struct {
	Employee
	Level int
}

// Greet 覆盖 Person 的 Greet 方法
func (a Admin) Greet() {
	fmt.Printf("你好,我是%d级管理员%s\n", a.Level, a.Name)
}

// ----------------------------------------------------------
// 支持 nil 接收者的方法
// ----------------------------------------------------------

type IntList struct {
	data []int
}

// Len 返回列表长度(安全处理 nil)
// 【技巧】方法可以安全处理 nil 接收者
func (l *IntList) Len() int {
	if l == nil {
		return 0
	}
	return len(l.data)
}

// ----------------------------------------------------------
// 几何形状示例(用于展示多态,将在接口章节详细介绍)
// ----------------------------------------------------------

type Circle struct {
	Radius float64
}

func (c Circle) Area() float64 {
	return math.Pi * c.Radius * c.Radius
}

func (c Circle) Perimeter() float64 {
	return 2 * math.Pi * c.Radius
}

// ============================================================
//                      重要注意事项
// ============================================================
//
// 1. 【值接收者 vs 指针接收者选择】
//    使用指针接收者的情况:
//    - 需要修改接收者的值
//    - 接收者是大型结构体
//    - 一致性:类型的其他方法使用指针接收者
//
//    使用值接收者的情况:
//    - 不需要修改接收者
//    - 接收者是小型结构体或基本类型
//    - 需要值语义(不可变性)
//
// 2. 【自动转换】
//    Go 会自动在值和指针之间转换来调用方法
//    r.Scale(2)  // r 是值,自动转为 (&r).Scale(2)
//    p.Area()    // p 是指针,自动转为 (*p).Area()
//
// 3. 【只能为本包类型添加方法】
//    不能为 int、string 等内置类型添加方法
//    解决方案:定义新类型 type MyInt int
//
// 4. 【方法集规则】
//    - 类型 T 的方法集:所有接收者为 T 的方法
//    - 类型 *T 的方法集:所有接收者为 T 或 *T 的方法
//    这在实现接口时很重要
//
// 5. 【嵌入不是继承】
//    Go 的嵌入是组合,不是继承
//    - 没有 super 关键字
//    - 被嵌入类型不知道外部类型
//    - 方法提升是语法糖
//
// 6. 【命名冲突】
//    如果嵌入类型和外部类型有同名方法/字段
//    外部类型的优先级更高(遮蔽)

💬 讨论

使用 GitHub 账号登录后即可参与讨论

基于 MIT 许可发布