Go语言快速入门Go语言快速入门
首页
基础篇
进阶篇
高阶篇
实战篇
Go官方网站
编程指南
首页
基础篇
进阶篇
高阶篇
实战篇
Go官方网站
编程指南
  • 进阶篇

    • 🚀 进阶篇
    • 方法
    • 接口
    • 错误处理
    • Goroutine
    • Channel
    • 包管理
    • 单元测试

方法

方法是给类型添加行为的方式,相当于Java的类方法,但更灵活!我记得当年学Java的时候,方法必须定义在类里面,很死板。Go的方法可以在类型外部定义,这个设计让代码组织更灵活!

什么是方法?

方法就是带有接收者的函数。接收者就是方法作用于的类型。

// 普通函数
func Area(r Rectangle) float64 {
    return r.Width * r.Height
}

// 方法 - 有接收者
func (r Rectangle) Area() float64 {
    return r.Width * r.Height
}

Java程序员看过来

// Java - 方法定义在类内部
public class Rectangle {
    public double area() {
        return width * height;
    }
}
// Go - 方法定义在类型外部
type Rectangle struct {
    Width, Height float64
}

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

Go的方法可以分开定义,更灵活!

方法定义语法

func (接收者变量 接收者类型) 方法名(参数列表) 返回值 {
    // 方法体
}
type Person struct {
    Name string
    Age  int
}

// 定义方法
func (p Person) SayHello() {
    fmt.Printf("大家好,我是%s\n", p.Name)
}

func (p Person) GetInfo() string {
    return fmt.Sprintf("%s, %d岁", p.Name, p.Age)
}

// 使用
func main() {
    person := Person{Name: "张三", Age: 25}
    person.SayHello()              // 大家好,我是张三
    fmt.Println(person.GetInfo())  // 张三, 25岁
}

值接收者 vs 指针接收者

这是Go方法最重要的概念!

值接收者

接收者是值的副本,不能修改原数据。

type Counter struct {
    count int
}

// 值接收者
func (c Counter) Increment() {
    c.count++  // 只修改副本!
}

func main() {
    c := Counter{count: 0}
    c.Increment()
    fmt.Println(c.count)  // 0(没变!)
}

指针接收者

接收者是指针,可以修改原数据。

// 指针接收者
func (c *Counter) Increment() {
    c.count++  // 修改原数据
}

func main() {
    c := Counter{count: 0}
    c.Increment()
    fmt.Println(c.count)  // 1(变了!)
}

什么时候用哪个?

场景推荐原因
需要修改接收者*T 指针值接收者无法修改
结构体很大*T 指针避免复制开销
接收者包含sync.Mutex等*T 指针复制会导致锁失效
其他情况看情况小结构体值接收者也行

一致性原则

如果一个类型有指针接收者的方法,最好所有方法都用指针接收者。保持一致性!

Go的自动转换

Go会自动在值和指针之间转换:

type Person struct {
    Name string
}

func (p *Person) SetName(name string) {
    p.Name = name
}

func (p Person) GetName() string {
    return p.Name
}

func main() {
    // 值类型
    p1 := Person{Name: "张三"}
    p1.SetName("李四")  // Go自动转换为 (&p1).SetName("李四")
    fmt.Println(p1.GetName())  // 李四
    
    // 指针类型
    p2 := &Person{Name: "王五"}
    fmt.Println(p2.GetName())  // Go自动转换为 (*p2).GetName()
}

给任意类型添加方法

不只是结构体,任何自定义类型都可以有方法:

// 给int起别名
type MyInt int

func (m MyInt) IsPositive() bool {
    return m > 0
}

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

func main() {
    var n MyInt = 5
    fmt.Println(n.IsPositive())  // true
    fmt.Println(n.Double())      // 10
}
// 给切片类型添加方法
type StringSlice []string

func (s StringSlice) Join(sep string) string {
    return strings.Join(s, sep)
}

func (s StringSlice) Filter(fn func(string) bool) StringSlice {
    result := StringSlice{}
    for _, v := range s {
        if fn(v) {
            result = append(result, v)
        }
    }
    return result
}

func main() {
    names := StringSlice{"Alice", "Bob", "Charlie"}
    fmt.Println(names.Join(", "))  // Alice, Bob, Charlie
    
    // 过滤以A开头的
    filtered := names.Filter(func(s string) bool {
        return strings.HasPrefix(s, "A")
    })
    fmt.Println(filtered)  // [Alice]
}

