结构体
结构体
想象你在填写一张个人信息登记表:姓名、年龄、电话、地址——这些信息彼此关联,共同描述一个人。在 Go 语言中,struct(结构体)就是这张"登记表":它将多个不同类型的字段组合在一起,形成一个自定义的复合类型。相比 map,struct 的字段在编译期就已确定,类型安全、语义清晰,是 Go 中组织数据的核心方式。
1. 核心概念
| 概念 | 说明 |
|---|---|
| 定义 | type StructName struct { ... },字段名在前、类型在后 |
| 初始化 | 字面量 S{f1: v1, f2: v2}、new(S) 返回指针、&S{} |
| 字段访问 | 点号 s.Field,指针同样用点号 p.Field(无需 ->) |
| 值类型 | struct 是值类型,赋值和传参会复制整个结构体 |
| 指针接收者 | 通过 &s 获取指针,修改指针指向的结构体会影响原值 |
| 匿名字段 | 只写类型不写字段名,类型名即字段名,实现"组合" |
| 嵌套结构体 | 结构体字段本身也是结构体,支持多层嵌套 |
| 结构体标签 Tag | 反引号包裹的元数据,常用于 JSON/数据库映射 |
⚠️ 注意:struct 是值类型,直接传参会复制一份。如需修改原值,应传指针
*S。
2. 基本语法/用法
定义结构体
GO
// 定义一个 Person 结构体
type Person struct {
Name string
Age int
City string
}
💡 提示:Go 的结构体字段名如果以大写字母开头,则该字段是导出的(public);小写开头则是未导出的(private),仅包内可见。
初始化方式
GO
// 方式一:字段名: 值(推荐,可读性好,顺序无关)
p1 := Person{Name: "Alice", Age: 25, City: "北京"}
// 方式二:按顺序赋值(字段多时不推荐,容易出错)
p2 := Person{"Bob", 30, "上海"}
// 方式三:先声明后赋值
var p3 Person
p3.Name = "Charlie"
p3.Age = 28
p3.City = "广州"
// 方式四:new 创建(返回指针)
p4 := new(Person) // *Person
p4.Name = "Diana"
// 方式五:取地址(返回指针)
p5 := &Person{Name: "Eve", Age: 22, City: "深圳"}
💡 提示:
var p Person 声明的结构体,所有字段会被初始化为对应类型的零值(string → "",int → 0,bool → false)。
访问与修改字段
GO
p := Person{Name: "Alice", Age: 25}
// 读取字段
fmt.Println(p.Name) // Alice
// 修改字段
p.Age = 26
fmt.Println(p.Age) // 26
💡 提示:指针和值访问字段的语法完全一致——Go 会自动解引用,不需要
p->Name 这样的写法。
结构体指针
GO
p := &Person{Name: "Alice", Age: 25}
// Go 自动解引用,直接用点号访问
fmt.Println(p.Name) // Alice
// 通过指针修改原值
p.Age = 30
💡 提示:Go 中没有
-> 运算符。无论 p 是值还是指针,都统一用 p.Field 访问。
3. 示例代码
示例 1:基础用法(难度⭐)
定义一个 Book 结构体,创建实例并打印信息。
GO
package main
import "fmt"
// Book 定义图书结构体
type Book struct {
Title string
Author string
Pages int
Price float64
}
func main() {
// 使用字面量创建
book1 := Book{
Title: "Go语言编程",
Author: "张三",
Pages: 350,
Price: 59.9,
}
// 打印结构体
fmt.Println(book1)
// 访问单个字段
fmt.Printf("书名: %s\n", book1.Title)
fmt.Printf("作者: %s\n", book1.Author)
fmt.Printf("价格: %.1f 元\n", book1.Price)
// 修改字段
book1.Price = 49.9
fmt.Printf("新价格: %.1f 元\n", book1.Price)
// 按顺序创建(不推荐)
book2 := Book{"Python入门", "李四", 280, 45.0}
fmt.Println(book2)
}
输出:
TEXT
{Go语言编程 张三 350 59.9}
书名: Go语言编程
作者: 张三
价格: 59.9 元
新价格: 49.9 元
{Python入门 李四 280 45}
示例 2:进阶用法(难度⭐⭐)
使用指针、嵌套结构体和匿名字段。
GO
package main
import "fmt"
// Address 地址结构体
type Address struct {
City string
Street string
ZipCode string
}
// Employee 员工结构体(使用匿名字段 Address)
type Employee struct {
Name string
Age int
Address // 匿名字段:类型名即字段名
Salary float64
}
func main() {
// ========== 嵌套结构体初始化 ==========
emp := Employee{
Name: "Alice",
Age: 30,
Salary: 15000,
Address: Address{
City: "北京",
Street: "中关村大街1号",
ZipCode: "100080",
},
}
fmt.Printf("员工: %s\n", emp.Name)
fmt.Printf("城市: %s\n", emp.Address.City) // 显式访问
// ========== 匿名字段的"提升"访问 ==========
// 匿名字段可以直接用类型名访问
fmt.Printf("街道: %s\n", emp.Street) // 等同于 emp.Address.Street
fmt.Printf("邮编: %s\n", emp.ZipCode) // 等同于 emp.Address.ZipCode
// ========== 指针操作 ==========
empPtr := &emp
// 通过指针修改(Go 自动解引用)
empPtr.Age = 31
empPtr.Salary = 18000
empPtr.City = "上海" // 通过指针修改匿名字段
fmt.Printf("\n修改后: %+v\n", *empPtr)
// ========== 值类型 vs 指针类型 ==========
fmt.Println("\n--- 值类型 vs 指针类型 ---")
// 值类型:赋值是复制
emp2 := emp
emp2.Name = "Bob"
fmt.Printf("emp.Name: %s\n", emp.Name) // 仍然是 Alice
fmt.Printf("emp2.Name: %s\n", emp2.Name) // Bob
// 指针类型:赋值是共享
emp3 := empPtr
emp3.Name = "Charlie"
fmt.Printf("empPtr.Name: %s\n", empPtr.Name) // Charlie(被修改了)
}
输出:
TEXT
员工: Alice
城市: 北京
街道: 中关村大街1号
邮编: 100080
修改后: {Name:Alice Age:31 Address:{City:上海 Street:中关村大街1号 ZipCode:100080} Salary:18000}
--- 值类型 vs 指针类型 ---
emp.Name: Alice
emp2.Name: Bob
empPtr.Name: Charlie
示例 3:综合应用(难度⭐⭐⭐)
使用结构体标签(Tag)实现 JSON 序列化/反序列化,并构建一个学生管理系统。
GO
package main
import (
"encoding/json"
"fmt"
"sort"
"strings"
)
// Student 学生结构体(带 JSON 标签)
type Student struct {
ID int `json:"id"`
Name string `json:"name"`
Age int `json:"age"`
Score float64 `json:"score"`
Tags []string `json:"tags,omitempty"` // omitempty: 空值时省略
secret string // 未导出字段,JSON 无法访问
}
// ClassRoom 班级(嵌套结构体)
type ClassRoom struct {
ClassName string `json:"class_name"`
Students []Student `json:"students"`
}
// AddStudent 添加学生
func (c *ClassRoom) AddStudent(s Student) {
c.Students = append(c.Students, s)
}
// GetTopN 获取成绩前 N 名
func (c *ClassRoom) GetTopN(n int) []Student {
// 复制一份,避免修改原数据
sorted := make([]Student, len(c.Students))
copy(sorted, c.Students)
// 按成绩降序排序
sort.Slice(sorted, func(i, j int) bool {
return sorted[i].Score > sorted[j].Score
})
if n > len(sorted) {
n = len(sorted)
}
return sorted[:n]
}
// Average 计算平均分
func (c *ClassRoom) Average() float64 {
if len(c.Students) == 0 {
return 0
}
total := 0.0
for _, s := range c.Students {
total += s.Score
}
return total / float64(len(c.Students))
}
// SearchByName 按名字搜索(模糊匹配)
func (c *ClassRoom) SearchByName(keyword string) []Student {
var result []Student
for _, s := range c.Students {
if strings.Contains(s.Name, keyword) {
result = append(result, s)
}
}
return result
}
func main() {
// 创建班级
class := &ClassRoom{ClassName: "2024级一班"}
// 添加学生
students := []Student{
{ID: 1, Name: "张三", Age: 18, Score: 92.5, Tags: []string{"数学好", "班长"}},
{ID: 2, Name: "李四", Age: 19, Score: 88.0, Tags: []string{"体育好"}},
{ID: 3, Name: "王五", Age: 18, Score: 95.5},
{ID: 4, Name: "赵六", Age: 20, Score: 78.0, Tags: []string{"文艺特长"}},
{ID: 5, Name: "张伟", Age: 19, Score: 91.0, Tags: []string{"数学好", "竞赛获奖"}},
}
for _, s := range students {
class.AddStudent(s)
}
// ========== JSON 序列化 ==========
fmt.Println("=== JSON 序列化 ===")
jsonData, err := json.MarshalIndent(class, "", " ")
if err != nil {
fmt.Println("序列化失败:", err)
} else {
fmt.Println(string(jsonData))
}
// ========== JSON 反序列化 ==========
fmt.Println("\n=== JSON 反序列化 ===")
jsonStr := `{"id":6,"name":"孙七","age":17,"score":97.0,"tags":["英语好"]}`
var newStudent Student
if err := json.Unmarshal([]byte(jsonStr), &newStudent); err != nil {
fmt.Println("反序列化失败:", err)
} else {
fmt.Printf("新学生: %+v\n", newStudent)
}
// ========== 功能演示 ==========
fmt.Println("\n=== 班级信息 ===")
fmt.Printf("班级: %s,共 %d 人\n", class.ClassName, len(class.Students))
fmt.Printf("平均分: %.1f\n", class.Average())
fmt.Println("\n=== 成绩前3名 ===")
top3 := class.GetTopN(3)
for i, s := range top3 {
fmt.Printf(" 第%d名: %s (%.1f分)\n", i+1, s.Name, s.Score)
}
fmt.Println("\n=== 搜索 '张' ===")
results := class.SearchByName("张")
for _, s := range results {
fmt.Printf(" %s (ID: %d, 分数: %.1f)\n", s.Name, s.ID, s.Score)
}
}
输出:
TEXT
=== JSON 序列化 ===
{
"class_name": "2024级一班",
"students": [
{
"id": 1,
"name": "张三",
"age": 18,
"score": 92.5,
"tags": [
"数学好",
"班长"
]
},
{
"id": 2,
"name": "李四",
"age": 19,
"score": 88
},
{
"id": 3,
"name": "王五",
"age": 18,
"score": 95.5
},
{
"id": 4,
"name": "赵六",
"age": 20,
"score": 78,
"tags": [
"文艺特长"
]
},
{
"id": 5,
"name": "张伟",
"age": 19,
"score": 91,
"tags": [
"数学好",
"竞赛获奖"
]
}
]
}
=== JSON 反序列化 ===
新学生: {ID:6 Name:孙七 Age:17 Score:97 Tags:[英语好]}
=== 班级信息 ===
班级: 2024级一班,共 5 人
平均分: 89.0
=== 成绩前3名 ===
第1名: 王五 (95.5分)
第2名: 张三 (92.5分)
第3名: 张伟 (91.0分)
=== 搜索 '张' ===
张三 (ID: 1, 分数: 92.5)
张伟 (ID: 5, 分数: 91.0)
3. 常见应用场景
场景一:JSON 数据交互
在 Web 开发中,struct 是处理 JSON 请求/响应的标准方式。通过 Tag 控制 JSON 字段名。
GO
package main
import (
"encoding/json"
"fmt"
)
// APIResponse 统一 API 响应结构
type APIResponse struct {
Code int `json:"code"`
Message string `json:"message"`
Data interface{} `json:"data,omitempty"`
}
// UserDTO 用户数据传输对象
type UserDTO struct {
ID int `json:"id"`
Username string `json:"username"`
Email string `json:"email"`
Password string `json:"-"` // "-" 表示 JSON 中忽略此字段
}
func main() {
// 构造响应
user := UserDTO{
ID: 1,
Username: "alice",
Email: "alice@example.com",
Password: "secret123", // 不会被序列化
}
resp := APIResponse{
Code: 200,
Message: "success",
Data: user,
}
// 序列化
jsonBytes, _ := json.MarshalIndent(resp, "", " ")
fmt.Println(string(jsonBytes))
// 反序列化
jsonStr := `{"code":404,"message":"用户不存在"}`
var errResp APIResponse
json.Unmarshal([]byte(jsonStr), &errResp)
fmt.Printf("\n错误码: %d, 消息: %s\n", errResp.Code, errResp.Message)
}
输出:
TEXT
{
"code": 200,
"message": "success",
"data": {
"id": 1,
"username": "alice",
"email": "alice@example.com"
}
}
错误码: 404, 消息: 用户不存在
场景二:面向对象模拟(组合优于继承)
Go 没有类和继承,通过结构体嵌套(组合)实现代码复用。
GO
package main
import "fmt"
// Logger 日志能力
type Logger struct {
Prefix string
}
// Log 输出日志
func (l *Logger) Log(msg string) {
fmt.Printf("[%s] %s\n", l.Prefix, msg)
}
// Cache 缓存能力
type Cache struct {
data map[string]string
}
// Get 从缓存获取
func (c *Cache) Get(key string) (string, bool) {
val, ok := c.data[key]
return val, ok
}
// Set 设置缓存
func (c *Cache) Set(key, value string) {
if c.data == nil {
c.data = make(map[string]string)
}
c.data[key] = value
}
// UserService 通过组合复用 Logger 和 Cache 的能力
type UserService struct {
Logger // 嵌入 Logger
Cache // 嵌入 Cache
Name string
}
func main() {
svc := UserService{
Logger: Logger{Prefix: "UserService"},
Name: "用户服务",
}
// 直接调用嵌入的方法
svc.Log("服务启动") // 来自 Logger
svc.Set("user:1", "Alice") // 来自 Cache
svc.Log("缓存已初始化")
if val, ok := svc.Get("user:1"); ok {
svc.Log(fmt.Sprintf("找到用户: %s", val))
}
}
输出:
TEXT
[UserService] 服务启动
[UserService] 缓存已初始化
[UserService] 找到用户: Alice
❓ 常见问题
Q1:struct 是值类型还是引用类型?
struct 是值类型。赋值和传参会复制整个结构体。如果需要修改原值,应传指针:
GO
func updateAge(p *Person, age int) {
p.Age = age // 直接修改原值
}
p := Person{Name: "Alice", Age: 25}
updateAge(&p, 30)
fmt.Println(p.Age) // 30
Q2:匿名字段有什么用?和嵌套结构体有什么区别?
匿名字段实现了"组合"(composition),内层类型的方法和字段会被"提升"到外层:
GO
type Address struct {
City string
}
type Person struct {
Name string
Address // 匿名字段
}
p := Person{Name: "Alice", Address: Address{City: "北京"}}
fmt.Println(p.City) // 直接访问,等同于 p.Address.City
fmt.Println(p.Address.City) // 也可以显式访问
如果嵌套的结构体有同名字段,需要用完整路径访问来消除歧义。
Q3:结构体标签 Tag 是什么?怎么用?
Tag 是反引号包裹的元数据,常用于控制 JSON、数据库 ORM 等库的行为:
GO
type User struct {
ID int `json:"id" db:"user_id"`
Name string `json:"name" db:"user_name"`
Password string `json:"-" db:"password"` // JSON 忽略
Email string `json:"email,omitempty" db:"email"` // 空值时 JSON 忽略
}
常见 Tag:
json:"fieldName"— 控制 JSON 字段名json:"-"— 序列化时忽略该字段json:",omitempty"— 零值时省略db:"column_name"— 数据库字段映射(需 ORM 支持)
Q4:结构体可以比较吗?
取决于字段类型。如果所有字段都是可比较类型(int、string、bool、数组等),结构体可以用 == 和 != 比较。如果包含 slice、map、func 等不可比较字段,则不能直接比较:
GO
type Point struct { X, Y int }
p1 := Point{1, 2}
p2 := Point{1, 2}
fmt.Println(p1 == p2) // true
// 包含 slice 的结构体不能用 ==
type Data struct { Items []int }
// d1 == d2 // 编译错误!
📖 小节
- struct 是 Go 的自定义复合类型,将多个字段组合成一个整体
- struct 是值类型,赋值和传参会复制整个结构体;修改原值需传指针
*S - 访问字段统一用点号
s.Field,指针同样p.Field,无需-> - 匿名字段(只写类型不写名字)实现组合,内层字段被"提升"到外层
- 嵌套结构体支持多层数据组织
- 结构体标签 Tag 通过反引号定义,常用于 JSON/ORM 等框架的字段映射
json:"-"忽略字段,json:",omitempty"空值省略- 结构体是否可比较取决于其字段类型
📝 作业
练习 1(⭐)
定义一个 Rectangle 结构体,包含 Width 和 Height 两个字段。实现以下功能:
- 创建一个 Rectangle 实例并打印
- 编写
Area()方法计算面积 - 编写
Perimeter()方法计算周长 - 编写
IsSquare()方法判断是否为正方形
练习 2(⭐⭐)
定义一个 BankAccount 结构体,包含 Owner(string)、Balance(float64)字段。实现以下功能:
Deposit(amount)存款Withdraw(amount)取款(余额不足时提示)Transfer(other *BankAccount, amount)转账- 使用 JSON 标签序列化账户信息(隐藏余额,使用
json:"-")
练习 3(⭐⭐⭐)
实现一个商品库存管理系统:
- 定义
Product结构体(ID、Name、Price、Stock、Tags) - 定义
Inventory结构体(ShopName、Products 切片) - 实现方法:
AddProduct、RemoveProduct(id)、FindByTag(tag)、TotalValue()(总库存价值) - 实现 JSON 序列化,要求:Price 保留两位小数,Stock 为 0 时省略
- 创建实例,添加商品,按标签搜索,计算总价值,最后 JSON 输出



