Go语言结构体

结构体(struct)是Go语言中组织数据的主要方式,类似于其他语言中的“类”(class),但不支持继承。结构体可以将多个任意类型的字段(field)组合成一个自定义类型,并可以为其定义方法(method),是实现面向对象编程的基础。

核心理念:Go不是传统面向对象的语言,但通过结构体和方法,可以实现数据封装和行为组合,并且更加轻量高效。

1. 定义结构体

使用type关键字定义新类型,后面跟上struct关键字及字段列表。

定义结构体
package main

import "fmt"

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

func main() {
    // 声明一个 Person 变量(零值)
    var p Person
    fmt.Printf("%#v\n", p) // main.Person{Name:"", Age:0, City:""}
}

字段命名通常采用驼峰式,首字母大写表示公开(可被其他包访问),小写表示私有。

2. 初始化结构体

2.1 字段赋值顺序初始化(不推荐)

p1 := Person{"Alice", 30, "New York"}
fmt.Println(p1)

缺点:依赖字段顺序,易出错。

2.2 使用字段名初始化(推荐)

p2 := Person{
    Name: "Bob",
    Age:  25,
    City: "Los Angeles",
}
// 未指定的字段为零值
p3 := Person{Name: "Charlie"} // Age:0, City:""

2.3 使用 new 或取地址初始化

p4 := new(Person)     // 返回 *Person,字段为零值
p4.Name = "David"

p5 := &Person{Name: "Eve", Age: 28} // 直接创建指针

3. 字段访问与修改

使用点号.访问或修改字段。

p := Person{Name: "Alice", Age: 30}
fmt.Println(p.Name)   // Alice
p.Age = 31
fmt.Println(p.Age)    // 31

// 指针访问同样用点(自动解引用)
ptr := &p
ptr.City = "Boston"   // 等价于 (*ptr).City = "Boston"
fmt.Println(p.City)   // Boston

4. 结构体嵌套

将一个结构体作为另一个结构体的字段,可以实现类似“组合”的效果。

type Address struct {
    Street string
    City   string
}

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

func main() {
    emp := Employee{
        Name: "John",
        Age:  35,
        Address: Address{
            Street: "123 Main St",
            City:   "Springfield",
        },
    }
    fmt.Println(emp.Address.City)
}

4.1 匿名嵌套(字段名提升)

如果只写类型名,不写字段名,称为匿名嵌套。外部结构体可以直接访问嵌套结构体的字段(提升)。

type Contact struct {
    Email string
    Phone string
}

type User struct {
    Name string
    Contact  // 匿名嵌套
}

func main() {
    u := User{
        Name: "Tom",
        Contact: Contact{
            Email: "tom@example.com",
            Phone: "123456",
        },
    }
    // 可以直接访问提升的字段
    fmt.Println(u.Email) // tom@example.com
    fmt.Println(u.Phone) // 123456
}
匿名嵌套常用于模拟“继承”,但本质是组合。如果多个嵌套有同名冲突,需要显式指定。

5. 结构体标签(Struct Tags)

结构体字段后可以添加反引号包裹的标签,常用于JSON序列化、ORM、表单验证等场景。

import "encoding/json"

type Product struct {
    ID    int     `json:"id"`
    Name  string  `json:"name"`
    Price float64 `json:"price,omitempty"`
    Secret string  `json:"-"`          // 忽略此字段
}

func main() {
    p := Product{ID: 1, Name: "Book", Price: 19.99}
    jsonData, _ := json.Marshal(p)
    fmt.Println(string(jsonData)) // {"id":1,"name":"Book","price":19.99}
}

常用的标签库:json、yaml、form、gorm等。

6. 结构体方法

Go可以为任意自定义类型(包括结构体)定义方法。方法是带有接收者(receiver)的函数。

6.1 值接收者

type Rectangle struct {
    Width, Height float64
}

// 值接收者:计算面积(不会修改原结构体)
func (r Rectangle) Area() float64 {
    return r.Width * r.Height
}

// 值接收者可以调用,但若要修改字段需使用指针接收者
func main() {
    rect := Rectangle{Width: 10, Height: 5}
    fmt.Println(rect.Area()) // 50
}

6.2 指针接收者

指针接收者可以修改结构体字段,并且避免大结构体的拷贝。

// 指针接收者:放大矩形
func (r *Rectangle) Scale(factor float64) {
    r.Width *= factor
    r.Height *= factor
}

func main() {
    rect := Rectangle{10, 5}
    rect.Scale(2)            // 自动取地址调用
    fmt.Println(rect.Width)  // 20
}
值接收者与指针接收者的选择:如果需要修改接收者,或者接收者是大对象,用指针接收者;否则值接收者更安全(不会意外修改)。

6.3 方法与普通函数的区别

// 方法
func (r Rectangle) Area() float64 { ... }
// 普通函数
func Area(r Rectangle) float64 { ... }

7. 构造函数(Convention)

Go没有内置构造函数,但通常使用New开头的函数来创建并初始化结构体,返回指针以避免大结构体拷贝。

func NewPerson(name string, age int) *Person {
    return &Person{
        Name: name,
        Age:  age,
        City: "Unknown",
    }
}

p := NewPerson("Alice", 30)
fmt.Println(p)

8. 结构体比较

如果结构体所有字段都是可比较的,则结构体变量可以使用==和!=比较。

p1 := Person{Name: "Alice", Age: 30}
p2 := Person{Name: "Alice", Age: 30}
fmt.Println(p1 == p2) // true

// 不可比较的字段(如切片)会导致编译错误

综合示例:学生成绩管理

结构体与方法综合应用
package main

import "fmt"

type Student struct {
    ID     int
    Name   string
    Scores []float64
}

// 计算平均分
func (s Student) Average() float64 {
    if len(s.Scores) == 0 {
        return 0
    }
    sum := 0.0
    for _, score := range s.Scores {
        sum += score
    }
    return sum / float64(len(s.Scores))
}

// 添加成绩(指针接收者,需要修改切片)
func (s *Student) AddScore(score float64) {
    s.Scores = append(s.Scores, score)
}

func NewStudent(id int, name string) *Student {
    return &Student{
        ID:     id,
        Name:   name,
        Scores: []float64{},
    }
}

func main() {
    stu := NewStudent(1001, "张三")
    stu.AddScore(85.5)
    stu.AddScore(92.0)
    stu.AddScore(78.0)

    fmt.Printf("学生: %s (ID:%d), 平均分: %.2f\n", stu.Name, stu.ID, stu.Average())
}
学生: 张三 (ID:1001), 平均分: 85.17
小结:结构体是Go中组织和操作数据的核心,结合方法可以实现面向对象风格编程。掌握结构体的定义、初始化、嵌套、标签和方法接收者,是编写高质量Go程序的关键。下一章将介绍接口(interface),进一步理解Go的多态机制。