Go 语言中的结构体

我们刚刚学习了方法(Methods)和接口(Interfaces),而它们几乎总是与结构体(Structs)一起使用。可以说,结构体是 Go 语言中组织数据的基石,理解它至关重要。

它扮演的角色非常像 PHP 中的 class,但只负责封装数据(属性),不直接包含方法。

我们同样分场景,从简单到高阶来介绍它,并首先回答你最关心的问题。


核心:如何判断属性是否可被外部访问?(可见性规则)

在 PHP 中,我们用 public, private, protected 关键字来控制属性和方法的可见性。

Go 的规则要简单得多,并且只有一个标准:根据名称的首字母大小写来判断

  • 首字母大写 (Exported) :如果一个结构体的字段(或任何顶层变量、函数、类型)以大写字母 开头,那么它就是导出的(Exported) ,相当于 PHP 的 public。这意味着在**任何其他包(package)**中都可以直接访问它。

  • 首字母小写 (Unexported) :如果字段以小写字母 开头,那么它就是未导出的(Unexported) ,类似于 PHP 的 private。它只能在同一个包内部被访问。

举个例子:

假设我们有一个 models 包,里面定义了一个 User 结构体:

go 复制代码
// in package models
package models

type User struct {
    ID        int    // 大写,公开的
    FirstName string // 大写,公开的
    lastName  string // 小写,私有的
    password  string // 小写,私有的
}

现在,我们在 main 包(另一个包)中使用它:

go 复制代码
// in package main
package main

import "myproject/models"

func main() {
    u := models.User{}

    u.ID = 101           // 正确!ID 是大写的,可以访问
    u.FirstName = "Alice" // 正确!FirstName 是大写的,可以访问

    // u.lastName = "Smith" // 编译错误!lastName 在包外不可见
    // u.password = "123"   // 编译错误!password 在包外不可见
}

这个规则简单、一致,适用于 Go 中所有需要控制可见性的地方。


场景一:基础用法

1. 聚合相关数据

这是 struct 最基本、最核心的用途:将一堆相关的变量组合成一个单一的、有意义的逻辑单元。

  • 场景 :

    • 定义一个"用户",包含姓名、年龄、邮箱。
    • 定义一个数据库连接配置,包含主机、端口、用户名、密码。
    • 定义一个 API 响应,包含状态码、消息和数据。
  • 用法 :

    go 复制代码
    type DBConfig struct {
        Host     string
        Port     int
        Username string
        Password string
    }
    
    func main() {
        // 创建并初始化一个 DBConfig 实例
        config := DBConfig{
            Host:     "localhost",
            Port:     3306,
            Username: "root",
            Password: "password",
        }
    
        fmt.Println("Connecting to host:", config.Host)
    }

场景二:中阶用法

2. 结构体嵌套与组合(替代继承)

Go 语言没有 PHP 的 extends 继承。作为替代,Go 提倡使用组合(Composition) ,通过在一个结构体中嵌套另一个结构体来实现功能的复用和扩展。

  • 场景:

    • 一个"员工 (Employee)"拥有"个人信息 (Person)"的所有属性,同时还有额外的"职位 (Position)"属性。
    • 一个"文章 (Post)"包含一个"作者 (Author)"的信息。
  • 用法 :

    有两种嵌套方式:

    a) 作为普通字段嵌套

    go 复制代码
    type Address struct {
        City    string
        Country string
    }
    
    type Company struct {
        Name    string
        Address Address // Address 是 Company 的一个普通字段
    }
    
    func main() {
        c := Company{
            Name: "Google",
            Address: Address{
                City:    "Mountain View",
                Country: "USA",
            },
        }
        // 访问嵌套字段需要逐级访问
        fmt.Println(c.Address.City)
    }

    b) 匿名嵌套 (Embedding)

    通过只写类型而不写字段名,可以将嵌套结构体的字段"提升"到外层,实现类似继承的效果。

    go 复制代码
    type Person struct {
        Name string
        Age  int
    }
    
    type Employee struct {
        Person            // 匿名嵌套 Person
        Position string
    }
    
    func main() {
        e := Employee{
            Person: Person{
                Name: "Alice",
                Age:  30,
            },
            Position: "Engineer",
        }
        // 可以直接访问 Person 的字段,就像是 Employee 自己的一样
        fmt.Println(e.Name)     // 直接访问,而不是 e.Person.Name
        fmt.Println(e.Position)
    }
3. 使用结构体标签 (Struct Tags)

标签是附加在结构体字段上的元数据(metadata)。它们本身对 Go 代码没有影响,但可以被其他库(如 encoding/json)读取,用来指导这些库如何处理这个字段。

  • 场景 :

    • 控制一个结构体如何被编码/解码成 JSON 格式(最常用!)。
    • 定义 ORM(对象关系映射)中结构体字段与数据库表列的对应关系。
    • 为字段添加验证规则。
  • 用法 : 标签是写在反引号 ````` 内的字符串。

    go 复制代码
    type Product struct {
        ID          int     `json:"id"`
        Name        string  `json:"product_name"`
        Price       float64 `json:"price,omitempty"` // omitempty 表示如果字段值为零值,则忽略它
        Description string  `json:"-"`               // - 表示永远不要处理这个字段
    }

    当你使用 json.MarshalProduct 实例转换为 JSON 时,encoding/json 包会读取这些标签,并生成对应 key 的 JSON。


场景三:高阶用法

4. 零宽度结构体 struct{}

我们在用 map 实现"集合"时已经见过它。因为它不占用任何内存,所以是表达"存在性"或发送"信号"的绝佳工具。

  • 场景 :

    • 集合(Set) : map[string]struct{},我们只关心键是否存在。
    • 通道信号: 在并发编程中,有时我们只想通知另一个 Goroutine 某件事已经完成,而不需要传递任何实际数据。
  • 用法 :

    go 复制代码
    // 使用 channel 发送一个完成信号
    done := make(chan struct{})
    
    go func() {
        fmt.Println("Worker is running...")
        // ... do some work ...
        // 工作完成,发送一个空结构体作为信号
        done <- struct{}{}
    }()
    
    // main goroutine 会在这里阻塞,直到收到信号
    <-done
    fmt.Println("Worker finished.")

总而言之,结构体是 Go 程序中数据的"骨架"。你通过定义结构体来构建你的数据模型,然后通过为它附加方法 来赋予它行为,最后通过接口来定义它的抽象契约,从而构建出清晰、健壮、可扩展的 Go 应用。

相关推荐
阿巴斯甜20 小时前
Android 报错:Zip file '/Users/lyy/develop/repoAndroidLapp/l-app-android-ble/app/bu
android
Kapaseker20 小时前
实战 Compose 中的 IntrinsicSize
android·kotlin
xq952721 小时前
Andorid Google 登录接入文档
android
黄林晴1 天前
告别 Modifier 地狱,Compose 样式系统要变天了
android·android jetpack
冬奇Lab1 天前
Android触摸事件分发、手势识别与输入优化实战
android·源码阅读
城东米粉儿2 天前
Android MediaPlayer 笔记
android
Jony_2 天前
Android 启动优化方案
android
阿巴斯甜2 天前
Android studio 报错:Cause: error=86, Bad CPU type in executable
android
张小潇2 天前
AOSP15 Input专题InputReader源码分析
android
_小马快跑_2 天前
Kotlin | 协程调度器选择:何时用CoroutineScope配置,何时用launch指定?
android