类型与数据结构
类型与数据结构 · 阅读后完成下方实验与检查题,再用「标记完成」记录进度。
学习目标
这课把书签数据装进 Go 的类型系统:用结构体定义 Bookmark,用切片和映射组织集合,看清零值和共享底层数据这两个最容易出 bug 的地方,并亲手修复一次误用。
问题场景
书签服务要存的东西很朴素:id、标题、链接、标签、创建时间。Python 里一个 dict 就装下了,Go 却逼你先回答「字段各是什么类型、缺省是什么值、拷贝时谁跟谁共享内存」。这些约束换来的,是编译器替你挡住一大类字段拼错、类型用错的低级 bug。
核心解释
结构体一次定义书签的形状:
type Bookmark struct {
ID int64
Title string
URL string
Tags []string
CreatedAt time.Time
}
func main() {
var b Bookmark // 零值即可用:数值 0、字符串 ""、切片 nil
fmt.Printf("%+v\n", b)
b.Tags = append(b.Tags, "go", "docs") // nil 切片可以直接 append
fmt.Printf("%+v\n", b)
}
第一行输出是 {ID:0 Title: URL: Tags:[] CreatedAt:0001-01-01 ...}。Go 没有「未初始化」这个状态:任何变量声明出来就有零值,nil 切片照样能 append。这解释了为什么 Go 代码里很少看到「先判空再初始化」的仪式。
切片的坑在共享底层数组:
base := []string{"go", "docs", "web"}
head := base[:2] // 与 base 共用同一块底层数组
head = append(head, "X") // 未超容量,写进了 base[2] 的位置
fmt.Println(base) // [go docs X],base 被改了
base[:2] 不是拷贝,是同一块内存上的一个窗口。往窗口里 append 且未超容量时,新元素落进 base 的第 3 个位置。想要独立副本就 slices.Clone(base) 或 append([]string(nil), base...)。映射没有容量问题,但赋值同样不拷贝:把 map 传给函数,函数里改了,外面看得到。
书签按标签索引适合用映射:map[string][]int64(标签到 id 列表),查询 O(1),代价是要自己维护它和切片两个结构的一致性。
示例拆解
func CountByTag(bookmarks []Bookmark, tag string) int {
n := 0
for _, b := range bookmarks {
for _, t := range b.Tags {
if t == tag {
n++
}
}
}
return n
}
三个关键点:range bookmarks 拿到的 b 是元素副本,改它不影响原切片,想改要用下标 bookmarks[i].Title = ...;range 同时给出行下标和值,不需要时用 _ 丢弃;统计类逻辑写成纯函数(输入切片、输出数字),不挂全局变量,G03 抽接口时可以直接搬走。
放进 main.go 同包运行 go run . 验证:造三个书签、其中两个带 go 标签,CountByTag 应返回 2;空切片和不存在的标签都返回 0。
动手任务
- 给 Bookmark 加
Pinned bool字段,观察零值打印出来是 false,理解「零值有定义但不可信」。 - 写 CountByTag 并自测三种输入:空切片、找不到的标签、命中一个。
- 复现共享底层数组:构造 base 和 head,append 后打印两者,再用 slices.Clone 修复,确认 base 不再被改。
常见错误
- 结构体字段首字母小写后奇怪别的包读不到:小写是包内私有,跨包访问要大写导出。
- 认为 nil 切片要先 make 才能 append:直接 append 就行,反而 make 后忘了存东西更容易误判。
- 对切片「截取后修改」影响原数据感到意外:切片是窗口不是拷贝,跨函数共享时尤其危险。
- 遍历时用 range 的副本去改元素:改动会丢,要用下标访问原切片。
检查与复盘
完成下方检查题。自查清单:
- 能默写 Bookmark 结构体并说清每个字段的零值
- 能解释「切片是底层数组的窗口」并演示一次踩坑
- 集合操作默认写成纯函数,不依赖全局状态
参考资料
- Go Spec:结构体类型 —— 字段与标签的语法权威
- Go 博客:Go Slices 入门 —— len/cap/底层数组机制图解
- Go Spec:映射类型 —— map 的取值与删除语义
- 标准库 builtin —— append/copy/make/nil 的官方定义
读完这一课了?
标记完成后,工作台会安排接下来的学习。