Go 结构体与方法

结构体定义

基本语法

package main
 
import "fmt"
 
// 定义结构体
type Person struct {
    Name string
    Age  int
    City string
}
 
// 嵌套结构体
type Address struct {
    Street string
    City   string
    Zip    string
}
 
type Employee struct {
    Person        // 匿名字段(嵌入)
    Address       // 匿名字段
    EmployeeID    int
    Department    string
}
 
func main() {
    // 创建结构体实例
    var p1 Person
    p1.Name = "Alice"
    p1.Age = 30
    p1.City = "Beijing"
    
    // 字面量创建
    p2 := Person{
        Name: "Bob",
        Age:  25,
        City: "Shanghai",
    }
    
    // 按顺序创建(不推荐,容易出错)
    p3 := Person{"Charlie", 35, "Guangzhou"}
    
    // 部分字段初始化
    p4 := Person{
        Name: "David",
        // Age 和 City 使用零值
    }
    
    fmt.Printf("p1: %+v\n", p1)
    fmt.Printf("p2: %+v\n", p2)
    fmt.Printf("p3: %+v\n", p3)
    fmt.Printf("p4: %+v\n", p4)
    
    // 使用嵌套结构体
    emp := Employee{
        Person: Person{
            Name: "Alice",
            Age:  30,
        },
        Address: Address{
            Street: "123 Main St",
            City:   "Beijing",
            Zip:    "100000",
        },
        EmployeeID: 1001,
        Department: "Engineering",
    }
    
    // 通过嵌入字段访问
    fmt.Printf("员工姓名: %s\n", emp.Name)        // 直接访问嵌入的 Person.Name
    fmt.Printf("员工城市: %s\n", emp.City)        // 直接访问嵌入的 Address.City
    fmt.Printf("员工ID: %d\n", emp.EmployeeID)
}

方法定义

值接收者方法

package main
 
import "fmt"
 
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) Scale(factor float64) Rectangle {
    return Rectangle{
        Width:  r.Width * factor,
        Height: r.Height * factor,
    }
}
 
func main() {
    rect := Rectangle{Width: 10, Height: 5}
    
    area := rect.Area()
    fmt.Printf("面积: %.2f\n", area)  // 50.00
    
    perimeter := rect.Perimeter()
    fmt.Printf("周长: %.2f\n", perimeter)  // 30.00
    
    // 值接收者方法不会修改原始值
    scaled := rect.Scale(2)
    fmt.Printf("原始: %+v\n", rect)      // {Width:10 Height:5}
    fmt.Printf("缩放后: %+v\n", scaled)   // {Width:20 Height:10}
}

指针接收者方法

package main
 
import "fmt"
 
type Point struct {
    X, Y int
}
 
// 指针接收者方法:可以修改接收者
func (p *Point) Move(dx, dy int) {
    p.X += dx
    p.Y += dy
}
 
// 指针接收者方法:设置坐标
func (p *Point) SetPosition(x, y int) {
    p.X = x
    p.Y = y
}
 
func main() {
    point := Point{X: 1, Y: 2}
    fmt.Printf("原始: %+v\n", point)  // {X:1 Y:2}
    
    point.Move(10, 10)
    fmt.Printf("移动后: %+v\n", point)  // {X:11 Y:12}
    
    point.SetPosition(100, 200)
    fmt.Printf("设置后: %+v\n", point)  // {X:100 Y:200}
}

值接收者 vs 指针接收者

package main
 
import "fmt"
 
type Counter struct {
    value int
}
 
// 值接收者:不会修改原始值
func (c Counter) IncrementByValue() {
    c.value++  // 只修改副本
}
 
// 指针接收者:会修改原始值
func (c *Counter) IncrementByPointer() {
    c.value++  // 修改原始值
}
 
// 选择原则:
// 1. 如果方法需要修改接收者,使用指针接收者
// 2. 如果结构体很大,使用指针接收者(避免复制)
// 3. 如果结构体包含 sync.Mutex 等字段,使用指针接收者
// 4. 其他情况,值接收者和指针接收者都可以,但建议保持一致
 
