map、结构体与指针¶
这一章回答一个问题:切片不够用之后,数据往哪放、怎么组织。 三个答案各占一段:map(用任意可比较的键查值,底层是哈希表)、 结构体(把相关字段捆成一个自定义类型,Go 面向对象的全部真相都在这里)、 指针(直接操纵内存地址,函数的参数从此分「拷一份」和「指过去」两种世界)。 读完你会看懂这本书此后每一行数据建模代码。
1. 这一章在原书里的位置¶
原书第 2 章(Data Collections and Functions)是「Go 快速入门」的第二块。 第 1 章给了原子类型和切片,这一章把「复合数据」补完——然后全书就再也不回头讲基础了。 判断这章的读法:map 一节快读(你会的都覆盖),结构体一节慢读(这是全章重心, 原书在这里一口气演了一个程序的 14 个面貌),指针一节中速(链表例子值得跟着走一遍)。
2. 顶层全景¶
map → 键值对 + 哈希表:O(1) 查找,顺序随机,是引用类型(§3)
struct → 14 个面貌:定义/嵌入/方法/私有字段/接口/tag/匿名/空结构体(§4–§8)
指针 → & 和 *,传引用改原件,结构体+指针=链表(§9–§10)
图说:三种机制一个共同主题——谁和谁共享同一份数据。map 和切片共享,结构体默认拷备, 指针显式共享。整章其实就是这一句话的三种展开。
3. map:哈希表是内置类型¶
map 解决的问题: 切片只能用从 0 开始的连续整数当下标。哪怕你只想用下标 99, 切片也得把 0–98 全占着1。map 把下标推广成键:字符串、整数、布尔都可以, 条件是「这个类型能用 == 和 != 比较」——不能比较的类型(比如切片)不能当键2。 底层实现是哈希表,插入、删除、查找的平均复杂度都是 O(1)(常数时间)3。
一条主线走完全部规则:学生成绩表¶
书上用一个 grades map[string]int(学生名字 → 分数)把 map 的全部日常操作串起来,
我们照原书顺序走,每一步是机制,旁边是具体值:
第 1 步,创建。 三种写法,三种脾气:
var grades map[string]int // (a) 声明不初始化 → nil map,还不能用
scores := make(map[string]int) // (b) make 造空 map,能用
inventory := map[string]int{ // (c) 字面量,带初始值
"apples": 50, "bananas": 30, "oranges": 25,
}
grades = make(map[string]int) // nil map 必须先 make 才能往里放东西
nil map 是个坑:对 nil map 读是安全的(返回零值),但往里写直接 panic。 所以「声明了没初始化」和「空 map」是两回事4。
第 2 步,增和改。 grades["Marietta"] = 95、grades["Athina"] = 92;
键已存在再赋就是改:grades["Marietta"] = 985。输出确认:
Grades after adding: map[Athina:92 Epifanios:87 Marietta:95],更新后 Marietta 变 98。
第 3 步,读不存在的键——本章最重要的机制。
grades["David"] 在 David 不存在时不报错,返回 int 的零值 06。
输出里一行 David's grade (doesn't exist): 0。这带来一个真问题:
「键不存在」和「键存在但值就是 0」分不出来。解法是 comma ok 惯用法:
if grade, exists := grades["David"]; exists {
fmt.Printf("David exists with grade: %d\n", grade)
} else {
fmt.Printf("David doesn't exist in grades map\n")
}
grades["David"] 其实返回两个值:值本身,和一个「在不在」的布尔。
一次取值两个结果都拿到,这就是这一行在 if 里的写法的全部内容7。
输出:David doesn't exist in grades map——而 Marietta 走同一个判断进 true 分支。
第 4 步,删。 delete(grades, "Epifanios");删不存在的键安全,什么都不会发生8。
第 5 步,遍历——顺序是随机的。 for name, grade := range grades 同时拿键值;
只拿键、只拿值也都有写法。但 map 的遍历顺序是随机化的,两次运行打印顺序可能不同,
想要确定输出就得把键捞出来排序再按序取值9。
第 6 步,map 是引用类型。 copy := original 之后两边是同一张表:
往 copy 里加 "c": 3,original 也变成 map[a:1 b:2 c:3]10。
这和切片一样、和数组/结构体相反——第二章开头的「谁和谁共享」在这里又出现一次。
第 7 步,两个常用技巧。 一是「map 当集合」:Go 没有集合类型,
map[string]struct{}(值为空结构体,零字节)去重,键自动唯一11;
二是「key 排序后遍历」换确定性输出,书上用商品名排序示范12。
嵌套 map(map[string]map[string]int,学生→科目→分数)做层级数据,一笔带过13。
4. 结构体(一):定义、初始化、嵌入——Go 的「组合式继承」¶
结构体(struct)把不同类型的字段捆成一个类型:一个 Person 有 Name(string)、 Age(int)、City(string)14。Go 没有类,结构体就是全部—— 原书用一个大程序从 14 个角度演示,我们合并成四个值得慢读的面貌。
面貌一:嵌入——没有「继承」关键字,字段被「提升」上来¶
type Person struct { Name string; Age int; City string }
type Address struct { Street, City, State, ZipCode string }
type Employee struct {
Person // 匿名字段:只写类型名,不写字段名
Address // 第二个匿名字段
ID int; Department string; Salary float64; IsManager bool
}
Employee 里直接写 Person 和 Address 类型名,这叫嵌入(embedding)。
效果是字段提升:emp.Name 直接读到 Person 里的 Name,emp.Street 直接读到
Address 里的 Street,不用写 emp.Person.Name15。
但这里埋着本章最值钱的一个细节:Person 和 Address 都有 City 字段。
提升规则遇上同名会撞车——emp.City 变得有歧义,编译器直接报错,
必须显式写 emp.Person.City 或 emp.Address.City 二选一16。
Employee
├── Person {Name, Age, City} ─┐
├── Address {Street, City, …} ─┤ City 撞名 → emp.City 编译报错,必须写全路径
└── 自己的 {ID, Department, Salary, IsManager}
图说:嵌入 = 字段提升;提升撞名时,显式路径是唯一的解法。
这就是 Go 对「继承」的回答。 别的语言给子类一条 is-a 链, Go 给的是「把零件嵌进来,字段自动提到外层;撞名了就写全路径」—— 组合(composition)优先于继承,且没有任何隐式的优先级仲裁。
面貌二:初始化与值的脾气¶
Person 有四种造法:var p1 Person(零值:Name 空串、Age 0、City 空串)、
p2 := Person{}(同上,意图更显式)、
p3 := Person{"Alice", 30, "New York"}(位置初始化:按声明顺序,一个不能少)、
p4 := Person{Name: "Bob", Age: 25, City: "Boston"}(命名字段:顺序随意,可只给一部分)17。
%+v 打印会连字段名带值一起列出——调试结构体的日常姿势。
结构体是值类型:point4 := point1 拷一份,改 point4.X 不影响 point1;
全字段可比较的结构体可以直接 == 逐字段比18。
注意书里一句关键小字:「only primitive data types are copied」——
如果字段里有切片或指针,拷贝的是那个引用,两边仍共享同一份底层数据19。
「结构体拷贝 = 深拷贝」是错觉,这句话是对 02 号切片那章的呼应。
面貌三:方法、接收者与「没有构造器」的构造器¶
方法就是一个函数,只是签名前面多了一个接收者(receiver):
func (r Rectangle) Area() float64 { return r.Width * r.Height } // 值接收者:拿到的是拷贝
func (r *Rectangle) Scale(f float64) { r.Width *= factor; r.Height *= factor } // 指针接收者:拿到的是地址,改原件
rect.Scale(2) 前后对比是教科书式的:前 {Width:10 Height:5},后 {Width:20 Height:10};
而值接收者的 Area() 算归算,rect 纹丝不动20。判据一句话:只读用值接收者,要改用指针接收者。
Go 还替你自动补全:rect1.Scale(1.5) 会被编译器悄悄改写成 (&rect1).Scale(1.5),
值和指针两种调用方式对两种接收者都能用(前提:值得有固定地址,临时字面量不行)21。
Go 没有构造器关键字,惯例是自己写一个 New 开头的函数(constructor pattern):
type BankAccount struct {
AccountNumber string // 大写 → 导出,包外可见
balance float64 // 小写 → 未导出,只有本包能碰
ownerName string // 小写 → 同上
}
func NewBankAccount(accountNumber, ownerName string, initialBalance float64) *BankAccount {
return &BankAccount{AccountNumber: accountNumber, balance: initialBalance, ownerName: ownerName}
}
配上 Deposit/Withdraw 方法(里面校验正数、校验余额),一套封装就齐了:
balance 谁也碰不到,只能从方法进出;Withdraw(2000) 超支时报
Invalid withdrawal: insufficient funds,状态不变22。
Go 的「面向对象」三件套到此全部亮相:结构体当数据、方法挂行为、小写 + New 函数做封装。
面貌四:隐式接口、struct tag、匿名与空结构体¶
隐式接口是 Go 最大的差异化设计,值得单独看清楚:
type Shape interface { // 接口 = 一份方法清单
Area() float64
Perimeter() float64
}
func (c Circle) Area() float64 { return 3.14159 * c.Radius * c.Radius }
func (c Circle) Perimeter() float64 { return 2 * 3.14159 * c.Radius }
// Circle 从头到尾没说过自己实现了 Shape——不需要说
只要一个类型的方法集合凑齐了清单,它就自动满足接口,不需要 implements 声明。
直接后果:shapes := []Shape{Rectangle{...}, Circle{...}} 把两种具体类型装进同一个切片,
shape.Area() 在 Rectangle 上算矩形、在 Circle 上算圆——多态,但没有任何继承语法23。
(%T 打印出 main.Rectangle/main.Circle,说明接口变量心里始终知道自己装的是谁。)
struct tag:字段后面反引号里的一串元数据,别的库来读:
type User struct {
ID int `json:"id" db:"user_id" validate:"required"`
Password string `json:"-" db:"password_hash"` // json:"-" = 序列化时整个字段消失
FullName string `json:"full_name,omitempty" db:"name"` // omitempty = 零值时不进 JSON
}
json.Marshal(user) 的输出里,Password 消失了(这就是为什么 API 响应里永远不该有密码),
FullName 变成 snake_case 的 full_name,空值时连字段都不出现24。
反射(reflect 包,第 4 章细讲)能在运行期把这些 tag 逐个读出来25。
匿名结构体:类型定义直接写在字段位置上,不另起名。
Config 里的 Database 和 Server 都是匿名结构体——两个「Port」字段不冲突,
因为各自有自己的命名空间 config.Database.Port / config.Server.Port26。
空结构体 struct{}:零字节。unsafe.Sizeof(struct{}{}) 报 0。
所以「map 当集合」用 map[string]struct{}:值的位置不花一分钱内存,
纯靠键的存在性表示「在集合里」27。
5. 结构体(二):作者演示的顺序里藏着的话¶
值得点破:原书把 BankAccount(封装)排在接口之前、tag 排在匿名结构体之前,这个顺序不是随机的。 它实际讲的是 Go 类型系统的能力梯度:值语义 → 方法 → 访问控制 → 抽象(接口)→ 元数据(tag)→ 一次性结构(匿名)→ 零成本标记(空结构体)。能力一种比一种「省」,代价一种比一种「特定场合才值」。 后四种(接口、tag、匿名、空结构体)各自的代价是:接口丢静态类型信息、tag 把契约挪出了类型系统、 匿名结构体不可复用、空结构体不能带数据。
判断(我们的,不是书里的): 这一章(尤其结构体一节)是全书语言部分信息密度最高的地方, 而且它不是「14 个知识点」,是一条能力梯度。复述这一章的正确方式是说清梯度, 而不是背 14 个名字——这也应该是读任何语言特性清单的姿势。 如果错,会错在: 作者也许只是按教学惯例从简单排到复杂;「能力梯度」是我们后加的组织方式, 原书没有明说,尽管排序事实支持它。
6. 指针:&、* 与传引用¶
指针两个运算符:&x 取 x 的内存地址,*p 取地址里存的值(解引用)。
书上第一个例子一句话讲完机制:
func updateValue(ptr *int) { *ptr = *ptr + 5 }
// main 里:x := 10;updateValue(&x)
// 输出:Before: 10 → After: 15
&x 把 x 的地址递进去,函数里 *ptr = *ptr + 5:右边读出 10、加 5,
左边把 15 写回同一个内存位置——main 里的 x 就这么被改了28。
没有指针,函数拿到的参数永远是一份拷贝,改拷贝动不了原件。
这一节还预告了 new 和 make 的分工(new 返回指针、make 只用于切片/map/channel), 但详细的对比表原书放在第 4 章(见 05 号拆解的「make 和 new」一节)。
7. 主线例子(二):一条链表,把指针和结构体缝起来¶
指针的经典用例是自引用数据结构。书上实现了单链表,两个类型:
type Node struct { Value int; Next *Node } // Next 指向同类型的下一个节点,nil 表示到头
type LinkedList struct { Head *Node } // Head 为 nil 表示空表
Next *Node 是结构体里装着自己的指针——值类型做不到这个,会陷入无限嵌套,
指针让「节点指向下一个节点」成为可能。四个方法跟着走一遍(插入 5、3、8、1)29:
Insert(追加到尾): 空表(Head == nil)新节点直接当 Head;
否则从头走到 current.Next == nil 的最后一个节点,把它的 Next 指向新节点。
输出 5 -> 3 -> 8 -> 1 -> nil。
Delete(3): 先特判——要删的恰是 Head(把 Head 挪到下一个);否则两个指针并排走:
prev 落后 curr 一步,找到 curr.Value == 3 时执行 prev.Next = curr.Next——
被删节点并没有被释放操作,只是从链上摘了出去(垃圾回收稍后会发现没人再指向它,
这正是第 4 章 GC 的活)。输出 5 -> 8 -> 1 -> nil。
Sort(冒泡): 不搬节点,只交换相邻节点的 Value 字段;
swapped 标志控制「整趟没换过就停」。输出 1 -> 5 -> 8 -> nil。
Print: 从头走到 nil,每个值后面打 ->,结尾打 nil。
插入: 5 → 3 → 8 → 1 → nil
删 3: 5 → 8 → 1 → nil (prev.Next 跳过 curr,curr 悬空待回收)
排序: 1 → 5 → 8 → nil (只换值,不挪节点)
图说:三个操作各动一次链接,指针的指向变化就是算法的全部状态。
图说:链表的全部难度不在逻辑,在「谁先指向谁」的顺序——Delete 里先挪 prev 还是先挪 curr, 写反一行就是断链。这也是指针作为「必须显式管理的关系」的最好教材。
8. 作者的判断与证据¶
| 判断 | 性质 | 依据 |
|---|---|---|
| map 的零值返回 + comma ok 是处理缺失数据的标准方式 | 语言惯例,作者作为「惯用法」教 | 原文明确称之为 comma ok idiom7 |
| 嵌入字段提升撞名必须显式限定 | 语言规则,作者给了反例 | Person/Address 双 City 的说明16 |
| 值接收者只读、指针接收者改写 | 语言规则 + 社区惯例 | Scale 前后对比输出20 |
| 隐式接口让多态不需要继承 | Go 的核心设计主张 | 「differs fundamentally from explicit interface implementation」23 |
| 空结构体零字节、适合做集合 | 语言事实 | unsafe.Sizeof 输出 0 bytes27 |
9. 边界与局限¶
- map 一节没讲并发。 map 不是并发安全的,两个 goroutine 同时写会 panic, 本章只字未提——第 3 章讲并发时才补(sync.Map 也没进那章)。
- 链表是纯教学示例。 生产代码里切片几乎总是更好的选择(缓存友好、少一次间接寻址); 书上没给「什么时候真需要链表」的判断标准。
- 结构体对齐与内存布局(struct padding)没讲。 unsafe.Sizeof 露了一面就收了, 而系统编程里结构体内存布局常是性能关键。
- 接口的「意外实现」风险没提。 隐式实现意味着任何类型凑巧有同名同签名方法 就满足了接口——方便与脆弱同根,原书只讲了方便那半。
10. 可带走的¶
- map 查不存在的键返回零值不报错;分「不存在」和「值是零」用 comma ok。
- nil map 不能写;
delete删不存在的东西是安全的;遍历顺序随机。 - map 和切片是引用类型(共享),数组和结构体是值类型(拷贝)——但结构体里的切片/指针字段拷的仍是引用。
- 嵌入 = 字段提升;提升撞名必须写全路径;Go 用组合替继承。
- 方法接收者:只读值、改写指针;Go 自动补 & 和 *,但临时字面量取不了地址。
- 没有构造器,惯例是
NewXxx()返回*T;小写字段 + 方法 = 封装。 - 接口靠「凑齐方法」隐式满足,不用声明;这是 Go 多态的全部机制。
- struct tag 驱动序列化:
json:"-"隐藏字段、omitempty跳空值——API 里不漏密码就靠它。 - 空结构体
struct{}零字节,map[string]struct{}是去重集合的惯用法。 - 指针基本功:
&取地址、*解引用;自引用结构体(链表/树)只能靠指针。
11. 原文地图¶
| 主题 | 原书章 | 原文位置 |
|---|---|---|
| 为什么需要 map(切片下标的局限) | Understanding and using maps | text/29-fm-understanding-and-using-maps.txt:2(搜「positive integers as indexes」) |
| 可比较才能当键、哈希表 O(1) | Understanding and using maps | text/29-fm-understanding-and-using-maps.txt:8(搜「comparable」) · text/28-ch02.txt:13(搜「hash tables」) |
| nil map 与三种创建方式 | Understanding and using maps | text/29-fm-understanding-and-using-maps.txt:31(搜「nil map」) |
| 零值返回与 comma ok | Understanding and using maps | text/29-fm-understanding-and-using-maps.txt:71(搜「zero value」) · :75(搜「comma ok idiom」) |
| delete、遍历随机、引用类型、排序遍历、map 当集合 | Understanding and using maps | text/29-fm-understanding-and-using-maps.txt:106(搜「deleting a non-existent key」) · :127(搜「randomized」) · :181(搜「Reference Types」) · :203(搜「Sorting Map Keys」) · :228(搜「Using Maps as Sets」) |
| 嵌入与字段提升、City 撞名 | Understanding the versatility of structures | text/30-fm-understanding-the-versatility-of-structures.txt:92(搜「Embedded Fields」) · :124(搜「becomes ambiguous」) |
| 四种初始化、值语义、拷贝只拷原始字段 | Understanding the versatility of structures | text/30-fm-understanding-the-versatility-of-structures.txt:379(搜「Zero value initialization」) · :869(搜「independent copy」) · :871(搜「only primitive data types are copied」) |
| 值/指针接收者、New 构造器、BankAccount 流程 | Understanding the versatility of structures | text/30-fm-understanding-the-versatility-of-structures.txt:139(搜「Value receiver」) · :191(搜「Constructor function」) · :1049(搜「Invalid withdrawal」) |
| 隐式接口与 Shape 多态 | Understanding the versatility of structures | text/30-fm-understanding-the-versatility-of-structures.txt:247(搜「Interface Implementation」) · :273(搜「no explicit declaration」) · :649(搜「slice of Shape」) |
| struct tag 与 JSON、反射读 tag | Understanding the versatility of structures | text/30-fm-understanding-the-versatility-of-structures.txt:309(搜「special json」) · :699(搜「json.Marshal」) · :707(搜「reflect.TypeOf」) |
| 匿名结构体与命名空间隔离 | Understanding the versatility of structures | text/30-fm-understanding-the-versatility-of-structures.txt:325(搜「Anonymous Structs」) · :828(搜「namespace isolation」) |
| 空结构体零字节 | Understanding the versatility of structures | text/30-fm-understanding-the-versatility-of-structures.txt:943(搜「Empty struct size」) |
| 指针基础 &x/*ptr | Pointers | text/31-fm-pointers.txt:45(搜「func updateValue」) · :37(搜「Before」) |
| 链表 Insert/Delete/Sort/Print | Pointers | text/31-fm-pointers.txt:77(搜「type Node struct」) · :94(搜「func (l *LinkedList) Insert」) · :107(搜「func (l *LinkedList) Delete」) · :147(搜「bubble sort」) · text/32-fm-summary.txt:16(搜「5 -> 3 -> 8 -> 1」) |
-
出处:「Understanding and using maps」第 2–6 段(
text/29-fm-understanding-and-using-maps.txt:2,搜「positive integers as indexes」)。「even if you want to put data in the slice element at index 99 only, the slice is still going to occupy 100 elements」。 ↩ -
出处:「Understanding and using maps」第 7–9 段(
text/29-fm-understanding-and-using-maps.txt:8,搜「comparable」)。「if a type cannot be compared using == and !=, you cannot use it as a map key」。 ↩ -
出处:「Data Collections and Functions」(
text/28-ch02.txt:13,搜「hash tables」)。「Go implements maps as hash tables, providing average O(1) time (constant) complexity」。 ↩ -
出处:「Understanding and using maps」第 29–35 段(
text/29-fm-understanding-and-using-maps.txt:31,搜「nil map」)。「If you start with a nil map, you must initialize it using make before adding elements」。 ↩ -
出处:「Understanding and using maps」第 42–58 段(
text/29-fm-understanding-and-using-maps.txt:51,搜「grades["Marietta"] = 98」;:303,搜「Grades after adding」)。 ↩ -
出处:「Understanding and using maps」第 68–72 段(
text/29-fm-understanding-and-using-maps.txt:71,搜「zero value for the value type」)。「If the key is missing, Go returns the zero value」。 ↩ -
出处:「Understanding and using maps」第 74–91 段(
text/29-fm-understanding-and-using-maps.txt:75,搜「comma ok idiom」;:313,搜「David doesn't exist」)。 ↩↩ -
出处:「Understanding and using maps」第 94–106 段(
text/29-fm-understanding-and-using-maps.txt:106,搜「deleting a non-existent key is safe」)。 ↩ -
出处:「Understanding and using maps」第 124–128 段(
text/29-fm-understanding-and-using-maps.txt:127,搜「randomized」)。「map iteration order in Go is randomized」。 ↩ -
出处:「Understanding and using maps」第 181–195 段(
text/29-fm-understanding-and-using-maps.txt:181,搜「Reference Types」;:347,搜「After modifying copy」)。 ↩ -
出处:「Understanding and using maps」第 223–246 段(
text/29-fm-understanding-and-using-maps.txt:228,搜「Using Maps as Sets」)。map[string]struct{}与去重示例;struct{}{}是空结构体字面量。 ↩ -
出处:「Understanding and using maps」第 198–222 段(
text/29-fm-understanding-and-using-maps.txt:203,搜「Sorting Map Keys」;:213,搜「sort.Strings」)。 ↩ -
出处:「Understanding and using maps」第 167–180 段(
text/29-fm-understanding-and-using-maps.txt:169,搜「Nested Maps」;:342,搜「Alice Math: 95」)。 ↩ -
出处:「Understanding the versatility of structures」(
text/30-fm-understanding-the-versatility-of-structures.txt:34,搜「type Person struct」)。 ↩ -
出处:「Understanding the versatility of structures」第 92–120 段(
text/30-fm-understanding-the-versatility-of-structures.txt:92,搜「Embedded Fields」;:520,搜「From embedded Person」)。emp.Name、emp.Street的提升访问见输出。 ↩ -
出处:「Understanding the versatility of structures」第 121–128 段(
text/30-fm-understanding-the-versatility-of-structures.txt:125,搜「becomes ambiguous」)。「the promoted name employee.City becomes ambiguous and cannot be used directly」。 ↩↩ -
出处:「Understanding the versatility of structures」第 372–414 段(
text/30-fm-understanding-the-versatility-of-structures.txt:379,搜「Zero value initialization」;:381,搜「Positional initialization」;:382,搜「Named field initialization」)。输出对比见第 1014–1018 行。 ↩ -
出处:「Understanding the versatility of structures」第 836–883 段(
text/30-fm-understanding-the-versatility-of-structures.txt:845,搜「point1 == point2」;:869,搜「independent copy」)。point1 == point2: true,改 point4 后 point1 不变。 ↩ -
出处:「Understanding the versatility of structures」第 869–873 段(
text/30-fm-understanding-the-versatility-of-structures.txt:871,搜「only primitive data types are copied」)。「if you have a slice or pointer to a struct, it will reference that same location and will not be a copy」。 ↩ -
出处:「Understanding the versatility of structures」第 139–180 段(
text/30-fm-understanding-the-versatility-of-structures.txt:139,搜「Value receiver」;:1045,搜「After scaling by 2」)。{Width:10 Height:5}→{Width:20 Height:10}。 ↩↩ -
出处:「Understanding the versatility of structures」第 993–1005 段(
text/30-fm-understanding-the-versatility-of-structures.txt:998,搜「(&rect1).Scale(1.5)」)。「the compiler rewrites the expression as (&rect1).Scale(1.5)」;临时值不可取地址见第 1002–1003 行。 ↩ -
出处:「Understanding the versatility of structures」第 184–242 段(
text/30-fm-understanding-the-versatility-of-structures.txt:191,搜「Constructor function」;:1049,搜「Invalid withdrawal」)。「Because Go does not provide built-in constructors」在同段。 ↩ -
出处:「Understanding the versatility of structures」第 243–284 段(
text/30-fm-understanding-the-versatility-of-structures.txt:247,搜「Interface Implementation」;:273,搜「no explicit declaration」;:656,搜「runtime polymorphism」)。 ↩↩ -
出处:「Understanding the versatility of structures」第 293–320 段(
text/30-fm-understanding-the-versatility-of-structures.txt:295,搜「json:"-"」;:1066,搜「JSON representation」)。实际输出不含 Password,FullName 变 full_name。 ↩ -
出处:「Understanding the versatility of structures」第 698–739 段(
text/30-fm-understanding-the-versatility-of-structures.txt:707,搜「reflect.TypeOf」)。「We will revisit reflection in Chapter 4」是原文自己的指向。 ↩ -
出处:「Understanding the versatility of structures」第 325–360 段(
text/30-fm-understanding-the-versatility-of-structures.txt:326,搜「type Config struct」;:828,搜「namespace isolation」)。 ↩ -
出处:「Understanding the versatility of structures」第 925–966 段(
text/30-fm-understanding-the-versatility-of-structures.txt:943,搜「Empty struct size」;:1103,搜「0 bytes」)。 ↩↩ -
出处:「Pointers」第 35–64 段(
text/31-fm-pointers.txt:45,搜「func updateValue」;:37,搜「Before:」)。输出 Before: 10 / After: 15 见text/32-fm-summary.txt之前的第 37、41 行原码注释预期,运行流程在同段逐行解释。 ↩ -
出处:「Pointers」第 65–210 段(
text/31-fm-pointers.txt:77,搜「type Node struct」;:94,搜「Insert」;:107,搜「Delete」;:147,搜「Sort」)与「Summary」输出(text/32-fm-summary.txt:16,搜「5 -> 3 -> 8 -> 1」;:17,搜「After deleting 3」;:19,搜「After sorting」)。 ↩