方法表达式和方法值

方法值

将方法绑定到特定实例:

type Calculator struct {
    value float64
}

func (c *Calculator) Add(n float64) {
    c.value += n
}

func (c Calculator) GetValue() float64 {
    return c.value
}

func main() {
    calc := &Calculator{value: 10}
    
    // 方法值:绑定到calc实例
    addFn := calc.Add
    getFn := calc.GetValue
    
    addFn(5)
    fmt.Println(getFn())  // 15
}

方法表达式

将方法转换为普通函数:

type Person struct {
    Name string
}

func (p Person) Greet() string {
    return "Hello, " + p.Name
}

func main() {
    // 方法表达式:第一个参数是接收者
    greetFn := Person.Greet
    
    p := Person{Name: "张三"}
    fmt.Println(greetFn(p))  // Hello, 张三
}

嵌入类型的方法

嵌入的类型的方法会被"继承":

type Animal struct {
    Name string
}

func (a Animal) Eat() {
    fmt.Printf("%s is eating\n", a.Name)
}

type Dog struct {
    Animal        // 嵌入Animal
    Breed  string
}

func (d Dog) Bark() {
    fmt.Printf("%s says: 汪汪!\n", d.Name)
}

func main() {
    dog := Dog{
        Animal: Animal{Name: "旺财"},
        Breed:  "柴犬",
    }
    
    dog.Eat()   // 旺财 is eating(继承的方法)
    dog.Bark()  // 旺财 says: 汪汪!
}

方法覆盖

type Animal struct {
    Name string
}

func (a Animal) Speak() {
    fmt.Println("...")
}

type Cat struct {
    Animal
}

// 覆盖Animal的Speak方法
func (c Cat) Speak() {
    fmt.Println("喵喵!")
}

func main() {
    cat := Cat{Animal: Animal{Name: "咪咪"}}
    cat.Speak()         // 喵喵!(调用Cat的方法)
    cat.Animal.Speak()  // ...(调用Animal的方法)
}

实战案例:银行账户

package main

import (
    "errors"
    "fmt"
    "time"
)

// 交易记录
type Transaction struct {
    Type      string    // "deposit" 或 "withdraw"
    Amount    float64
    Balance   float64
    Timestamp time.Time
}

// 银行账户
type BankAccount struct {
    accountNo    string
    owner        string
    balance      float64
    transactions []Transaction
}

// 创建账户
func NewBankAccount(accountNo, owner string, initialDeposit float64) *BankAccount {
    account := &BankAccount{
        accountNo:    accountNo,
        owner:        owner,
        balance:      0,
        transactions: make([]Transaction, 0),
    }
    if initialDeposit > 0 {
        account.Deposit(initialDeposit)
    }
    return account
}

// 存款
func (a *BankAccount) Deposit(amount float64) error {
    if amount <= 0 {
        return errors.New("存款金额必须大于0")
    }
    
    a.balance += amount
    a.transactions = append(a.transactions, Transaction{
        Type:      "deposit",
        Amount:    amount,
        Balance:   a.balance,
        Timestamp: time.Now(),
    })
    return nil
}

// 取款
func (a *BankAccount) Withdraw(amount float64) error {
    if amount <= 0 {
        return errors.New("取款金额必须大于0")
    }
    if amount > a.balance {
        return errors.New("余额不足")
    }
    
    a.balance -= amount
    a.transactions = append(a.transactions, Transaction{
        Type:      "withdraw",
        Amount:    amount,
        Balance:   a.balance,
        Timestamp: time.Now(),
    })
    return nil
}

// 查询余额
func (a *BankAccount) GetBalance() float64 {
    return a.balance
}

// 获取账户信息
func (a *BankAccount) GetInfo() string {
    return fmt.Sprintf("账号: %s, 户主: %s, 余额: ¥%.2f", 
        a.accountNo, a.owner, a.balance)
}

// 打印交易记录
func (a *BankAccount) PrintTransactions() {
    fmt.Printf("📋 %s 的交易记录\n", a.owner)
    fmt.Println("────────────────────────────────────────")
    for i, t := range a.transactions {
        typeStr := "存入"
        if t.Type == "withdraw" {
            typeStr = "取出"
        }
        fmt.Printf("%d. %s ¥%.2f  余额: ¥%.2f  时间: %s\n",
            i+1, typeStr, t.Amount, t.Balance, 
            t.Timestamp.Format("2006-01-02 15:04:05"))
    }
}