func main() {
    counter := Counter{value: 0}
    
    counter.IncrementByValue()
    fmt.Printf("值接收者: %d\n", counter.value)  // 0(未改变)
    
    counter.IncrementByPointer()
    fmt.Printf("指针接收者: %d\n", counter.value)  // 1(已改变)
}

方法集

package main
 
import "fmt"
 
type Person struct {
    Name string
    Age  int
}
 
// 值接收者方法
func (p Person) GetName() string {
    return p.Name
}
 
// 指针接收者方法
func (p *Person) SetAge(age int) {
    p.Age = age
}
 
func main() {
    // 值类型可以调用值接收者方法
    p1 := Person{Name: "Alice", Age: 30}
    fmt.Println(p1.GetName())  // "Alice"
    
    // 值类型也可以调用指针接收者方法(Go 自动转换)
    p1.SetAge(31)  // Go 自动转换为 (&p1).SetAge(31)
    fmt.Printf("年龄: %d\n", p1.Age)  // 31
    
    // 指针类型可以调用指针接收者方法
    p2 := &Person{Name: "Bob", Age: 25}
    p2.SetAge(26)
    fmt.Printf("年龄: %d\n", p2.Age)  // 26
    
    // 指针类型也可以调用值接收者方法(Go 自动解引用)
    fmt.Println(p2.GetName())  // Go 自动转换为 (*p2).GetName()
}

方法表达式和方法值

package main
 
import "fmt"
 
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 main() {
    rect := Rectangle{Width: 10, Height: 5}
    
    // 方法值:将方法绑定到特定实例
    areaFunc := rect.Area  // 类型:func() float64
    fmt.Println(areaFunc())  // 50
    
    // 方法表达式:将方法作为函数
    areaMethod := Rectangle.Area  // 类型:func(Rectangle) float64
    fmt.Println(areaMethod(rect))  // 50
    
    // 指针接收者的方法表达式
    scaleMethod := (*Rectangle).Scale  // 类型:func(*Rectangle, float64)
    scaleMethod(&rect, 2)
    fmt.Printf("缩放后: %+v\n", rect)  // {Width:20 Height:10}
}

嵌入和方法提升

package main
 
import "fmt"
 
type Engine struct {
    Power int
}
 
func (e *Engine) Start() {
    fmt.Println("引擎启动")
}
 
func (e *Engine) Stop() {
    fmt.Println("引擎停止")
}
 
type Car struct {
    Engine  // 嵌入 Engine(匿名字段)
    Brand   string
    Model   string
}
 
// Car 自动获得了 Engine 的方法
// 可以直接调用 car.Start() 和 car.Stop()
 
func main() {
    car := Car{
        Engine: Engine{Power: 200},
        Brand:  "Toyota",
        Model:  "Camry",
    }
    
    // 直接调用嵌入类型的方法
    car.Start()  // "引擎启动"
    car.Stop()   // "引擎停止"
    
    // 也可以显式调用
    car.Engine.Start()
    
    // 访问嵌入类型的字段
    fmt.Printf("功率: %d 马力\n", car.Power)  // 直接访问
    fmt.Printf("功率: %d 马力\n", car.Engine.Power)  // 显式访问
}

结构体标签(Tag)

package main
 
import (
    "encoding/json"
    "fmt"
)
 
type User struct {
    ID       int    `json:"id" db:"user_id"`           // 多个标签
    Username string `json:"username" db:"username"`
    Email    string `json:"email" db:"email"`
    Password string `json:"-" db:"password"`            // json:"-" 表示不序列化
    Age      int    `json:"age,omitempty" db:"age"`     // omitempty: 零值时不序列化
}
 
func main() {
    user := User{
        ID:       1,
        Username: "alice",
        Email:    "alice@example.com",
        Password: "secret",
        Age:      30,
    }
    
    // JSON 序列化(使用 json 标签)
    jsonData, err := json.Marshal(user)
    if err != nil {
        fmt.Printf("错误: %v\n", err)
        return
    }
    fmt.Println(string(jsonData))
    // 输出: {"id":1,"username":"alice","email":"alice@example.com","age":30}
    // 注意:Password 字段被忽略(json:"-")
    
    // JSON 反序列化
    jsonStr := `{"id":2,"username":"bob","email":"bob@example.com"}`
    var user2 User
    err = json.Unmarshal([]byte(jsonStr), &user2)
    if err != nil {
        fmt.Printf("错误: %v\n", err)
        return
    }
    fmt.Printf("用户: %+v\n", user2)
}

