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 应用。

相关推荐
zhangfeng11332 小时前
wgcna 相关性热图中4个颜色 4个共表达模块 的模块基因是否都要做GO/KEGG分析”,核心取决于你的**研究目标和模块的生物学意义*
开发语言·r语言·生物信息
Dream_Ji2 小时前
Swift 入门(一 - 基础语法)
开发语言·ios·swift
勇闯逆流河3 小时前
【C++】AVL详解
开发语言·c++
一口面条一口蒜3 小时前
R语言中的获取函数与替换函数
开发语言·r语言
程序员烧烤3 小时前
【Java初学基础10】一文讲清反射
java·开发语言
武陵悭臾3 小时前
安卓应用开发学习:应用ViewPager2翻页视图实现页面水平切换
android·学习·viewpager2·deepseek·翻页视图
tingting01193 小时前
mysql 8.4.2 备份脚本
android·数据库·mysql
大飞pkz3 小时前
【设计模式】状态模式
开发语言·设计模式·c#·状态模式
ajassi20004 小时前
开源 C# 快速开发(十)通讯--http客户端
开发语言·开源·c#