// 转账
func (a *BankAccount) Transfer(target *BankAccount, amount float64) error {
    if err := a.Withdraw(amount); err != nil {
        return fmt.Errorf("转账失败: %v", err)
    }
    if err := target.Deposit(amount); err != nil {
        // 回滚
        a.Deposit(amount)
        return fmt.Errorf("转账失败: %v", err)
    }
    return nil
}

func main() {
    // 创建账户
    alice := NewBankAccount("1001", "Alice", 1000)
    bob := NewBankAccount("1002", "Bob", 500)
    
    // 操作
    alice.Deposit(500)
    alice.Withdraw(200)
    
    // 转账
    alice.Transfer(bob, 300)
    
    // 打印信息
    fmt.Println(alice.GetInfo())
    fmt.Println(bob.GetInfo())
    
    fmt.Println()
    alice.PrintTransactions()
}

输出:

账号: 1001, 户主: Alice, 余额: ¥1000.00
账号: 1002, 户主: Bob, 余额: ¥800.00

📋 Alice 的交易记录
────────────────────────────────────────
1. 存入 ¥1000.00  余额: ¥1000.00  时间: 2024-01-15 10:30:00
2. 存入 ¥500.00  余额: ¥1500.00  时间: 2024-01-15 10:30:01
3. 取出 ¥200.00  余额: ¥1300.00  时间: 2024-01-15 10:30:01
4. 取出 ¥300.00  余额: ¥1000.00  时间: 2024-01-15 10:30:01

实战案例:游戏角色系统

package main

import (
    "fmt"
    "math/rand"
)

// 角色属性
type Character struct {
    Name   string
    HP     int
    MaxHP  int
    Attack int
    Defense int
    Level  int
    Exp    int
}

// 创建新角色
func NewCharacter(name string) *Character {
    return &Character{
        Name:    name,
        HP:      100,
        MaxHP:   100,
        Attack:  10,
        Defense: 5,
        Level:   1,
        Exp:     0,
    }
}

// 是否存活
func (c *Character) IsAlive() bool {
    return c.HP > 0
}

// 受到伤害
func (c *Character) TakeDamage(damage int) {
    actualDamage := damage - c.Defense
    if actualDamage < 1 {
        actualDamage = 1
    }
    c.HP -= actualDamage
    if c.HP < 0 {
        c.HP = 0
    }
    fmt.Printf("💥 %s 受到 %d 点伤害,剩余HP: %d\n", c.Name, actualDamage, c.HP)
}

// 攻击目标
func (c *Character) AttackTarget(target *Character) {
    if !c.IsAlive() {
        fmt.Printf("❌ %s 已经死亡,无法攻击\n", c.Name)
        return
    }
    
    // 伤害浮动 80%-120%
    damage := c.Attack * (80 + rand.Intn(41)) / 100
    fmt.Printf("⚔️ %s 攻击 %s\n", c.Name, target.Name)
    target.TakeDamage(damage)
}

// 恢复HP
func (c *Character) Heal(amount int) {
    c.HP += amount
    if c.HP > c.MaxHP {
        c.HP = c.MaxHP
    }
    fmt.Printf("💚 %s 恢复了 %d HP,当前HP: %d/%d\n", c.Name, amount, c.HP, c.MaxHP)
}

// 获得经验
func (c *Character) GainExp(exp int) {
    c.Exp += exp
    fmt.Printf("✨ %s 获得 %d 经验值\n", c.Name, exp)
    
    // 检查升级(每100经验升一级)
    for c.Exp >= 100 {
        c.LevelUp()
        c.Exp -= 100
    }
}

// 升级
func (c *Character) LevelUp() {
    c.Level++
    c.MaxHP += 20
    c.HP = c.MaxHP  // 升级回满血
    c.Attack += 5
    c.Defense += 2
    fmt.Printf("🎉 %s 升级了!当前等级: %d\n", c.Name, c.Level)
    fmt.Printf("   属性提升: HP+20, 攻击+5, 防御+2\n")
}

// 状态信息
func (c *Character) Status() string {
    return fmt.Sprintf("[%s] Lv.%d HP:%d/%d ATK:%d DEF:%d EXP:%d/100",
        c.Name, c.Level, c.HP, c.MaxHP, c.Attack, c.Defense, c.Exp)
}

