跳转至

为什么用 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. 可带走的

  1. 选 Go 的真正理由不是「快」,是小语言 + 自带并发 + 静态链接单文件交付; 它的短板是垃圾回收,别拿它写内核和驱动。
  2. go run 一步跑(原型),go build 出文件(部署)。
  3. Go 没有循环 import:依赖网被编译器硬性限制成有向无环图,这是它大型项目可维护的根基之一。
  4. 变量没赋值也有确定零值;「未初始化」不是 Go 的 bug 类别。
  5. 首字母大写=包外可见,小写=包内私有。没有 public/private 关键字。
  6. len(字符串) 数的是字节,不是字符;按字符走用 for i, r := range。 一个 ♥ = 3 字节,这是 UTF-8 变的魔术,不是 Go 的怪癖。
  7. 字符串只读;拼字符串用 strings.Builder,知道大小就先 Grow(n)。
  8. 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」)


  1. 出处:「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」——审美理由,作者明说是感受层面。 ↩

  3. 出处:「Why use Go?」第 27 段(text/13-fm-why-use-go.txt:27,搜「25 reserved keywords」)。原文的反问是「Can you say that about C++?」——C++ 保留字数量不在书里,来自通用知识。 ↩

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

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

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

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

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

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

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

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

  12. 出处:「Fundamental data types」第 44–47 段(text/16-fm-fundamental-data-types.txt:45,搜「type inference」)。x := 10 推断为 int 是原文给的例子。 ↩

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

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

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

  16. 出处:「Fundamental data types」(text/16-fm-fundamental-data-types.txt:233,搜「Go ♥ Zig」)。str := "Go ♥ Zig",示例程序 stringsRunes.go 全文在同文件第 225–256 行。 ↩

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

  18. 出处:「Fundamental data types」(text/16-fm-fundamental-data-types.txt:286,搜「Length in bytes: 10」起,至 :310)。输出的两部分(按字节、按 rune)都在同一段输出块里,♥ 的三个字节 0xE2 0x99 0xA5 见第 293–295 行。 ↩↩

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

  20. 出处:「Fundamental data types」(text/16-fm-fundamental-data-types.txt:307,搜「Converted to []byte」)与第 336–338 段(搜「8, which matches」)。utf8.RuneCountInString() 报 8。 ↩

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

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

  23. 出处:「Fundamental data types」第 144–153 段(text/16-fm-fundamental-data-types.txt:145,搜「6 to 7 decimal digits」)。float32 约 6–7 位十进制精度,float64 约 15–17 位。 ↩