结构体:Go 世界里的「类」,以及嵌套与嵌入之分¶
这一章讲 Go 里最重要的自定义类型:结构体。 它是 Go 对「类」的回答——有数据、能打包,但不带继承。 读完你会:熟练声明和初始化结构体,分清「嵌套」和「嵌入」这两个一字之差、 机制完全不同的概念(后者是 Go 组合式设计的原子),并掌握结构体的值语义。
1. 结构体是什么¶
书里给的定义:结构体是用户定义的复合数据类型,把若干不同类型的变量 定义在同一块内存里,合成一个类型1。
书里还说了一句定位的话:struct 在 Go 里的角色和面向对象语言里的类相同, 区别是类支持继承,结构体支持组合2——这句话是第 11、12 两章的总纲。
声明用 type ... struct:
type 关键字告诉编译器:后面是一个新类型。每个字段是「名字 + 类型」,
也可以不写名字只写类型(见第 5 节匿名结构体)。
2. 主线例子:一个 student 的完整一生¶
本章主线,照原书的 student 走完全程。
出生——声明变量。 结构体变量的零值是「每个字段各取零值」:
零值的规律和第 3 章一致:Go 不会给你留随机数据,新结构体永远是干净的3。
落户——初始化,两种姿势。
姿势一,按位置给值(顺序必须和字段声明一致):
书里给过警告:顺序不对,编译错4。姿势二,按字段名给值(推荐):
按名字初始化可以乱序、可以漏字段——可读性和可维护性都更好,日常写码默认用这种4。
日常使用——点号访问字段,结构体是可变的:
点号既能读也能写,所以书里说 struct 是 mutable(可变的)—— 注意这和字符串的不可变形成对照:结构体本身可以改,字符串不能5。
复制与传参——值语义:
书里演示了 getValue(student_copy1 *student) 里改 student_copy1.name = "Manish",
外面的副本跟着变——和拆解 09 的指针改值是同一个机制6。
比较——可判等:
student{...rollno: 11} == student{...rollno: 11} // true
student{...rollno: 11} == student{...rollno: 0} // false
// {rahul jaipur 0} == {rahul jaipur} // true,零值补全后相等
两个结构体相等 = 类型相同 + 每个字段的值都相等;漏写的字段按零值参与比较7。
3. 指针与结构体:new 和一个语法糖¶
创建指向结构体的指针有两条路,书里都给了8:
s := student{"rahul", "jaipur", 67}
p1 := &s // 先造变量,再取地址
p2 := &student{"rahul", "jaipur", 67} // 一步到位
p3 := new(student) // new 分配内存,字段全是零值
new(student) 返回的就是 *student——new 永远返回指针,
p3.city = "jaipur" 直接改的是那块新内存9。
访问字段有一个重要的语法糖,书里并排演示了两种写法:
对结构体指针用点号,Go 自动解引用——所以日常代码里几乎见不到 (*p).field 的写法。
(注意:这个糖只对结构体有效;普通变量的指针仍要写 *p。)
4. 嵌套 vs 嵌入:一字之差,两个机制¶
这是本章最关键的一对概念,书里用同一个 result(数学/科学/英语成绩+是否及格)
放在 student 里做了两种版本10:
嵌套(nested)——内层结构体是外层的一个普通字段,有名字:
type student struct {
name, city string
rollno int
result result // 字段名就叫 result,类型也叫 result
}
a := student{..., result: result{45, 78, 90, true}}
fmt.Println(a.result.isPass) // 访问要过两层:外层字段名.内层字段名
嵌入(embedded)——内层结构体不带字段名,字段被「提升」:
type student struct {
name, city string
rollno int
result // 只有类型,没有字段名——这就是嵌入
}
a := student{"rahul", "jaipur", 11, result{45, 78, 90, true}}
fmt.Println(a.isPass) // 直接访问内层字段!不用写 a.result.isPass
图说:嵌套是「书包里装着一个文具盒」,拿笔要开两层; 嵌入是「文具盒的格子直接长在书包上」,拿笔只要开一层。 书里把嵌入的效果叫字段提升(promote):内层字段被提升为外层可直接访问的字段10。
嵌入就是 Go 的「组合式继承」:第 12 章会讲,嵌进去的结构体的能力 (字段、方法)整个归外层使用——这就是 Go 没有 class 继承之后, 复用代码的标准姿势。
判断(我们的,不是书里的): 「嵌入」是 Go 最被低估的设计之一: 语法上只是少写一个字段名,机制上却给了完整的代码复用。 但书里也点到它的代价——命名冲突:外层和内层有同名字段时, 外层的优先,内层的被遮蔽,想访问内层得写全
a.result.isPass。 嵌入层级多了之后,字段「从哪一层来」会变难追,嵌两层以上要慎重。 如果错,会错在: 有些团队(包括 Go 官方的部分代码风格讨论)主张 只用嵌入做「满足接口」的委托,不拿它共享数据——这是风格之争, 嵌入的机制能力本身不受影响。
5. 匿名结构体与匿名字段¶
匿名结构体:不为结构体起类型名,直接声明变量,一次性的形状用它:
它和有名字的 struct 完全同权:能初始化、能比较、能取指针11。
匿名字段:字段不给名字,只写类型,类型名就是字段名:
type student struct {
string // 字段名就是 "string"
int // 字段名就是 "int"
}
a := student{"rahul", 67}
fmt.Println(a) // → {rahul 67}
规矩:一个结构体里每种类型只能匿名一次——两个 string 匿名字段,
编译错 string redeclared;同类型的第二个字段必须起名字12。
6. 作者的判断与证据¶
| 书里的说法 | 性质 |
|---|---|
| 「struct 类似类,区别是继承 vs 组合」 | 准确的定位,Go 官方 FAQ 的口径 |
| 「嵌入更干净,但可能引起命名冲突」 | 事实陈述,克制且属实 |
7. 边界与局限¶
- 方法没有登场:给结构体绑函数(方法)是第 13 章接口和第 12 章组合的前置, 本章的结构体只有数据。看完第 12 章再回头,struct 才是完整的「类替代品」;
- 结构体标签(tag,如 JSON 用的反引号标记)本章没讲——序列化(把内存里的 数据转成可存储、可传输的文本)和反序列化都靠它,那是结构体 在真实项目里的高频用法(补充,不在书里);
- 嵌入的指针形式(嵌入
*result)、多层嵌入的冲突规则本章未展开,第 12 章会补指针嵌入。
8. 可带走的¶
- 结构体 = 若干字段捆成一个类型;
type T struct { 字段 类型 ... }; - 结构体变量的零值 = 每个字段各取零值,永远是干净的;
- 初始化两种姿势:按位置(顺序必须对)和按字段名(推荐,可乱序可漏);
- 点号读写字段,结构体可变;字符串不可变——对照记;
- 结构体是值类型:赋值复制整份;函数想改它,传
&s; - 指向结构体的指针:
&T{...}或new(T);指针用点号访问字段时 Go 自动解引用; - 嵌套 vs 嵌入:嵌套要过两层(
a.result.isPass),嵌入字段提升一层直达(a.isPass); - 嵌入 = Go 组合式继承的原子,代价是可能命名冲突;
- 匿名结构体一次性使用;匿名字段类型即字段名,每种类型只能匿名一次;
- 结构体可判等:类型相同 + 逐字段相等(漏写按零值)。
9. 原文地图¶
| 主题 | 原书章 | 原文位置 |
|---|---|---|
| 结构体定义、类与组合的区分 | Structures | text/19-fm-learning-go-programming.txt:64(搜「composite data type」) · text/19-fm-learning-go-programming.txt:994(搜「classes support inheritance」) |
| 声明与零值 | Structures | text/19-fm-learning-go-programming.txt:100(搜「type student struct」) · text/19-fm-learning-go-programming.txt:178(搜「{ 0}」) |
| 两种初始化 | Structures | text/19-fm-learning-go-programming.txt:229(搜「student{"rahul", "jaipur", 67}」) · text/19-fm-learning-go-programming.txt:262(搜「rollno:67」) |
| 点号访问与可变性 | Structures | text/19-fm-learning-go-programming.txt:322(搜「student.city」) |
| 指针与结构体、自动解引用 | Structures | text/19-fm-learning-go-programming.txt:385(搜「p1 := &student_var」) · text/19-fm-learning-go-programming.txt:394(搜「(*p1).city」) |
| new 与结构体 | Structures | text/19-fm-learning-go-programming.txt:475(搜「new(student)」) · text/19-fm-learning-go-programming.txt:487(搜「{ jaipur 67}」) |
| 匿名字段与重复报错 | Structures | text/19-fm-learning-go-programming.txt:514(搜「type as the name」) · text/19-fm-learning-go-programming.txt:571(搜「string redeclared」) |
| 嵌套结构体 | Structures | text/19-fm-learning-go-programming.txt:646(搜「Nested struct as a field」) · text/19-fm-learning-go-programming.txt:685(搜「a.result.isPass」) |
| 嵌入结构体与字段提升 | Structures | text/19-fm-learning-go-programming.txt:730(搜「11, result{45, 78, 90, true}」) · text/19-fm-learning-go-programming.txt:733(搜「{rahul jaipur 11 {45 78 90 true}}」) |
| 复制、传地址给函数 | Structures | text/19-fm-learning-go-programming.txt:811(搜「new copy is created」) · text/19-fm-learning-go-programming.txt:868(搜「getValue(&student_copy)」) |
| 结构体比较 | Structures | text/19-fm-learning-go-programming.txt:934(搜「student==student1」) · text/19-fm-learning-go-programming.txt:985(搜「student3==student4」) |
-
出处:「Structures」第 64 段(
text/19-fm-learning-go-programming.txt:64,搜「composite data type」)。 ↩ -
出处:「Structures」第 994 段(
text/19-fm-learning-go-programming.txt:994,搜「classes support inheritance」)。 ↩ -
出处:「Structures」第 175–199 段(
text/19-fm-learning-go-programming.txt:178,搜「{ 0}」;text/19-fm-learning-go-programming.txt:199,搜「default values for every field」)。 ↩ -
出处:「Structures」第 229–265 段(
text/19-fm-learning-go-programming.txt:229,搜「student{"rahul", "jaipur", 67}」;text/19-fm-learning-go-programming.txt:238,搜「same order」;text/19-fm-learning-go-programming.txt:262,搜「rollno:67」)。 ↩↩ -
出处:「Structures」第 301–352 段(
text/19-fm-learning-go-programming.txt:328,搜「{ jaipur 67}」)。 ↩ -
出处:「Structures」第 808–871 段(
text/19-fm-learning-go-programming.txt:811,搜「new copy is created」;text/19-fm-learning-go-programming.txt:829,搜「Manish」;text/19-fm-learning-go-programming.txt:868,搜「getValue(&student_copy)」)。 ↩ -
出处:「Structures」第 874–985 段(
text/19-fm-learning-go-programming.txt:934,搜「student==student1」;text/19-fm-learning-go-programming.txt:985,搜「student3==student4」)。 ↩ -
出处:「Structures」第 358–448 段(
text/19-fm-learning-go-programming.txt:385,搜「p1 := &student_var」;text/19-fm-learning-go-programming.txt:403,搜「p2 := &student」)。 ↩ -
出处:「Structures」第 451–508 段(
text/19-fm-learning-go-programming.txt:475,搜「new(student)」;text/19-fm-learning-go-programming.txt:487,搜「{ jaipur 67}」)。 ↩ -
出处:「Structures」第 604–805 段(
text/19-fm-learning-go-programming.txt:607,搜「promotes their fields」;text/19-fm-learning-go-programming.txt:685,搜「a.result.isPass」;text/19-fm-learning-go-programming.txt:733,搜「{rahul jaipur 11 {45 78 90 true}}」)。 ↩↩ -
出处:「Structures」第 133–199 段(
text/19-fm-learning-go-programming.txt:133,搜「anonymous structure」)。 ↩ -
出处:「Structures」第 511–598 段(
text/19-fm-learning-go-programming.txt:571,搜「string redeclared」;text/19-fm-learning-go-programming.txt:598,搜「{rahul jaipur 67}」)。 ↩