flyfei

方法(绑定一个已定义类型的函数、值接收者vs指针接收者)

1. 语法讲解

方法定义:

func (接收者 接收者类型) 方法名(参数列表) 返回值列表 {
    // 方法体
}

接收者类型:

2. 应用场景

真实业务场景:银行账户管理系统 在金融应用中,账户对象需要封装各种操作(存款、取款、查询余额),这些操作需要维护账户状态的一致性,使用方法可以很好地封装这些行为。

3. 编程实例

package main

import (
    "errors"
    "fmt"
)

type BankAccount struct {
    AccountNumber string
    AccountHolder string
    Balance       float64
    IsActive      bool
}

// 值接收者方法 - 查询操作,不修改状态
func (acc BankAccount) GetAccountInfo() string {
    status := "活跃"
    if !acc.IsActive {
        status = "冻结"
    }
    return fmt.Sprintf("账户: %s, 持有人: %s, 余额: ¥%.2f, 状态: %s",
        acc.AccountNumber, acc.AccountHolder, acc.Balance, status)
}

// 指针接收者方法 - 需要修改账户状态
func (acc *BankAccount) Deposit(amount float64) error {
    if !acc.IsActive {
        return errors.New("账户已被冻结,无法存款")
    }
    if amount <= 0 {
        return errors.New("存款金额必须大于0")
    }
    acc.Balance += amount
    return nil
}

// 指针接收者方法 - 需要修改账户状态
func (acc *BankAccount) Withdraw(amount float64) error {
    if !acc.IsActive {
        return errors.New("账户已被冻结,无法取款")
    }
    if amount <= 0 {
        return errors.New("取款金额必须大于0")
    }
    if amount > acc.Balance {
        return errors.New("余额不足")
    }
    acc.Balance -= amount
    return nil
}

// 指针接收者方法
func (acc *BankAccount) FreezeAccount() {
    acc.IsActive = false
}

// 指针接收者方法
func (acc *BankAccount) ActivateAccount() {
    acc.IsActive = true
}

func main() {
    // 创建账户
    account := BankAccount{
        AccountNumber: "6222021234567890",
        AccountHolder: "张三",
        Balance:       1000.00,
        IsActive:      true,
    }
    
    // 调用值接收者方法
    fmt.Println(account.GetAccountInfo())
    
    // 调用指针接收者方法 - 存款
    err := account.Deposit(500.0)
    if err != nil {
        fmt.Println("存款失败:", err)
    } else {
        fmt.Println("存款成功,当前余额:", account.Balance)
    }
    
    // 调用指针接收者方法 - 取款
    err = account.Withdraw(200.0)
    if err != nil {
        fmt.Println("取款失败:", err)
    } else {
        fmt.Println("取款成功,当前余额:", account.Balance)
    }
    
    // 尝试取款超过余额
    err = account.Withdraw(2000.0)
    if err != nil {
        fmt.Println("取款失败:", err)
    }
    
    // 冻结账户并尝试操作
    account.FreezeAccount()
    fmt.Println(account.GetAccountInfo())
    
    err = account.Deposit(100.0)
    if err != nil {
        fmt.Println("操作失败:", err)
    }
}

4. 其他用法

值接收者 vs 指针接收者的调用方式:

package main

import "fmt"

type Counter struct {
    value int
}

// 值接收者方法
func (c Counter) GetValue() int {
    return c.value
}

// 指针接收者方法
func (c *Counter) Increment() {
    c.value++
}

func main() {
    // 值类型变量
    counter1 := Counter{value: 10}
    
    // 指针类型变量
    counter2 := &Counter{value: 20}
    
    // 值类型可以调用值接收者和指针接收者方法
    fmt.Println("counter1初始值:", counter1.GetValue()) // 值接收者方法
    counter1.Increment() // 指针接收者方法,Go自动转换为(&counter1).Increment()
    fmt.Println("counter1增值后:", counter1.GetValue())
    
    // 指针类型也可以调用值接收者和指针接收者方法
    fmt.Println("counter2初始值:", counter2.GetValue()) // 值接收者方法,Go自动转换为(*counter2).GetValue()
    counter2.Increment() // 指针接收者方法
    fmt.Println("counter2增值后:", counter2.GetValue())
    
    // 方法集规则总结:
    // - 值类型变量可以调用值接收者方法,也可以调用指针接收者方法
    // - 指针类型变量可以调用值接收者方法,也可以调用指针接收者方法
}

选择值接收者还是指针接收者:

package main

import "fmt"

type Product struct {
    ID       int
    Name     string
    Price    float64
    Quantity int
}

// 值接收者:适用于不修改结构体状态的查询方法
func (p Product) GetDisplayInfo() string {
    return fmt.Sprintf("商品: %s, 价格: ¥%.2f", p.Name, p.Price)
}

// 值接收者:适用于返回新值的计算方法
func (p Product) CalculateTotalValue() float64 {
    return p.Price * float64(p.Quantity)
}

// 指针接收者:需要修改结构体状态
func (p *Product) ApplyDiscount(discount float64) {
    if discount > 0 && discount <= 1 {
        p.Price = p.Price * (1 - discount)
    }
}

// 指针接收者:需要修改结构体状态
func (p *Product) UpdateQuantity(newQuantity int) {
    if newQuantity >= 0 {
        p.Quantity = newQuantity
    }
}

// 指针接收者:大结构体时提高性能
func (p *Product) UpdateProduct(name string, price float64) {
    p.Name = name
    p.Price = price
}

func main() {
    product := Product{
        ID:       1,
        Name:     "笔记本电脑",
        Price:    5999.00,
        Quantity: 10,
    }
    
    fmt.Println(product.GetDisplayInfo())
    fmt.Printf("库存总价值: ¥%.2f\n", product.CalculateTotalValue())
    
    product.ApplyDiscount(0.1) // 9折
    fmt.Println("打折后:", product.GetDisplayInfo())
    
    product.UpdateQuantity(15)
    fmt.Printf("更新库存后总价值: ¥%.2f\n", product.CalculateTotalValue())
}

5. 课时总结