Skip to content

Go 结构体

在 Go 语言中,结构体(struct)是用于定义复杂数据类型的基本构造。结构体允许你将多个不同类型的数据组合成一个单一的类型。它们是 Go 中最常用的自定义数据类型之一,通常用于表示具有多个字段的实体,如用户、文件、数据库记录等。

1. 定义结构体

结构体通过 struct 关键字定义。结构体的字段可以是任何类型,包括基础数据类型、数组、切片、其他结构体、甚至是函数。

基本结构体定义

go
package main

import "fmt"

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

func main() {
    // 创建结构体实例并初始化
    p := Person{
        Name: "Alice",
        Age:  30,
    }

    fmt.Println(p) // 输出: {Alice 30}
}

在这个例子中,Person 是一个结构体类型,包含了两个字段:NameAgeName 是字符串类型,Age 是整数类型。

2. 结构体字段的命名和访问

结构体字段的命名有一些规则和约定:

  • 字段名必须是 首字母大写 的才能在包外访问(这与 Go 的封装机制相关)。
  • 字段可以在定义结构体时被初始化,也可以在实例化时进行初始化。

示例:访问结构体字段

go
package main

import "fmt"

type Person struct {
    Name string
    Age  int
}

func main() {
    p := Person{
        Name: "Alice",
        Age:  30,
    }

    // 访问结构体字段
    fmt.Println("Name:", p.Name) // 输出: Name: Alice
    fmt.Println("Age:", p.Age)   // 输出: Age: 30
}

3. 结构体字段标签(Tags)

结构体字段标签(tags)是结构体字段附加的元数据,通常用于与其他包或库进行交互(如 JSON 编解码、数据库映射等)。标签放在字段后面,使用反引号 ` 来包围。

示例:结构体标签

go
package main

import (
	"encoding/json"
	"fmt"
)

type Person struct {
	Name string `json:"name"`
	Age  int    `json:"age"`
}

func main() {
	p := Person{Name: "Alice", Age: 30}
	
	// 使用 JSON 编码时,结构体标签定义了如何将字段名转换为 JSON 键
	data, err := json.Marshal(p)
	if err != nil {
		fmt.Println("Error:", err)
		return
	}
	
	fmt.Println(string(data)) // 输出: {"name":"Alice","age":30}
}

在上面的例子中,结构体字段 NameAge 有对应的标签,指示如何在 JSON 编码时将字段名映射为 JSON 键名。

  • json:"name":指定将 Name 字段编码为 name
  • json:"age":指定将 Age 字段编码为 age

4. 结构体的零值和初始化

  • 零值:如果结构体没有显式初始化,它会自动赋予零值(例如,字符串为 "",整数为 0)。
  • 初始化:可以通过直接初始化结构体或使用 new() 函数来创建结构体实例。

零值示例

go
package main

import "fmt"

type Person struct {
    Name string
    Age  int
}

func main() {
    var p Person // 创建一个结构体变量,默认值为零值
    fmt.Println(p) // 输出: { 0},即 Name 为 "", Age 为 0
}

通过构造函数初始化结构体

go
package main

import "fmt"

type Person struct {
    Name string
    Age  int
}

// 使用构造函数初始化结构体
func NewPerson(name string, age int) Person {
    return Person{Name: name, Age: age}
}

func main() {
    p := NewPerson("Bob", 25)
    fmt.Println(p) // 输出: {Bob 25}
}

5. 结构体指针

结构体指针是指向结构体的指针,它通常用于避免复制结构体的大量数据或修改结构体字段。结构体指针可以直接通过 & 操作符获取。

示例:结构体指针

go
package main

import "fmt"

type Person struct {
    Name string
    Age  int
}

func main() {
    p := Person{Name: "Charlie", Age: 40}
    
    // 使用结构体指针
    ptr := &p
    ptr.Age = 41 // 修改指针指向的结构体字段

    fmt.Println(p) // 输出: {Charlie 41}
}

通过指针,你可以直接修改结构体的字段,因为指针指向的是结构体的实际位置。

6. 结构体的比较

Go 支持结构体的比较操作。你可以直接比较两个结构体的值,如果它们的字段值完全相等,则返回 true,否则返回 false。结构体的字段必须是可以比较的类型(如数字、字符串、指针等)。

示例:结构体比较

go
package main

import "fmt"

type Person struct {
    Name string
    Age  int
}

func main() {
    p1 := Person{Name: "Alice", Age: 30}
    p2 := Person{Name: "Alice", Age: 30}
    p3 := Person{Name: "Bob", Age: 40}

    fmt.Println(p1 == p2) // 输出: true
    fmt.Println(p1 == p3) // 输出: false
}

7. 结构体嵌套(组合)

Go 允许将一个结构体嵌套到另一个结构体中。这种方式有时被称为“组合”或“嵌入式结构体”,它是一种简单而灵活的继承方式。

示例:结构体嵌套

go
package main

import "fmt"

// 定义 Address 结构体
type Address struct {
    City  string
    State string
}

// 定义 Person 结构体,嵌套 Address
type Person struct {
    Name    string
    Age     int
    Address // 嵌套 Address
}

func main() {
    p := Person{
        Name:   "Alice",
        Age:    30,
        Address: Address{
            City:  "New York",
            State: "NY",
        },
    }

    fmt.Println(p.Name)        // 输出: Alice
    fmt.Println(p.City)        // 输出: New York (通过嵌套的 Address)
    fmt.Println(p.State)       // 输出: NY (通过嵌套的 Address)
}

在上面的例子中,Person 结构体嵌套了 Address 结构体,所以 Person 结构体可以直接访问 Address 中的字段(如 CityState)。

8. 结构体方法

Go 支持为结构体定义方法,方法是绑定到结构体类型的函数。方法可以通过结构体的值或指针来调用。

示例:结构体方法

go
package main

import "fmt"

type Person struct {
    Name string
    Age  int
}

// 定义方法
func (p Person) Greet() {
    fmt.Printf("Hello, my name is %s and I am %d years old.\n", p.Name, p.Age)
}

func main() {
    p := Person{Name: "Alice", Age: 30}
    p.Greet() // 调用方法
}

使用指针接收者

go
package main

import "fmt"

type Person struct {
    Name string
    Age  int
}

func (p *Person) HaveBirthday() {
    p.Age++
}

func main() {
    p := Person{Name: "Bob", Age: 29}
    p.HaveBirthday()
    fmt.Println(p.Age) // 输出: 30
}

9. 结构体的并发安全

结构体的并发安全通常需要通过使用同步原语(如 sync.Mutexsync.RWMutex)来确保多 goroutine 同时访问结构体时不会发生竞态条件。

go
package main

import (
	"fmt"
	"sync"
)

type Counter struct {
    mu    sync.Mutex
    value int
}

func (c *Counter) Increment() {
    c.mu.Lock()
    defer c.mu.Unlock()
    c.value++
}

func (c *Counter) GetValue() int {
    c.mu.Lock()
    defer c.mu.Unlock()
    return c.value
}

func main() {
    c := Counter{}
    c.Increment()
    fmt.Println(c.GetValue()) // 输出: 1
}

总结

Go 中的结构体是非常灵活和强大的数据类型,能够帮助你高效地组织和管理数据。常见的结构体特性