强曰为道
与天地相似,故不违。知周乎万物,而道济天下,故不过。旁行而不流,乐天知命,故不忧.
文档目录

Go 语言完全指南 / 10 - 结构体:字段、方法、嵌入、组合

10 - 结构体

10.1 结构体基础

package main

import "fmt"

// 声明结构体
type User struct {
    ID       int
    Name     string
    Email    string
    Age      int
    IsActive bool
}

func main() {
    // 创建方式一:字段名赋值
    u1 := User{
        ID:       1,
        Name:     "Alice",
        Email:    "[email protected]",
        Age:      30,
        IsActive: true,
    }

    // 创建方式二:按顺序赋值(不推荐,字段顺序变化会出错)
    u2 := User{2, "Bob", "[email protected]", 25, true}

    // 创建方式三:部分字段(其余为零值)
    u3 := User{Name: "Carol", Email: "[email protected]"}

    // 创建方式四:new(返回指针)
    u4 := new(User)
    u4.Name = "Dave"

    // 创建方式五:&User{...}
    u5 := &User{Name: "Eve"}

    fmt.Printf("%+v\n", u1)
    fmt.Printf("%+v\n", u2)
    fmt.Printf("%+v\n", u3)
    fmt.Printf("%+v\n", u4)
    fmt.Printf("%+v\n", u5)
}

10.2 字段标签(Tag)

package main

import (
    "encoding/json"
    "fmt"
    "reflect"
)

type User struct {
    ID       int    `json:"id" db:"id" validate:"required"`
    Name     string `json:"name" db:"name" validate:"required,min=2"`
    Email    string `json:"email" db:"email" validate:"required,email"`
    Password string `json:"-" db:"password"` // json:"-" 表示忽略
    Age      int    `json:"age,omitempty"`    // 零值时忽略
}

func main() {
    u := User{
        ID:       1,
        Name:     "Alice",
        Email:    "[email protected]",
        Password: "secret",
        Age:      0, // 零值会被忽略
    }

    // JSON 序列化
    data, _ := json.MarshalIndent(u, "", "  ")
    fmt.Println(string(data))
    // {"id":1,"name":"Alice","email":"[email protected]"}

    // 反射读取 tag
    t := reflect.TypeOf(u)
    for i := 0; i < t.NumField(); i++ {
        field := t.Field(i)
        fmt.Printf("%s: json=%s, db=%s\n",
            field.Name,
            field.Tag.Get("json"),
            field.Tag.Get("db"),
        )
    }
}

10.3 结构体方法

package main

import (
    "fmt"
    "math"
)

type Point struct {
    X, Y float64
}

// 值接收者(不修改原值)
func (p Point) Distance(q Point) float64 {
    return math.Hypot(q.X-p.X, q.Y-p.Y)
}

func (p Point) String() string {
    return fmt.Sprintf("(%.1f, %.1f)", p.X, p.Y)
}

// 指针接收者(可以修改原值)
func (p *Point) Translate(dx, dy float64) {
    p.X += dx
    p.Y += dy
}

func (p *Point) Scale(factor float64) {
    p.X *= factor
    p.Y *= factor
}

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
}

func main() {
    p1 := Point{0, 0}
    p2 := Point{3, 4}
    fmt.Printf("距离: %.1f\n", p1.Distance(p2)) // 5.0

    p1.Translate(10, 20)
    fmt.Println("平移后:", p1) // (10.0, 20.0)

    rect := Rectangle{Width: 10, Height: 5}
    fmt.Printf("面积: %.1f, 周长: %.1f\n", rect.Area(), rect.Perimeter())
    rect.Scale(2)
    fmt.Printf("缩放后: 面积=%.1f\n", rect.Area())
}

值接收者 vs 指针接收者

特性值接收者 func (T)指针接收者 func (*T)
修改接收者❌ 不能✅ 可以
调用者类型T 或 *T 都可调用T 或 *T 都可调用
并发安全每次调用独立副本共享数据,需加锁
性能大结构体有拷贝开销零拷贝

💡 选择规则

  • 需要修改接收者 → 指针接收者
  • 结构体很大 → 指针接收者
  • 结构体包含 sync.Mutex → 指针接收者
  • 一致性:同一类型的所有方法用相同接收者类型

10.4 结构体嵌入(Embedding)

Go 通过嵌入实现组合,而非继承。

package main

import "fmt"

type Address struct {
    City    string
    Country string
}

func (a Address) FullAddress() string {
    return a.City + ", " + a.Country
}

type Company struct {
    Name    string
    Address // 嵌入(匿名字段)
}

type Employee struct {
    Name    string
    Salary  float64
    Address // 嵌入
    Company // 嵌入
}

