第一部分导读:语言的地基
同样是 a = b 这一行,在四种语言里背后发生的事并不一样。在 Python 里,它让 a 和 b 这两个名字指向同一个对象;在 C# 里,如果 b 是 struct,它会复制一份;在 Go 里,除非你写了指针,它也是复制。再比如调用 user.nickname.upper():Python 在运行到这一行时才发现 nickname 是 None,TS 和 C# 可以在运行前就提醒你,Go 则根本没有“字符串为空值”这回事,字符串的零值是空串。
这一部分讲的就是这类问题:一行代码在四种语言里背后发生的事为什么不一样。它们不属于某一个具体特性,而是语言的底层约定:源码什么时候变成指令,值带不带类型,名字能不能改指向,赋值复制的是什么,“没有”怎么表示。后面讲函数、类、泛型时,处处会用到这里的结论,所以把它们放在最前面。
先建立一个整体印象:Python 和 JS 把大部分决定推迟到运行时,C# 和 Go 在编译时定下更多,TS 站在中间,在编译时检查、运行时擦掉。这个印象不是定论,每一章都会拆开讲实现,并给出例外。
| 章 | 一句话 | 四种语言差异最大的一点 |
|---|---|---|
| 第 2 章 编译、解释、字节码、JIT 与 AOT | 源码变成 CPU 指令这一步放在什么时候做 | 分发形式:Python 带源码和依赖清单,JS 是脚本或 bundle,C# 是程序集,也可以是单个本机可执行文件,Go 只有单个可执行文件 |
| 第 3 章 类型系统 | 尽早发现不该放在一起的值 | 运行时是否保留类型:同是静态类型,TS 编译完什么都不剩,C# 连泛型的类型实参都留着;其次是兼容规则,TS 和 Go 按形状判断,C# 按声明判断 |
| 第 4 章 变量、常量与可变性 | 说清哪些会变、哪些不会变 | 绑定不可变与值不可变:const、readonly 管的是名字,不是对象;Python、JS 的名字指向对象,C# 和 Go 的变量直接装着值 |
| 第 5 章 值与引用、相等性、内存与垃圾回收 | 赋值复制了什么,谁来收回内存 | 用户自定义值类型:Python 和 JS 里不存在,要复制只能造新对象;C# 的 struct 和 Go 的结构体赋值本身就会复制 |
| 第 6 章 空值 | 怎样表示“没有”而不崩溃 | 表示“没有”的值:Python 是 None,JS 有 undefined 和 null,C# 是 null,Go 是零值和 nil |
先看一眼下面这张图,把你手上的问题对到章节上。
图的左列是你可能遇到的问题,右列是该先翻的章。
遇到什么问题先看哪章:
- 你想弄清楚程序为什么启动慢,或者部署时要不要带运行时 → 看第 2 章 编译、解释、字节码、JIT 与 AOT。
- 你想知道一个类型错误会在编译时还是运行时报出来,以及
1 + "2"会怎样 → 看第 3 章 类型系统。 - 你以为复制了一份配置,改了一处却发现原来的也变了 → 先看第 4 章 变量、常量与可变性,再看第 5 章。
- 你想知道两个对象怎样才算相等,或者内存由谁回收 → 看第 5 章 值与引用、相等性、内存与垃圾回收。
- 你想区分“没有这个键”和“值为零”,或者想避开空值引起的崩溃 → 看第 6 章 空值。