结构体比较

package main
 
import "fmt"
 
type Point struct {
    X, Y int
}
 
func main() {
    p1 := Point{X: 1, Y: 2}
    p2 := Point{X: 1, Y: 2}
    p3 := Point{X: 2, Y: 3}
    
    // 结构体可以比较(如果所有字段都是可比较的)
    fmt.Println(p1 == p2)  // true
    fmt.Println(p1 == p3)  // false
    
    // 如果结构体包含不可比较的字段(如切片、map),则不能比较
    type BadStruct struct {
        X int
        Y []int  // 切片不可比较
    }
    
    // b1 := BadStruct{X: 1, Y: []int{1, 2}}
    // b2 := BadStruct{X: 1, Y: []int{1, 2}}
    // fmt.Println(b1 == b2)  // 编译错误:不能比较
}

空结构体

package main
 
import "fmt"
 
// 空结构体:不占用内存空间
type Empty struct{}
 
// 用途 1:作为 map 的 value(实现 Set)
func setExample() {
    set := make(map[string]struct{})
    set["apple"] = struct{}{}
    set["banana"] = struct{}{}
    
    if _, exists := set["apple"]; exists {
        fmt.Println("apple 存在")
    }
}
 
// 用途 2:作为 channel 的信号
func signalExample() {
    done := make(chan struct{})
    
    go func() {
        // 做一些工作
        fmt.Println("工作完成")
        done <- struct{}{}  // 发送信号
    }()
    
    <-done  // 等待信号
    fmt.Println("收到信号")
}
 
// 用途 3:实现方法(不需要状态)
type Counter struct{}
 
func (c Counter) Count() int {
    return 0
}
 
func main() {
    setExample()
    signalExample()
}

最佳实践

package main
 
import "fmt"
 
// 1. 使用构造函数
type User struct {
    Name  string
    Email string
    Age   int
}
 
// NewUser 是构造函数(Go 的约定)
func NewUser(name, email string, age int) *User {
    return &User{
        Name:  name,
        Email: email,
        Age:   age,
    }
}
 
// 2. 使用指针接收者当需要修改状态时
type BankAccount struct {
    balance float64
}
 
func (b *BankAccount) Deposit(amount float64) {
    b.balance += amount
}
 
func (b *BankAccount) Withdraw(amount float64) error {
    if amount > b.balance {
        return fmt.Errorf("余额不足")
    }
    b.balance -= amount
    return nil
}
 
func (b *BankAccount) Balance() float64 {
    return b.balance
}
 
// 3. 使用值接收者当不需要修改状态时
type Point struct {
    X, Y int
}
 
func (p Point) DistanceTo(other Point) float64 {
    dx := float64(p.X - other.X)
    dy := float64(p.Y - other.Y)
    return dx*dx + dy*dy  // 简化版,实际应该开方
}
 
func main() {
    // 使用构造函数
    user := NewUser("Alice", "alice@example.com", 30)
    fmt.Printf("用户: %+v\n", user)
    
    // 使用银行账户
    account := &BankAccount{balance: 1000}
    account.Deposit(500)
    fmt.Printf("余额: %.2f\n", account.Balance())  // 1500
    
    err := account.Withdraw(2000)
    if err != nil {
        fmt.Printf("错误: %v\n", err)  // 余额不足
    }
}

总结

Go 结构体和方法的特点:

  1. 结构体:自定义类型,可以包含多个字段
  2. 方法:可以为类型定义方法(值接收者或指针接收者)
  3. 嵌入:通过嵌入实现组合,方法自动提升
  4. 标签:使用标签进行元数据标注(JSON、数据库等)
  5. 构造函数:使用 NewXxx 函数创建实例
  6. 选择原则:需要修改状态用指针接收者,否则用值接收者

掌握结构体和方法后,就可以实现面向对象的编程了!


go 结构体 方法 面向对象