func main() {
    e := Employee{
        Name:   "Alice",
        Salary: 100000,
        Address: Address{
            City:    "Beijing",
            Country: "China",
        },
        Company: Company{
            Name: "TechCorp",
            Address: Address{
                City:    "Shanghai",
                Country: "China",
            },
        },
    }

    // 直接访问嵌入字段
    fmt.Println(e.City) // Beijing(提升的字段)

    // 显式访问
    fmt.Println(e.Address.City)  // Beijing
    fmt.Println(e.Company.City)  // Shanghai

    // 嵌入的方法也被提升
    fmt.Println(e.Address.FullAddress()) // Beijing, China
}

嵌入接口

type Reader interface {
    Read(p []byte) (n int, err error)
}

type Writer interface {
    Write(p []byte) (n int, err error)
}

// 嵌入接口组合成新接口
type ReadWriter interface {
    Reader
    Writer
}

// 结构体嵌入接口
type Logger struct {
    io.Writer // 嵌入接口
    prefix    string
}

func (l *Logger) Log(msg string) {
    l.Write([]byte(l.prefix + msg + "\n"))
}

10.5 构造函数模式

Go 没有构造函数关键字,用 New 函数模拟:

package main

import "errors"

type Config struct {
    Host     string
    Port     int
    MaxConns int
    Debug    bool
}

// Option 函数模式
type Option func(*Config)

func WithPort(port int) Option {
    return func(c *Config) {
        c.Port = port
    }
}

func WithMaxConns(n int) Option {
    return func(c *Config) {
        c.MaxConns = n
    }
}

func WithDebug(debug bool) Option {
    return func(c *Config) {
        c.Debug = debug
    }
}

// NewConfig 使用 Option 模式
func NewConfig(host string, opts ...Option) (*Config, error) {
    if host == "" {
        return nil, errors.New("host is required")
    }

    // 默认值
    c := &Config{
        Host:     host,
        Port:     8080,
        MaxConns: 100,
        Debug:    false,
    }

    for _, opt := range opts {
        opt(c)
    }

    return c, nil
}

func main() {
    cfg, err := NewConfig("localhost",
        WithPort(3000),
        WithMaxConns(200),
        WithDebug(true),
    )
    if err != nil {
        panic(err)
    }
    fmt.Printf("%+v\n", cfg)
}

10.6 结构体比较和复制

func main() {
    // 结构体比较(所有字段可比较时)
    type Point struct{ X, Y int }
    p1 := Point{1, 2}
    p2 := Point{1, 2}
    fmt.Println(p1 == p2) // true

    // 值复制
    p3 := p1
    p3.X = 100
    fmt.Println(p1) // {1 2}(不受影响)

    // 深拷贝(包含引用类型字段时)
    type Data struct {
        Name string
        Tags []string
    }
    d1 := Data{Name: "test", Tags: []string{"a", "b"}}
    d2 := d1
    d2.Tags[0] = "modified"
    fmt.Println(d1.Tags) // [modified b](受影响!slice 共享底层数组)

    // 深拷贝
    d3 := Data{
        Name: d1.Name,
        Tags: make([]string, len(d1.Tags)),
    }
    copy(d3.Tags, d1.Tags)
    d3.Tags[0] = "independent"
    fmt.Println(d1.Tags) // [modified b]
    fmt.Println(d3.Tags) // [independent b]
}

10.7 空结构体

func main() {
    // 空结构体不占内存
    var s struct{}
    fmt.Println(unsafe.Sizeof(s)) // 0

    // 用作集合(不关心值,只关心键存在性)
    set := make(map[string]struct{})
    set["a"] = struct{}{}
    set["b"] = struct{}{}
    if _, ok := set["a"]; ok {
        fmt.Println("a 存在")
    }

    // channel 仅作信号通知
    done := make(chan struct{})
    go func() {
        fmt.Println("工作完成")
        done <- struct{}{}
    }()
    <-done

    // 方法接收者(无状态)
    type Validator struct{}
    func (v Validator) Validate(s string) bool {
        return len(s) > 0
    }
}

10.8 结构体组合设计模式

// 装饰器模式
type Logger struct {
    next http.Handler
}

func (l *Logger) ServeHTTP(w http.ResponseWriter, r *http.Request) {
    start := time.Now()
    l.next.ServeHTTP(w, r)
    log.Printf("%s %s %v", r.Method, r.URL.Path, time.Since(start))
}

// 中间件链
type Chain struct {
    handlers []http.Handler
}

// 仓储模式
type UserRepository struct {
    db *sql.DB
}

func (r *UserRepository) FindByID(id int) (*User, error) {
    // 查询数据库
}

func (r *UserRepository) Save(user *User) error {
    // 保存到数据库
}

🏢 业务场景

  1. 数据模型:ORM 模型定义,JSON 序列化
  2. 配置管理:Option 模式构建灵活配置
  3. 中间件:嵌入 + 组合实现 HTTP 中间件链
  4. 领域对象:结构体方法封装业务逻辑
  5. 仓储模式:结构体封装数据访问

📖 扩展阅读