# Go 语言结构体

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

## 结构体定义

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

```go
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` 返回结构体的指针，所有字段为零值。

```go
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}
}
```

### 使用取地址符 &

```go
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）可以是值类型或指针类型。

```go
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 支持匿名字段，实现类似"继承"的效果。

```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 映射等。

```go
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/json`、`gorm`）使用。

## 结构体比较

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

```go
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）的结构体不能使用 `==`：

```go
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
}
```

## 结构体嵌套

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

```go
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，通过标签控制字段名称和行为。

```go
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` 工厂函数来初始化结构体。

```go
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 接口、方法等高级特性的前提。

