
前言在前面的文章中我们学过了数组、切片和map它们都可以存储多个数据但有一个共同点它们存储的数据都是相同类型的。然而在现实世界中一个“实体”往往包含多种不同类型的信息例如一个学生有姓名string、年龄int、成绩float64等。在Go中这种将多个不同类型的数据组合成一个复合类型的能力由结构体struct提供。结构体是Go语言中“面向对象”风格的基石。它不像是类class没有继承和多态但通过组合和方法它同样能实现高效的数据封装。今天我们就来全面学习结构体如何定义、初始化、访问字段以及它与指针、方法的配合。一、什么是结构体结构体是一种聚合数据类型它由零个或多个任意类型的字段field组成。每个字段都可以有自己的名称和类型。你可以把它想象成一个“表格的一行”每个字段就是那一列的数据。结构体的定义使用type和struct关键字type 结构体类型名 struct { 字段1 类型1 字段2 类型2 // ... }例如定义一个表示学生的结构体type Student struct { Name string Age int Score float64 }二、创建和初始化结构体实例1. 零值实例声明结构体变量时如果没有显式初始化它的字段会被赋予对应类型的零值。var s Student fmt.Println(s) // { 0 0} 字符串默认 int 0float64 0.02. 使用字面量初始化可以按字段顺序提供值但不推荐容易出错更推荐使用字段名:值的方式。// 不推荐依赖字段顺序 s1 : Student{Alice, 20, 88.5} // 推荐使用字段名顺序无关 s2 : Student{ Name: Bob, Age: 21, Score: 92.0, }如果某些字段未指定会被赋予零值。3. 使用new或创建指针new(Student)返回指向零值结构体的指针而Student{...}也可以直接得到指针。p1 : new(Student) // 类型 *Student字段都是零值 p2 : Student{Name: Charlie} // 直接取地址三、访问和修改字段使用点号.访问字段s : Student{Name: Diana, Age: 22} fmt.Println(s.Name) // Diana s.Age 23 // 修改字段即使是通过指针访问Go 也支持直接使用.无需像 C 那样写-p : Student{Name: Eva} p.Age 20 // 相当于 (*p).Age 20语法糖四、结构体作为函数参数和返回值结构体既可以值传递也可以指针传递。值传递函数内修改副本不影响原结构体。指针传递可以修改原结构体且传递效率高只复制地址。func updateName(s Student, newName string) Student { s.Name newName return s // 必须返回修改后的副本 } func updateAge(p *Student, newAge int) { p.Age newAge // 修改原结构体 } func main() { s : Student{Name: Frank, Age: 18} s2 : updateName(s, Franklin) fmt.Println(s.Name) // Frank没变 fmt.Println(s2.Name) // Franklin updateAge(s, 19) fmt.Println(s.Age) // 19变了 }如果结构体很大建议使用指针传递避免复制开销。五、嵌套结构体结构体的字段可以是另一个结构体类型形成嵌套。Go 支持“组合”而非继承。type Address struct { City, Province string } type Person struct { Name string Age int Address Address // 嵌套 } func main() { p : Person{ Name: Grace, Age: 30, Address: Address{ City: Shanghai, Province: Shanghai, }, } fmt.Println(p.Address.City) // 访问嵌套字段 }如果经常访问嵌套字段Go 提供了匿名嵌套嵌入可以省略字段名直接提升字段type Person struct { Name string Age int Address // 匿名嵌套类型名即字段名 } func main() { p : Person{ Name: Henry, Age: 25, Address: Address{City: Beijing, Province: Beijing}, } // 可以直接访问提升的字段 fmt.Println(p.City) // Beijing直接从Address提升 fmt.Println(p.Address.City) // 也可以显式 }但注意如果外层也有同名字段则外层优先此时需显式指定。六、结构体比较如果结构体的所有字段都是可比较的例如 int、string、bool 等那么结构体本身就可以直接使用和!进行比较。比较结果是逐个字段比较的结果。type Point struct { X, Y int } p1 : Point{1, 2} p2 : Point{1, 2} p3 : Point{2, 3} fmt.Println(p1 p2) // true fmt.Println(p1 p3) // false如果结构体包含不可比较的字段如 slice、map则结构体不可比较只能与 nil 比较如果是指针。七、结构体与JSON结构体经常与JSON配合使用通过标签tag可以控制字段在序列化时的名称和行为。import encoding/json type User struct { ID int json:id Username string json:username Password string json:- // 忽略该字段 } func main() { u : User{ID: 1, Username: alice, Password: 123456} data, _ : json.Marshal(u) fmt.Println(string(data)) // {id:1,username:alice} }标签是反引号包裹的键值对常用作json、xml、db等库的配置。八、结构体方法预览虽然下一篇可能会详细讲方法但结构体常常与“方法”捆绑。方法就是绑定在结构体类型上的函数接收者可以是指针或值。type Rectangle struct { Width, Height float64 } // 值接收者不修改原值 func (r Rectangle) Area() float64 { return r.Width * r.Height } // 指针接收者可修改 func (r *Rectangle) Scale(factor float64) { r.Width * factor r.Height * factor } func main() { rect : Rectangle{10, 5} fmt.Println(rect.Area()) // 50 rect.Scale(2) fmt.Println(rect.Area()) // 200 }这是Go实现“面向对象”的主要方式。九、结构体的内存布局结构体的字段在内存中是连续排列的但为了提高访问效率编译器会添加一些填充字节padding进行对齐。了解这一点有助于优化内存使用但平时不必过度关注。十、空结构体struct{}不包含任何字段它的大小为0字节。常用于占位或通道信号。var empty struct{} fmt.Println(unsafe.Sizeof(empty)) // 0常与 map 结合用作集合set : make(map[string]struct{}) set[item] struct{}{} // 只关心键存在性 _, ok : set[item] // 检查是否存在十一、综合示例学生信息管理系统package main import ( fmt ) type Student struct { ID int Name string Scores map[string]float64 // 科目→成绩 } // 计算平均分 func (s Student) Average() float64 { if len(s.Scores) 0 { return 0 } total : 0.0 for _, score : range s.Scores { total score } return total / float64(len(s.Scores)) } // 添加成绩指针接收者修改map func (s *Student) AddScore(subject string, score float64) { if s.Scores nil { s.Scores make(map[string]float64) } s.Scores[subject] score } func main() { stu : Student{ID: 1, Name: Alice} stu.AddScore(Math, 90.5) stu.AddScore(English, 88.0) fmt.Printf(%s 的平均分: %.2f\n, stu.Name, stu.Average()) // JSON示例略 }输出Alice 的平均分: 89.25十二、常见陷阱与注意事项结构体是值类型赋值或作为参数传递会复制整个结构体除非使用指针。因此要修改原结构体必须使用指针。大结构体指针传递更高效避免复制开销。nil结构体指针访问字段会 panic使用前确保指针非空。字段命名冲突嵌套结构体时如果外层有同名字段必须显式指定层级。结构体不能包含自身不能递归定义但可以包含指向自身类型的指针用于链表、树等。结构体比较的字段限制若包含不可比较字段slice、map则结构体不可直接比较需自定义比较逻辑。十三、总结今天我们学习了Go语言中的结构体结构体是多个不同类型字段的复合类型使用type Name struct { ... }定义。初始化方式零值、字面量推荐字段名方式、new或。访问和修改字段使用.指针也可直接用.。嵌套结构体可实现组合匿名嵌套可提升字段。结构体作为函数参数时值传递拷贝指针传递可修改原值。结构体可比较若字段都可比较可配合JSON标签序列化。结构体可以有方法接收者分为值接收者和指针接收者方法将在后续详细讲解。空结构体struct{}大小为0常用作集合信号。结构体是Go中组织数据的最基本单位也是后续学习接口、包管理的基础。掌握了结构体你就能写出更贴近现实世界模型的代码。如果你对今天的讲解有任何疑问或者想进一步探索欢迎在评论区留言。坚持从零开始我们一步步掌握Go语言感谢阅读我们下篇再见如果我的内容对你有帮助请点赞评论收藏。创作不易大家的支持就是我坚持下去的动力