// 战斗(回合制)
func Battle(c1, c2 *Character) *Character {
    fmt.Println("════════════════════════════════════")
    fmt.Printf("⚔️ 战斗开始: %s vs %s\n", c1.Name, c2.Name)
    fmt.Println("════════════════════════════════════")
    
    round := 1
    for c1.IsAlive() && c2.IsAlive() {
        fmt.Printf("\n【第 %d 回合】\n", round)
        
        // c1先攻击
        c1.AttackTarget(c2)
        if !c2.IsAlive() {
            break
        }
        
        // c2反击
        c2.AttackTarget(c1)
        round++
    }
    
    var winner *Character
    if c1.IsAlive() {
        winner = c1
    } else {
        winner = c2
    }
    
    fmt.Println("\n════════════════════════════════════")
    fmt.Printf("🏆 %s 获胜!\n", winner.Name)
    fmt.Println("════════════════════════════════════")
    
    return winner
}

func main() {
    // 创建角色
    hero := NewCharacter("勇者")
    monster := NewCharacter("哥布林")
    monster.Attack = 8
    monster.Defense = 3
    
    fmt.Println("📊 初始状态:")
    fmt.Println(hero.Status())
    fmt.Println(monster.Status())
    
    // 战斗
    winner := Battle(hero, monster)
    
    // 战斗后
    fmt.Println("\n📊 战斗结束:")
    fmt.Println(hero.Status())
    fmt.Println(monster.Status())
    
    // 获胜者获得经验
    if winner == hero {
        hero.GainExp(80)
        hero.Heal(30)
        
        fmt.Println("\n📊 最终状态:")
        fmt.Println(hero.Status())
    }
}

练习

  1. 创建一个 Stack 结构体,实现 Push、Pop、IsEmpty、Size 方法
  2. 创建一个 Rectangle 结构体,实现 Area、Perimeter、Scale 方法
  3. 创建一个 Queue 结构体,实现队列的基本操作
参考答案
package main

import (
    "errors"
    "fmt"
)

// 1. Stack 栈
type Stack struct {
    items []interface{}
}

func NewStack() *Stack {
    return &Stack{items: make([]interface{}, 0)}
}

func (s *Stack) Push(item interface{}) {
    s.items = append(s.items, item)
}

func (s *Stack) Pop() (interface{}, error) {
    if s.IsEmpty() {
        return nil, errors.New("栈为空")
    }
    item := s.items[len(s.items)-1]
    s.items = s.items[:len(s.items)-1]
    return item, nil
}

func (s *Stack) IsEmpty() bool {
    return len(s.items) == 0
}

func (s *Stack) Size() int {
    return len(s.items)
}

// 2. Rectangle 矩形
type Rectangle struct {
    Width, 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) Scale(factor float64) {
    r.Width *= factor
    r.Height *= factor
}

// 3. Queue 队列
type Queue struct {
    items []interface{}
}

func NewQueue() *Queue {
    return &Queue{items: make([]interface{}, 0)}
}

func (q *Queue) Enqueue(item interface{}) {
    q.items = append(q.items, item)
}

func (q *Queue) Dequeue() (interface{}, error) {
    if q.IsEmpty() {
        return nil, errors.New("队列为空")
    }
    item := q.items[0]
    q.items = q.items[1:]
    return item, nil
}

func (q *Queue) IsEmpty() bool {
    return len(q.items) == 0
}

func (q *Queue) Size() int {
    return len(q.items)
}

func main() {
    // 测试Stack
    stack := NewStack()
    stack.Push(1)
    stack.Push(2)
    stack.Push(3)
    fmt.Printf("栈大小: %d\n", stack.Size())
    for !stack.IsEmpty() {
        item, _ := stack.Pop()
        fmt.Printf("弹出: %v\n", item)
    }
    
    // 测试Rectangle
    rect := Rectangle{10, 5}
    fmt.Printf("面积: %.2f, 周长: %.2f\n", rect.Area(), rect.Perimeter())
    rect.Scale(2)
    fmt.Printf("放大2倍后 - 面积: %.2f\n", rect.Area())
    
    // 测试Queue
    queue := NewQueue()
    queue.Enqueue("first")
    queue.Enqueue("second")
    queue.Enqueue("third")
    for !queue.IsEmpty() {
        item, _ := queue.Dequeue()
        fmt.Printf("出队: %v\n", item)
    }
}

方法是Go面向对象的基础,下一节学习接口——Go的多态魔法!

最近更新: 2025/12/27 13:26
Contributors: 王长安
Prev
🚀 进阶篇
Next
接口