编程语言研究书库

map、结构体与指针:Go 组织数据的三种方式,以及一张「14 个面貌」的结构体全景图

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. 可带走的

  1. map 查不存在的键返回零值不报错;分「不存在」和「值是零」用 comma ok。
  2. nil map 不能写;delete 删不存在的东西是安全的;遍历顺序随机。
  3. map 和切片是引用类型(共享),数组和结构体是值类型(拷贝)——但结构体里的切片/指针字段拷的仍是引用。
  4. 嵌入 = 字段提升;提升撞名必须写全路径;Go 用组合替继承。
  5. 方法接收者:只读值、改写指针;Go 自动补 & 和 *,但临时字面量取不了地址。
  6. 没有构造器,惯例是 NewXxx() 返回 *T;小写字段 + 方法 = 封装。
  7. 接口靠「凑齐方法」隐式满足,不用声明;这是 Go 多态的全部机制。
  8. struct tag 驱动序列化:json:"-" 隐藏字段、omitempty 跳空值——API 里不漏密码就靠它。
  9. 空结构体 struct{} 零字节,map[string]struct{} 是去重集合的惯用法。
  10. 指针基本功:& 取地址、* 解引用;自引用结构体(链表/树)只能靠指针。

11. 原文地图

主题原书章原文位置
为什么需要 map(切片下标的局限)Understanding and using mapstext/29-fm-understanding-and-using-maps.txt:2(搜「positive integers as indexes」)
可比较才能当键、哈希表 O(1)Understanding and using mapstext/29-fm-understanding-and-using-maps.txt:8(搜「comparable」) · text/28-ch02.txt:13(搜「hash tables」)
nil map 与三种创建方式Understanding and using mapstext/29-fm-understanding-and-using-maps.txt:31(搜「nil map」)
零值返回与 comma okUnderstanding and using mapstext/29-fm-understanding-and-using-maps.txt:71(搜「zero value」) · :75(搜「comma ok idiom」)
delete、遍历随机、引用类型、排序遍历、map 当集合Understanding and using mapstext/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 structurestext/30-fm-understanding-the-versatility-of-structures.txt:92(搜「Embedded Fields」) · :124(搜「becomes ambiguous」)
四种初始化、值语义、拷贝只拷原始字段Understanding the versatility of structurestext/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 structurestext/30-fm-understanding-the-versatility-of-structures.txt:139(搜「Value receiver」) · :191(搜「Constructor function」) · :1049(搜「Invalid withdrawal」)
隐式接口与 Shape 多态Understanding the versatility of structurestext/30-fm-understanding-the-versatility-of-structures.txt:247(搜「Interface Implementation」) · :273(搜「no explicit declaration」) · :649(搜「slice of Shape」)
struct tag 与 JSON、反射读 tagUnderstanding the versatility of structurestext/30-fm-understanding-the-versatility-of-structures.txt:309(搜「special json」) · :699(搜「json.Marshal」) · :707(搜「reflect.TypeOf」)
匿名结构体与命名空间隔离Understanding the versatility of structurestext/30-fm-understanding-the-versatility-of-structures.txt:325(搜「Anonymous Structs」) · :828(搜「namespace isolation」)
空结构体零字节Understanding the versatility of structurestext/30-fm-understanding-the-versatility-of-structures.txt:943(搜「Empty struct size」)
指针基础 &x/*ptrPointerstext/31-fm-pointers.txt:45(搜「func updateValue」) · :37(搜「Before」)
链表 Insert/Delete/Sort/PrintPointerstext/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」)

Footnotes

  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」。 ↩

  2. 出处:「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」。 ↩

  3. 出处:「Data Collections and Functions」(text/28-ch02.txt:13,搜「hash tables」)。「Go implements maps as hash tables, providing average O(1) time (constant) complexity」。 ↩

  4. 出处:「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」。 ↩

  5. 出处:「Understanding and using maps」第 42–58 段(text/29-fm-understanding-and-using-maps.txt:51,搜「grades["Marietta"] = 98」;:303,搜「Grades after adding」)。 ↩

  6. 出处:「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」。 ↩

  7. 出处:「Understanding and using maps」第 74–91 段(text/29-fm-understanding-and-using-maps.txt:75,搜「comma ok idiom」;:313,搜「David doesn't exist」)。 ↩ ↩2

  8. 出处:「Understanding and using maps」第 94–106 段(text/29-fm-understanding-and-using-maps.txt:106,搜「deleting a non-existent key is safe」)。 ↩

  9. 出处:「Understanding and using maps」第 124–128 段(text/29-fm-understanding-and-using-maps.txt:127,搜「randomized」)。「map iteration order in Go is randomized」。 ↩

  10. 出处:「Understanding and using maps」第 181–195 段(text/29-fm-understanding-and-using-maps.txt:181,搜「Reference Types」;:347,搜「After modifying copy」)。 ↩

  11. 出处:「Understanding and using maps」第 223–246 段(text/29-fm-understanding-and-using-maps.txt:228,搜「Using Maps as Sets」)。map[string]struct{} 与去重示例;struct{}{} 是空结构体字面量。 ↩

  12. 出处:「Understanding and using maps」第 198–222 段(text/29-fm-understanding-and-using-maps.txt:203,搜「Sorting Map Keys」;:213,搜「sort.Strings」)。 ↩

  13. 出处:「Understanding and using maps」第 167–180 段(text/29-fm-understanding-and-using-maps.txt:169,搜「Nested Maps」;:342,搜「Alice Math: 95」)。 ↩

  14. 出处:「Understanding the versatility of structures」(text/30-fm-understanding-the-versatility-of-structures.txt:34,搜「type Person struct」)。 ↩

  15. 出处:「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 的提升访问见输出。 ↩

  16. 出处:「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」。 ↩ ↩2

  17. 出处:「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 行。 ↩

  18. 出处:「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 不变。 ↩

  19. 出处:「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」。 ↩

  20. 出处:「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}。 ↩ ↩2

  21. 出处:「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 行。 ↩

  22. 出处:「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」在同段。 ↩

  23. 出处:「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」)。 ↩ ↩2

  24. 出处:「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。 ↩

  25. 出处:「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」是原文自己的指向。 ↩

  26. 出处:「Understanding the versatility of structures」第 325–360 段(text/30-fm-understanding-the-versatility-of-structures.txt:326,搜「type Config struct」;:828,搜「namespace isolation」)。 ↩

  27. 出处:「Understanding the versatility of structures」第 925–966 段(text/30-fm-understanding-the-versatility-of-structures.txt:943,搜「Empty struct size」;:1103,搜「0 bytes」)。 ↩ ↩2

  28. 出处:「Pointers」第 35–64 段(text/31-fm-pointers.txt:45,搜「func updateValue」;:37,搜「Before:」)。输出 Before: 10 / After: 15 见 text/32-fm-summary.txt 之前的第 37、41 行原码注释预期,运行流程在同段逐行解释。 ↩

  29. 出处:「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」)。 ↩

On this page