为什么用 Go、怎么跑 Go、基本数据类型 — 系统程序员视角的语言地基
为什么用 Go、怎么跑 Go、基本数据类型
这一章讲三件事: 作者凭什么建议你用 Go 写系统软件(而不是 C、Rust 或 Zig); 一段 Go 代码从源文件到跑起来要经过什么;以及基本数据类型里最容易被忽略、 后面每一章都会撞上的那个事实——Go 的字符串是一串字节,不是一串字符。 读完你会拿到全书的第一块地基:这门语言的设计取舍和它的工具链脾气。 不需要任何 Go 基础,遇到的生词都在当场解释。
1. 这一章在原书里的位置
原书分三部分,本章是第一部分「Go 快速入门」的开篇(第 1 章,Essential Go)。 作者在这一部分只做一件事:用系统工程师的眼光把 Go 语言本身过一遍—— 不是语法大全,而是「写系统软件时,这门语言的哪些地方会天天碰到、哪些地方会咬人」。
全书的推理链从这里起步:选语言 → 学语言 → 用语言压过操作系统(文件、信号、网络)→ 用语言造出真实的东西(解释器、搜索引擎、会计系统)。 本章是「选语言」的那一步。
2. 顶层全景:三个问题,一节一个
① 为什么用 Go? → 出身 + 小语言 + 并发模型 + 静态链接(§3)
② 怎么跑 Go 代码? → go run / go build / 工具链一家子(§4)
③ 基本数据类型 → 数字、字符串、零值、大写小写管可见性(§5–§8)
│
└─ 主线例子:"Go ♥ Zig" —— 8 个字符,为什么 len() 数是 10图说:本章就一条线,从「为什么」走到「手上的类型」。后面每一章都建立在这条线上。
3. 为什么用 Go:作者给出的理由,逐条拆开
作者先说了一个容易被当成宣传语的判断,然后给的理由其实都可以一条条验。 我们把他的理由按「真能兑现的」和「要看场景的」分开讲。
理由一:出身。这门语言是 Unix 和 C 的那批人设计的
Go 由 Rob Pike、Robert Griesemer 和 Ken Thompson 设计—— Ken Thompson 是 Unix 的共同创造者和 C 语言的父亲1。 这一条的实际含义是:这不是一门从网页脚本起家的语言, 它的设计假设就是「你在写跟操作系统打交道的程序」。 作者还顺手给了一个感受层面的理由:Go 代码「好看」,你要天天读代码, 代码长得顺眼是生产力2。
理由二:小语言。25 个保留字
Go 只有 25 个保留字3。保留字是语言里你不能拿来当变量名的词(比如 if、for)。
对比着记: C++ 的保留字是一百多个,而且每年都有新增。
小语言的实际后果是:你要记的规则少,别人写的代码你读得懂,
「读别人代码」正是系统软件维护的日常。
理由三:并发模型是语言自带的,不是库
Go 用 goroutine 和 channel 实现并发——goroutine 是轻量的执行单元 (可以先理解成「开销极小的线程」,第 3 章整章讲它),channel 是它们之间传递消息的管道。 关键是:Go 替你管理操作系统的线程,你只管开 goroutine4。 这一点在第 3 章会展开成全书最重头的一章。
理由四:静态链接,单文件交付
Go 编译出来的可执行文件默认静态链接:把所有依赖都打包进一个文件, 不依赖目标机器上装没装某个共享库5。 对系统软件这意味着:在笔记本上编译出一个二进制,拷到服务器上直接跑, 不用管对方环境。docker、hugo 这类工具就是这么发布的。
放到竞品中间看:Go 对 Rust 对 Zig 对 C
作者没有把 Go 说成万灵药,而是把它和另外三门系统语言放在一张权衡表上6:
| 语言 | 给你什么 | 代价是什么 | 作者说它的位置 |
|---|---|---|---|
| C | 最少的抽象、最高的性能、掌控一切 | 内存手工管理;内存泄漏、缓冲区溢出是你的日常风险 | 底层编程的根基,嵌入式和内核还是它 |
| Rust | C 级的性能 + 编译期就挡掉的内存错误(所有权模型 + 借用检查) | 学习曲线陡 | 安全敏感、性能关键的软件 |
| Go | 简单 + goroutine 并发 + 垃圾回收 | 垃圾回收的开销使它不适合最细颗粒度的底层活 | 高并发的后端和云服务 |
| Zig | 手工内存管理 + 编译期执行代码 + 和 C 顺滑的互操作 + 可选安全检查 | 还年轻,生态小 | 想要底层掌控又怕 Rust 复杂的人 |
这张表里最诚实的一行是 Go 那行:作者明说垃圾回收的开销让 Go「less suitable for fine-grained systems programming」7——写操作系统内核、写驱动,Go 不是那把刀。
判断(我们的,不是书里的): 这张四方对照是本章最有保存价值的东西, 因为它回答了「为什么书库里既收 Go 书也收 Rust/Zig 书」——它们是不同的权衡落点, 不是互相替代。书出版时(2025–2026 年)这个格局仍然成立。 如果错,会错在: Zig 的生态如果两年内没有长到「能写生产服务」的规模, 它会从「中间地带」退成「爱好者语言」,表里它的位置就要改写。
4. 怎么跑 Go 代码:两个命令,一个工具链
跑 Go 程序只有两种日常姿势8:
$ go run ./ch01/hw.go ← 编译 + 运行,一步完成。写完就试,试完就丢。
$ go build ./ch01/hw.go ← 只编译,产出一个可执行文件 ./hw,之后随便跑几次都行。经验法则(书里原话):go run 适合快速测试和原型,go build 适合生产、
重复运行和部署9。
支撑这两个命令的是一整套工具链(toolchain):go 命令是前台;
背后是编译器(把源码翻译成机器码)、链接器(把编译产物拼成可执行文件)、
运行时(提供垃圾回收、并发支持等执行期服务,第 4 章讲)、包管理器(管依赖);
再外加 go test(测试)、gofmt(统一格式)、go vet(静态检查)10。
整套东西跟着 Go 发行版一起来,你不需要 Makefile、CMake 或任何第三方构建系统。
一个体现 Go 脾气的设计:循环 import 直接不让你编译
别的语言大多「不鼓励」包之间循环依赖(A 依赖 B、B 又依赖 A), Go 的做法是:出现循环 import,代码直接编译不过11。
包 A ──import──▶ 包 B ──import──▶ 包 C ──import──▶ 包 A
╳ 编译器在这里停下:「import cycle not allowed」图说:Go 把「别写成一团乱麻」从劝告升级成了编译错误。这是这门语言 「约定大于配置」哲学的早期信号,第 4 章的接口设计和第 10 章的解释器都受这条影响。
5. 变量、常量、零值:Go 把「没初始化」这个 bug 类别消灭了
三个事实,每个都有后果:
第一,静态类型 + 类型推断。 Go 是静态类型:每个变量的类型在编译期定死,
编译器拦类型错误。但写的时候可以用 := 让编译器推断:x := 10,x 就是 int12。
第二,常量必须在编译期就能算出值。 const 声明的值,编译器在运行程序之前就必须能确定,
所以 const bufferSize = 1024 可以,而「用户输多少算多少」不行。好处是:
常量不占用运行时的任何状态,用在缓冲区大小、配置键这类固定值上13。
第三,也是最重要的:每个声明出来的变量自动拿到「零值」。
数字类型是 0,布尔是 false,字符串是空串 "",指针、切片、map、channel、接口、函数统一是 nil14。
后果:Go 里不存在「忘了初始化就用」的未定义行为——别的语言里这是个经典 bug 来源,
在 Go 里你拿到的至少是类型规定的零值,行为可预测。
判断(我们的,不是书里的): 零值规则 + 禁止循环 import 这两件事, 是「Go 适合大型系统维护」论断的真正承重墙——比「代码好看」这种理由硬得多。 大型代码库的死因从来是依赖乱和成长出来的隐式状态,Go 在语言层把这两样都掐了。 如果错,会错在: 零值不总是「安全的默认值」——一个零值的结构体可能带着 语义上无效的空字段跑进业务逻辑,语言保证的只是「确定」,不是「合理」。书里没展开这层。
6. 大写小写管公开不公开:没有 public/private 关键字
Go 里没有 public、private 关键字。规则只有一条,对所有名字(变量、常量、
函数、类型、结构体字段)一致适用:
- 首字母大写 → 导出的(exported):包外面看得见,相当于 public;
- 首字母小写 → 未导出的(unexported):只在定义它的包内部可见,相当于 private。
PrintMessage() 外面能调,printMessage() 只有自己包能调15。
这条规则会出现在原书此后的每一个代码例子里,现在记住它,后面不再解释。
7. 主线例子:"Go ♥ Zig" —— 8 个字符,10 个字节
这是本章的承重例子,也是全书反复回来的那个事实的第一次登场: Go 的字符串是一串只读的字节,内容是 UTF-8 编码;按位置取值拿到的是字节,不是字符。
书上拿字符串 str := "Go ♥ Zig" 做演示16。
肉眼看它 8 个字符:G、o、空格、♥、空格、Z、i、g。
但程序 len(str) 报出来的长度是 1017。
差的两个从哪来?UTF-8 是一种变长编码:普通 ASCII 字符(英文字母、空格)占 1 个字节, 而 ♥(爱心符号,Unicode 码位 U+2665)要占 3 个字节:0xE2、0x99、0xA5。 所以 6 个 ASCII × 1 + 1 个 ♥ × 3 = 6 + 3 = 10 个字节。
程序接着用两种方式各走一遍这个字符串,输出对比鲜明18:
*** Byte-wise iteration(按字节走):
Byte 0: 0x47 ('G') ← G,正常
Byte 1: 0x6F ('o')
Byte 2: 0x20 (空格)
Byte 3: 0xE2 ('â') ← ♥ 的第 1 个字节,单看是个乱码字符
Byte 4: 0x99 ← ♥ 的第 2 个字节
Byte 5: 0xA5 ('¥') ← ♥ 的第 3 个字节
Byte 6: 0x20 (空格)
Byte 7–9: Z i g
*** Rune-wise iteration(按字符走,for ... range):
Rune at byte 0: G (U+0047)
Rune at byte 3: ♥ (U+2665) ← 三个字节被还原成一个字符,位置标在起始字节 3
Rune at byte 6: 空格 …图说:同一段字节,按字节走 ♥ 被拆成三个看不懂的碎片;用 for i, r := range 走,
Go 自动把 UTF-8 序列解码成字符(rune,就是 Unicode 码位,是 int32 的别名),
♥ 完完整整地出现在它起始的那个字节下标 3 上19。
书上还给了第三种看法:把字符串转成 []byte(看到原始字节 71 111 32 226 153 165 …)
或 []rune(看到解码后的码位 71 111 32 9829 32 …),再用
utf8.RuneCountInString() 数出真正的字符数 820。
10 个字节对 8 个字符——**这个差值就是「处理文本前必须先想清楚自己在跟字节还是跟字符打交道」
的全部理由。**写网络协议、解析二进制格式时按字节走;做文本处理时按 rune 走,选错一边就是乱码 bug。
8. strings.Builder:拼字符串的正确姿势
上面说字符串是只读的。那要拼一个长字符串怎么办?
"a" + "b" + "c" 每加一次就新造一个字符串、拷一遍旧内容,拼 N 段就拷 N 遍。
strings.Builder(Go 1.10 引入)解决的就是这个:它内部维护一块可增长的缓冲区,
WriteString() 往里追加不造新字符串,拼完一次 String() 取出结果21。
主线例子回到 §7 的思路:书上示例先 builder.Grow(128) 预留 128 字节,
再 WriteString 几段、用 fmt.Fprintf(&builder, ...) 写入格式化内容,
最后 builder.Len() 报 75、builder.Cap() 报 128——预留起了作用,一次扩容都没发生22。
配套还有 WriteByte(单个字节)、WriteRune(单个 Unicode 字符,自动转 UTF-8)、
Reset()(清空重用)。
9. 作者的判断与证据
| 判断 | 性质 | 证据/出处 |
|---|---|---|
| Go 出身好、好学、代码顺眼 | 作者观点,带宣传色彩 | 设计者是 Ken Thompson 等人是事实1;「好看」是审美判断 |
| Go 适合高并发后端,不适合细颗粒度底层 | 行业共识,书里明说 | 四方权衡表,含 Go 自己的短板7 |
| 零值消除未初始化 bug | 语言事实 + 作者解读 | 各类型零值表14 |
go run 原型 / go build 部署 | 经验法则 | 书里的原话9 |
| 字符串按字节存、range 按字符解码 | 语言事实,全书最硬的知识点 | "Go ♥ Zig" 完整输出18 |
书里没有给benchmark 数字来支撑「Go 快」之类的说法——本章所有性能表述都是定性的, 真正的性能证据要到第 4 章的 benchmark 和第 5 章的文件拷贝 benchmark 才出现。
10. 边界与局限
- 本章是语言速览,不是语言定义。 闭包、defer、select、defer+recover 的组合拳, 在后面的章节才展开(error handling 部分在下一章文档讲 panic/recover)。
- 「Go 很好」的论证选择性呈现。 垃圾回收停顿、泛型来得晚(1.18 才有)、 错误处理的啰嗦,这些批评本章一律没提。第 4 章「Common misconceptions about Go」 一节倒是部分补了这个缺口(见 05 号拆解)。
- 数据类型的坑只讲了一个(字节 vs 字符)。 整数溢出、浮点精度(float32 只有 6–7 位 十进制精度)书上一笔带过23,真踩到要自己负责。
- go run/go build 只是入门姿势。 交叉编译、build tags、
go install、模块代理 都没进本章(模块在第 4 章讲)。
11. 可带走的
- 选 Go 的真正理由不是「快」,是小语言 + 自带并发 + 静态链接单文件交付; 它的短板是垃圾回收,别拿它写内核和驱动。
go run一步跑(原型),go build出文件(部署)。- Go 没有循环 import:依赖网被编译器硬性限制成有向无环图,这是它大型项目可维护的根基之一。
- 变量没赋值也有确定零值;「未初始化」不是 Go 的 bug 类别。
- 首字母大写=包外可见,小写=包内私有。没有 public/private 关键字。
len(字符串)数的是字节,不是字符;按字符走用for i, r := range。 一个 ♥ = 3 字节,这是 UTF-8 变的魔术,不是 Go 的怪癖。- 字符串只读;拼字符串用
strings.Builder,知道大小就先Grow(n)。 - 25 个保留字。跟 C++ 比,这门语言刻意小。
12. 原文地图
| 主题 | 原书章 | 原文位置 |
|---|---|---|
| 为什么用 Go(出身、25 关键字、并发、静态链接) | Why use Go? | text/13-fm-why-use-go.txt:20(搜「Rob Pike」) · text/13-fm-why-use-go.txt:27(搜「25 reserved keywords」) |
| Go 对 Rust/Zig/C 四方权衡 | Go versus Rust versus Zig versus C | text/15-fm-how-to-run-go-code.txt:1(搜「minimal abstraction」) · :5(搜「garbage collection」) |
| 工具链全家福(go run/go build/编译器/链接器/运行时) | How to run Go code | text/15-fm-how-to-run-go-code.txt:40(搜「toolchain」) · text/16-fm-fundamental-data-types.txt:25(搜「go run is ideal」) |
| 循环 import 硬禁止 | How to run Go code(Note) | text/16-fm-fundamental-data-types.txt:4(搜「cyclic imports」) |
| 变量/常量/零值/导出规则 | Fundamental data types | text/16-fm-fundamental-data-types.txt:45(搜「type inference」) · :54(搜「zero value」) · :70(搜「capitalization」) |
| 数值类型全家(int8…float64、byte/rune 别名) | Fundamental data types | text/16-fm-fundamental-data-types.txt:102(搜「signed and unsigned」) |
| "Go ♥ Zig" 主线例子(字节 vs rune) | Fundamental data types | text/16-fm-fundamental-data-types.txt:233(搜「Go ♥ Zig」) · :285(搜「Length in bytes: 10」) · :298(搜「Rune-wise iteration」) |
| strings.Builder | Fundamental data types | text/16-fm-fundamental-data-types.txt:343(搜「strings.Builder」) · text/17-fm-error-handling.txt:18(搜「Length: 75 bytes」) |
Footnotes
-
出处:「Why use Go?」第 20 段(
text/13-fm-why-use-go.txt:20,搜「Rob Pike」)。原文列了三位设计者:Rob Pike、Robert Griesemer、Ken Thompson,并注明 Thompson 是 Unix 共同创造者、C 语言之父。 ↩ ↩2 -
出处:「Why use Go?」第 23 段(
text/13-fm-why-use-go.txt:23,搜「good-looking」)。「Go code is good-looking, which is great when you are programming applications for a living」——审美理由,作者明说是感受层面。 ↩ -
出处:「Why use Go?」第 27 段(
text/13-fm-why-use-go.txt:27,搜「25 reserved keywords」)。原文的反问是「Can you say that about C++?」——C++ 保留字数量不在书里,来自通用知识。 ↩ -
出处:「Why use Go?」第 29–31 段(
text/13-fm-why-use-go.txt:29,搜「concurrency capabilities」)。原文:「Go manages OS threads for you and has a powerful runtime that allows you to spawn lightweight units of work (goroutines)」。 ↩ -
出处:「Why use Go?」第 39–40 段(
text/13-fm-why-use-go.txt:39,搜「statically linked」)。「executable binaries are typically statically linked, which means that once they are generated, they do not depend on any shared libraries」。 ↩ -
出处:「Go versus Rust versus Zig versus C」(
text/14-fm-go-versus-rust-versus-zig-versus-c.txt:35,搜「systems-oriented languages」与text/15-fm-how-to-run-go-code.txt:7,搜「manual memory management」)。C 的描述见 14 号文件第 1 行起,Rust/Zig/格局总结见 15 号文件第 1–19 行。 ↩ -
出处:「Go versus Rust versus Zig versus C」第 5–8 段(
text/15-fm-how-to-run-go-code.txt:5,搜「garbage collection」)。原文:「Go emphasizes simplicity and concurrency with goroutines and garbage collection… but less suitable for fine-grained systems programming due to garbage collection overhead」。 ↩ ↩2 -
出处:「How to run Go code」(
text/15-fm-how-to-run-go-code.txt:30,搜「go run command」)与「Fundamental data types」(text/16-fm-fundamental-data-types.txt:16,搜「go run ./ch01/hw.go」)。两种方式的原书示例输出 Hello World!。 ↩ -
出处:「Fundamental data types」第 25–28 段(
text/16-fm-fundamental-data-types.txt:25,搜「go run is ideal」)。原文即「go run is ideal for rapid testing and prototyping, whereas go build is more appropriate for production use」。 ↩ ↩2 -
出处:「How to run Go code」Note 第 40–51 段(
text/15-fm-how-to-run-go-code.txt:40,搜「toolchain」)。原文逐一点了 compiler、linker、runtime、package manager,以及 go test、gofmt、go vet。 ↩ -
出处:「How to run Go code」Note(
text/16-fm-fundamental-data-types.txt:4,搜「cyclic imports」)。原文:「it completely forbids import cycles. If an import cycle exists, the code simply will not compile」。 ↩ -
出处:「Fundamental data types」第 44–47 段(
text/16-fm-fundamental-data-types.txt:45,搜「type inference」)。x := 10推断为 int 是原文给的例子。 ↩ -
出处:「Fundamental data types」第 48–53 段(
text/16-fm-fundamental-data-types.txt:48,搜「const keyword」)。「must be assigned a value that the compiler can determine before runtime」。 ↩ -
出处:「Fundamental data types」第 54–67 段(
text/16-fm-fundamental-data-types.txt:54,搜「zero value」)。逐类型零值表:int/float64 → 0,bool → false,string → "",「pointers, slices, maps, channels, interfaces, and functions default to nil」。 ↩ ↩2 -
出处:「Fundamental data types」第 70–83 段(
text/16-fm-fundamental-data-types.txt:70,搜「capitalization」)。「If a name starts with an uppercase letter, it is exported… lowercase letter, it is unexported」。 ↩ -
出处:「Fundamental data types」(
text/16-fm-fundamental-data-types.txt:233,搜「Go ♥ Zig」)。str := "Go ♥ Zig",示例程序 stringsRunes.go 全文在同文件第 225–256 行。 ↩ -
出处:「Fundamental data types」(
text/16-fm-fundamental-data-types.txt:261,搜「8 characters」与:285,搜「Length in bytes: 10」)。原文:「the string visually contains 8 characters, its length in bytes is 10 due to the 3-byte encoding of the heart symbol」。 ↩ -
出处:「Fundamental data types」(
text/16-fm-fundamental-data-types.txt:286,搜「Length in bytes: 10」起,至:310)。输出的两部分(按字节、按 rune)都在同一段输出块里,♥ 的三个字节 0xE2 0x99 0xA5 见第 293–295 行。 ↩ ↩2 -
出处:「Fundamental data types」第 329–331 段(
text/16-fm-fundamental-data-types.txt:218,搜「decodes UTF-8 sequences」)。「identifies ♥ as a single character starting at byte index 3」。 ↩ -
出处:「Fundamental data types」(
text/16-fm-fundamental-data-types.txt:307,搜「Converted to []byte」)与第 336–338 段(搜「8, which matches」)。utf8.RuneCountInString()报 8。 ↩ -
出处:「Fundamental data types」第 343–362 段(
text/16-fm-fundamental-data-types.txt:343,搜「strings.Builder」)。「Introduced in Go 1.10… without creating many intermediate string values」。 ↩ -
出处:「Fundamental data types」(
text/17-fm-error-handling.txt:15,搜「Final output: Go is fast」;:18,搜「Length: 75 bytes」)。示例输出:Length: 75 bytes, Capacity: 128 bytes。 ↩ -
出处:「Fundamental data types」第 144–153 段(
text/16-fm-fundamental-data-types.txt:145,搜「6 to 7 decimal digits」)。float32 约 6–7 位十进制精度,float64 约 15–17 位。 ↩