目录

Go 语言结构体

结构体(struct)是 Go 语言中自定义复合类型的核心机制,用于将多个不同类型的字段组合成一个整体。Go 没有类(class),但通过结构体和方法(method)的组合,可以实现面向对象编程的大部分特性。

结构体定义

使用 type ... struct 语法定义结构体。

package main

import "fmt"

// 定义一个 Person 结构体
type Person struct {
    Name    string
    Age     int
    Email   string
    Address string
}

func main() {
    // 声明结构体变量,字段为零值
    var p1 Person
    fmt.Println(p1) // {  0  }

    // 使用字面量初始化(按顺序赋值)
    p2 := Person{"张三", 25, "zhangsan@example.com", "北京"}
    fmt.Println(p2)

    // 使用字段名初始化(推荐,顺序无关)
    p3 := Person{
        Name:    "李四",
        Age:     30,
        Email:   "lisi@example.com",
        Address: "上海",
    }
    fmt.Println(p3)

    // 部分字段初始化,未指定的字段为零值
    p4 := Person{Name: "王五"}
    fmt.Println(p4) // {王五 0   }
}

字段初始化

使用 new 创建

new 返回结构体的指针,所有字段为零值。

package main

import "fmt"

type Person struct {
    Name string
    Age  int
}

func main() {
    p := new(Person)
    fmt.Printf("类型: %T\n", p) // *main.Person
    fmt.Println(p)               // &{ 0}

    // 通过指针访问字段(Go 自动解引用)
    p.Name = "张三"
    p.Age = 25
    fmt.Println(*p) // {张三 25}
}

使用取地址符 &

package main

import "fmt"

type Person struct {
    Name string
    Age  int
}

func main() {
    // 创建结构体并获取指针
    p := &Person{Name: "李四", Age: 30}
    fmt.Printf("类型: %T\n", p) // *main.Person
    fmt.Println(p.Name)          // 李四
}

结构体方法

结构体可以定义方法,方法接收者(receiver)可以是值类型或指针类型。

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}

    fmt.Printf("面积: %.2f\n", rect.Area()) // 面积: 50.00

    rect.Scale(2)
    fmt.Printf("缩放后: %.2f x %.2f\n", rect.Width, rect.Height) // 20.00 x 10.00
    fmt.Printf("新面积: %.2f\n", rect.Area()) // 新面积: 200.00

    // 指针也可以调用值接收者方法
    pRect := &Rectangle{Width: 3, Height: 4}
    fmt.Printf("面积: %.2f\n", pRect.Area()) // 面积: 12.00
}

值接收者 vs 指针接收者的选择:

  • 需要修改结构体字段时使用指针接收者
  • 结构体较大时使用指针接收者(避免拷贝)
  • 否则使用值接收者

匿名字段(嵌入)

Go 支持匿名字段,实现类似"继承"的效果。

package main

import "fmt"

// 基础结构体
type Animal struct {
    Name string
    Age  int
}

// 方法
func (a Animal) Speak() string {
    return "..."
}

// 嵌入 Animal
type Dog struct {
    Animal        // 匿名字段
    Breed string // 具名字段
}

func main() {
    dog := Dog{
        Animal: Animal{Name: "旺财", Age: 3},
        Breed:  "金毛",
    }

    // 可以直接访问嵌入结构体的字段
    fmt.Println(dog.Name)  // 旺财
    fmt.Println(dog.Age)   // 3
    fmt.Println(dog.Breed) // 金毛

    // 也可以显式通过类型名访问
    fmt.Println(dog.Animal.Name) // 旺财

    // 调用嵌入结构体的方法
    fmt.Println(dog.Speak()) // ...
}

字段标签(Struct Tag)

结构体字段可以附带标签(tag),标签在运行时通过反射获取,常用于 JSON 序列化、ORM 映射等。

package main

import (
    "fmt"
    "reflect"
)

type User struct {
    ID       int    `json:"id" db:"user_id"`
    Username string `json:"username" db:"user_name"`
    Email    string `json:"email,omitempty"`
    Password string `json:"-"` // 序列化时忽略此字段
}

func main() {
    u := User{ID: 1, Username: "admin", Email: "admin@example.com", Password: "secret"}

    t := reflect.TypeOf(u)
    for i := 0; i < t.NumField(); i++ {
        field := t.Field(i)
        fmt.Printf("字段: %-10s json: %s\n", field.Name, field.Tag.Get("json"))
    }
}

