方法
方法是给类型添加行为的方式,相当于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())
}
}
练习
- 创建一个
Stack结构体,实现Push、Pop、IsEmpty、Size方法 - 创建一个
Rectangle结构体,实现Area、Perimeter、Scale方法 - 创建一个
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的多态魔法!