标签不会在运行时影响程序逻辑,它只是一种元数据,供外部库(如 encoding/jsongorm)使用。

结构体比较

结构体可以使用 == 比较,前提是所有字段都是可比较类型。

package main

import "fmt"

type Point struct {
    X int
    Y int
}

type Circle struct {
    Center Point
    Radius float64
}

func main() {
    p1 := Point{1, 2}
    p2 := Point{1, 2}
    p3 := Point{3, 4}

    fmt.Println(p1 == p2) // true
    fmt.Println(p1 == p3) // false

    c1 := Circle{Center: Point{0, 0}, Radius: 5}
    c2 := Circle{Center: Point{0, 0}, Radius: 5}
    fmt.Println(c1 == c2) // true
}

包含不可比较字段(如切片、map)的结构体不能使用 ==

package main

import "fmt"

type Data struct {
    Values []int // 切片不可比较
}

func main() {
    d1 := Data{Values: []int{1, 2, 3}}
    d2 := Data{Values: []int{1, 2, 3}}

    // fmt.Println(d1 == d2) // 编译错误:invalid operation
    _ = d1
    _ = d2
}

结构体嵌套

结构体可以包含其他结构体作为字段。

package main

import "fmt"

type Address struct {
    City    string
    District string
    Street  string
}

type Employee struct {
    Name    string
    Age     int
    Address Address // 嵌套结构体
}

func main() {
    emp := Employee{
        Name: "张三",
        Age:  28,
        Address: Address{
            City:     "北京",
            District: "朝阳区",
            Street:   "建国路",
        },
    }

    fmt.Println(emp.Name)            // 张三
    fmt.Println(emp.Address.City)    // 北京
    fmt.Println(emp.Address.District) // 朝阳区
}

JSON 序列化与结构体标签

encoding/json 包可以将结构体序列化为 JSON,通过标签控制字段名称和行为。

package main

import (
    "encoding/json"
    "fmt"
)

type User struct {
    ID       int      `json:"id"`
    Name     string   `json:"name"`
    Email    string   `json:"email,omitempty"` // 零值时省略
    Password string   `json:"-"`               // 忽略此字段
    Tags     []string `json:"tags,omitempty"`
}

func main() {
    u := User{
        ID:       1,
        Name:     "admin",
        Email:    "admin@example.com",
        Password: "secret",
        Tags:     []string{"root", "admin"},
    }

    // 序列化
    data, _ := json.MarshalIndent(u, "", "  ")
    fmt.Println(string(data))

    // 反序列化
    jsonStr := `{"id": 2, "name": "user"}`
    var u2 User
    json.Unmarshal([]byte(jsonStr), &u2)
    fmt.Printf("%+v\n", u2)
}

常用 JSON 标签选项:

  • json:"field_name" — 指定 JSON 字段名
  • json:"-" — 序列化时忽略
  • json:",omitempty" — 零值时省略该字段

工厂函数模式(NewXxx)

Go 没有构造函数,但通常使用 NewXxx 工厂函数来初始化结构体。

package main

import (
    "errors"
    "fmt"
)

type User struct {
    ID    int
    Name  string
    Email string
}

// NewUser 是工厂函数,返回指针
func NewUser(id int, name, email string) (*User, error) {
    if name == "" {
        return nil, errors.New("name cannot be empty")
    }
    return &User{ID: id, Name: name, Email: email}, nil
}

func main() {
    u1, err := NewUser(1, "张三", "zhangsan@example.com")
    if err != nil {
        fmt.Println("创建失败:", err)
    }
    fmt.Printf("%+v\n", u1)

    _, err = NewUser(2, "", "test@example.com")
    fmt.Println(err) // name cannot be empty
}

工厂函数的优势:可以在创建时执行验证逻辑、设置默认值、返回指针避免大结构体拷贝,符合 Go 的惯用写法。

总结

本篇全面介绍了 Go 语言结构体的核心知识:结构体的定义与初始化、值接收者与指针接收者方法、匿名字段(嵌入)实现组合复用、字段标签(struct tag)的用途、结构体比较的条件、结构体嵌套、JSON 序列化与标签配合,以及工厂函数模式。结构体是 Go 语言组织数据的基础,掌握结构体的使用是学习 Go 接口、方法等高级特性的前提。