由于本章内容引用了《你所缺失的那门计算机课》中的一些内容,因此:
本文章(教案)采用 CC BY-NC-SA 4.0 协议许可。
您可以在 https://creativecommons.org/licenses/by/4.0/deed.zh-hans 查看该许可的详细信息。
变量:万物都有名字
《道德经》开篇:“无名,天地之始;有名,万物之母。”
人类之所以能驯服世界,第一步就是给万物命名。没有名字,一切都是混沌。编程也是如此——在你给一个东西取名字之前,它对你而言是不存在的。
$x = 42,这一行代码,本质上是一次“创世”:你从虚无中划出一块内存,给它一个名字,从此它便有了身份,有了可以被呼唤、被修改、被传递的资格。
1.1 变量?怎么来的
数据不是凭空产生的。正如能量守恒,算出来的数据总得到哪里去。所以,计算机才有“内存”,用来存放算出来的这些数据。
在Windows 中有一个术语叫做“Working Set”即“工作集”,用来描述一个程序所使用的内存。我认为这是个绝佳术语,它生动的描述出了内存的用处——那就是,用于存放程序工作过程中产生的或者将要用到的一些数据。
要定位内存中的某些数据,很简单,一个地址即可。计算机是二进制的,我们常见的计算机都是 64 位的。正因为如此内存地址也是一个 64 比特(二进制位)长的整数。当然,64个1或者0是非常繁琐的东西,所以我们常常用一个高效的方式表示这么一个整数,那就是用十六进制。
一般来说,一个特定的内存地址往往表示的是内存中对应特定字节。按照一般的定义,一个字节是八个二进制位,即8bit(比特)。例如,地址0x000000000D000721表示的就是内存中编号为 218105633 的那个字节。
但是,记住这一串地址还是太烦了——而且还容易打错。所以我们引入了“变量”的概念。正如前文所述,“变量”是给一段内存取的名字。
在 PowerShell 中,用以下方式“定义”一个变量:
$variable = value就这么简单。$作为修饰符,告诉powershell: 后面的这一串字母,是一个变量。变量和值中间用一个等号链接,代表,将value这个值放到 variable 代表的这一段内存里面。
1.2 既然他都叫“变量”了……
变量一词,双字。一个“变”,一个“量”。能变化的量,这就叫做变量。所以,在程序的任何位置你都能对这个变量重新赋值。与之相对的是常量。常量在整个程序范围内是不可变的。你可以用以下方式创建一个常量:
Set-Variable -name [name] -value [value] -option constant而引用常量也是一样的$[name]这种写法。但是,常量名字里面带“常”——所以你在程序运行过程中更改他将会出错。记住,计算机是死板的。它容不得任何一点的变通。
1.3 变量,可以有类型哦……
PowerShell 不是一门强类型语言。换言之它并不强行指定一个变量要有怎样的“类型”。
但是这并不意味着我们不能给变量指定类型,无论是为了更加清晰还是为了防止我们自己出错。
不过请看以下例子,你就可以知道,这里其实真的非常容易犯浑:
[int]$integer = 42 PS> $integer = 114514
# 无输出
PS> $integer = "33550336"
# 无输出
PS> $integer = "This is a String"
MetadataError: Cannot convert value "this is a string" to type "System.Int32". Error: "The input string 'this is a string' was not in a correct format."代码 1 是定义一个带类型的变量。很简单,只需要在$前面用中括号括起这类型即可。有效的类型……多了去了。这里列出常用几个:
- int 整数类型
- double 双精度浮点数类型
- float 单精度浮点数类型
- bool 布尔值(真或假)
- array 数组(说白了就是一坨东西)
- regex 正则表达式
- xml 不必多言
当然,如果有做更多了解的人就知道,原则上PowerShell允许你使用底层 .NET 的任何类型。
但 代码 2 就有点混乱了。"33550336"乍一看是个字符串,怎么能赋值给整数变量呢?
这就要归功于(或者说归咎于)PowerShell 那极其热情且努力的隐式类型转换(Implicit Type Conversion)机制。
还记得我们前面说过,PowerShell 并不是一门强类型语言吗?当你在 代码 1 中写下 [int] 时,你其实并不是在创造一个绝对无法改变的“钢铁牢笼”,而是在跟 PowerShell 签订一份契约:“这个变量 $integer 今后只能装整数哦。”
当你试图把 “33550336” 这个字符串塞进这个整数变量时,PowerShell 并没有立刻拒绝。它就像一个热心肠的翻译官,在心里暗自琢磨:“嗯?主人给了一个字符串,但它看起来长得完全就是一个数字呀!没问题,我来帮主人把它转换成真正的整数!”于是,它在底层悄悄去掉了文本的外衣,把它解析成了数字,塞进了内存的模子里。这就是为什么 代码 2 的第二步没有任何输出——因为 PowerShell 默默替你把活儿干完了。
但是,这种“自作主张”的热心肠,在遇到真正的纯文本时就会遭遇滑铁卢。
当你把 “This is a String” 丢给 $integer 时,这位翻译官看着里面的一堆英文字母,无论如何也无法将它们理解成数字。它努力过了,但最终还是崩溃了,于是只能向你抛出一长串红色的错误提示:Cannot convert value…(我真的没法把这玩意儿变成数字啊!)。
所以,给变量加上 [int] 这样的类型约束,它的真实含义并不是“拒绝一切非整数的赋值”,而是“尽最大努力将所有赋值转换为整数,如果实在转不了,再报错”。
这种设计在很多时候会让我们写脚本变得非常丝滑,因为你不需要去手动写一大堆繁琐的转换代码(比如不用去刻意写 [int]::Parse("123"))。但正如你所见,它也确实非常容易让人犯浑。如果我们没有意识到它的这种自动转换机制,在处理复杂的数据流时,就很容易让一些本该暴露的逻辑错误,被悄悄转换成了看起来没问题、实则会在日后导致大麻烦的幽灵数据。
1.4 当你需要一组数据:集合
一个一个的变量还是太乏味了。计算机生来就是为了批量处理,但是为了装下这些数据据没人想一个一个a1,a2,a3,a4,a5,……这么一组组一个个手打。所以就有了“集合”这个概念。
一些学过其它语言的人可能不禁想起“数组(array)”这个概念。但这里我更倾向于用数学中的集合来解释——当然,不是原话引用《数学 必修一》的内容,那东西就是车轱辘话——集合的本质,就是一组相似的东西,被你放在一起,就叫做集合。任何东西都可以被你放在一起。PowerShell并不明确限制你到底往里面放什么,除非你手动声明:
# 混合类型集合
$mixedArray = 1, "Hello", $true, (Get-Date)
# 指定类型的集合
[int[]]$intArray = 1,2,3,4但与数学中的“集合”不同的是,你可以按照你的心情,随时随地增加、查询、更改里面的元素。而且,它是有序,且不互异的。这点要与数学概念区分开来。但既然都这样了为什么要被翻译成“集合”呢……因为原文叫做“Collection”。机翻发力了
1.4.1 拜访大杂院的住户:索引(Index)
既然你提到了集合是有序的,这就意味着每一个被你放进去的元素,都会获得一个专属的“门牌号”——在编程语言中,我们称之为索引(Index)。
不过请注意,计算机的思维方式和人类有一点点小分歧:当我们数数时,总是习惯从 1 开始;而计算机在分配门牌号时,总是执拗地从 0 开始。
# 呼唤大杂院里的第一位住户(注意门牌号是 0 哦!)
PS> $mixedArray[0]
1
# 呼唤第二位住户
PS> $mixedArray[1]
Hello而且,PowerShell 还有极其温柔和人性化的一面。假设你买了一大袋糖果(一个很长很长的集合),你只想拿出最后一块,但你懒得去数里面到底有多少块,怎么办呢?你可以使用负数索引:
# -1 代表倒数第一个元素,-2 就是倒数第二个,以此类推
PS> $mixedArray[-1]
2026年7月6日 12:42:411.4.2 宇宙的毁灭与重生:修改与增加
你刚才提到,我们可以“随时随地增加、更改里面的元素”。 要修改一个已经存在的元素非常简单,就像是给指定门牌号的房间换一个新住户:
$intArray[0] = 99 # 现在,原本是 1 的位置变成了 99而如果你想往集合里追加新的元素,PowerShell 提供了一个极其符合直觉的魔法符号:+=。
# 往集合的末尾再塞进一个 5
$intArray += 5分支:严谨中的变通
2.1 分支,啥啊?
黄色的树林里分出两条路,
可惜我不能同时去涉足,
我在那路口久久伫立,
我向着一条路极目望去,
直到它消失在丛林深处。
但我却选了另外一条路,
它荒草萋萋,十分幽寂,
显得更诱人、更美丽,
虽然在这条小路上,
都很少留下旅人的足迹,
虽然那天清晨落叶满地,
两条路都未经脚印污染。
啊,留下一条路等改日再见!
但我知道路径延绵无尽头,
恐怕我难以再回返。
也许多少年后在某个地方,
我将轻声叹息把往事回顾,
一片树林里分出两条路,
而我选了人迹更少的一条,
因此走出了这迥异的旅途。
分支。一棵树有很多的分支,这个分支映射到英文叫做branch。这不是我们这里要讨论的“分支”。我们这里要讨论的“分支”是case,也就是“不同的情况”。与树的一根一根分支不同,不同的case之间是互斥的。也就是说,你只能同时选择一个分支,而不是选择两个。
日常生活中我们经常遇到需要做判断的场景。今天晚上是吃KFC还是吃麦当劳,明天是不是应该去打舞萌……不同的场景中,我们需要做出不同的判断,不同的判断结果,就会使我们到达不同的控制流中。
2.2 分支,怎么写?
在 PowerShell 中,做出一个判断可以使用if-else指令,如下:
if(condition)
{
# do smth...
}
else
{
# otherwise...
}接下来的内容或多或少引用或总结自这篇Microsoft Learn文章。这真不错,强烈推荐你们去看看。
在我们的这个例子中,if语句将会计算放在括号中的这个表达式的值。如果为$true则执行if与else之间的那一对大括号中的语句;否则执行的是else下面那一对大括号之间的语句。
2.3 怎么比较?当然是运算符啦!
那你想。要判断……是不是应该搞点运算符来,不然怎么比较呢?下面是一个详细的表……
骗你的,哪来的表,一个表列出来我列的麻烦你们也记的麻烦。当然对于它的形式这里是有一个特定的东西:那就是前缀用-。当然,也不必死记硬背:如果我有提过的话,Powershell中所有的东西,都是一个对象。你可以对对象进行一些操作,而操作用的是——参数,如果你们还记得的话。比如下面这个表达式:
$v1 = 42
$v2 = 8192
$condition = $v1 -eq $v2
# 输出:false可以把 -eq 看作一个平平常常的命令执行要用的参数。这样好理解很多。
众所周知,PowerShell 是微软出品,所以就带着微软的味道:奇奇怪怪的缩写。想要快速记住所有的运算符可以借鉴一下英语中的词根词缀法:每种运算符都有一个词根,然后你可以再上面加上特定的“词缀”来组成一个正式的运算符。下面是常见的词根:
| 词根 | 含义 | 备注 |
| -eq | 相等 | 允许区分大小写变体 |
| -ne | 不相等 | 同上 |
| -gt | 大于 | |
| -ge | 大于等于 | |
| -lt | 小于 | |
| -le | 小于等于 | 这几个运算符原则上允许使用区分大小写变体——但是这有什么意义呢? |
| -like | (通配符)匹配 | |
| -match | (正则)匹配 | |
| -contains | (集合)包含 | |
| -in | (集合)包含 | 与上一个运算符的区别是,集合放在右侧而不是左侧 |
接下来的是词缀表:其实很简单,主要就是区分大小写和“非”这一逻辑:
| 词缀 | 含义 |
| (无) | 默认行为(不区分大小写) |
| c- | 区分大小写(case-sensitive),如 -ceq、-clike、-cmatch |
| i- | 明确指定不区分大小写(case-insensitive),如 -ieq、-ilike,与默认行为相同 |
| not- | 逻辑取反,如 -notlike、-notmatch、-notin、-notcontains |
| c- + not- | 区分大小写且取反,如 -cnotin、-cnotlike(组合顺序:c → not → 词根) |
| i- + not- | 不区分大小写且取反,如 -inotin、-inotlike |
当然,词缀可以组合,但不能乱组合:c- 和 i- 是互斥的(你不能既区分大小写又不区分大小写),not- 则可以自由地和两者搭档。
2.3.1 别光记表,来点真实的
光看表没有感觉。我们来看几个真实的例子,把运算符用起来:
PS> "Hello" -eq "hello"
True
PS> "Hello" -ceq "hello"
False# * 代表"任意个任意字符"
PS> "PowerShell" -like "Power*"
True
PS> "PowerShell" -notlike "Python*"
True$fruits = "苹果", "香蕉", "西瓜"
PS> $fruits -contains "香蕉"
True
# -in 和 -contains 是同一件事,只是左右互换
PS> "香蕉" -in $fruits
True2.4 等等,条件可以组合吗?
可以,而且非常常见。现实里我们做判断往往不是单一条件——“如果今天下雨并且我没带伞,那就打车”,这里就有两个条件同时成立的要求。PowerShell 提供了逻辑运算符来处理这种情况:
| 运算符 | 含义 | 示例 |
| -and | 逻辑与(两者都为真) | $a -gt 0 -and $a -lt 100 |
| -or | 逻辑或(至少一者为真) | $a -lt 0 -or $a -gt 100 |
| -not 或 ! | 逻辑非(取反) | -not ($a -eq 42) |
| -xor | 异或(恰好一者为真) | $a -xor $b |
来看一个综合的例子。假设我们在写一个简单的成绩判断脚本:
$score = 85
if ($score -ge 90) {
"优秀"
}
elseif ($score -ge 60 -and $score -lt 90) {
"及格"
}
else {
"不及格,好好努力"
}2.5 switch:优雅的多路判断
记得我们刚才说的 elseif 吗?如果你发现自己写出了这样的东西:
if ($day -eq "Monday") {
"😩 又是周一"
} elseif ($day -eq "Tuesday") {
"还有四天"
} elseif ($day -eq "Wednesday") {
"熬到一半了"
} elseif ($day -eq "Friday") {
"🎉 快乐星期五!"
} else {
"过着呢"
}……恭喜你,你已经写出了一段在代码审查中会让人皱眉头的东西。不是说它错了——它完全能跑——而是它太啰嗦了。每一行都在重复 $day -eq,就像你每次介绍朋友都要把对方的全名念一遍。
这种时候,switch 就是你的救星。
2.5.1 基本写法
switch 的基本结构长这样:
switch ($变量) {
值1 { # 当变量等于值1时执行}
值2 { # 当变量等于值2时执行 }
值3 { # 当变量等于值3时执行 }
default { # 以上都不匹配时执行 }
}把上面那个啰嗦的例子改写一下:
switch ($day) {
"Monday"{ "😩 又是周一" }
"Tuesday" { "还有四天" }
"Wednesday" { "熬到一半了" }
"Friday" { "🎉 快乐星期五!" }
default{ "过着呢" }
}清爽多了,不是吗?switch 会拿着$day 的值,从上到下逐一比对,一旦找到匹配的就执行对应的大括号,然后跳出去。default 是兜底选项,当所有条件都不匹配时才会执行——如果你不写 default,没有匹配到也不会报错,只是悄无声息地什么都不做。
2.5.2 switch 的隐藏技能:参数
还记得第一课说的吗,PowerShell 里什么都是对象,什么都可以带参数。switch 也不例外。
| 参数 | 效果 |
| -exact | 精确匹配(默认行为) |
| -wildcard | 使用通配符匹配,可以用 * 和 ? |
| -regex | 使用正则表达式匹配 |
| -casesensitive | 区分大小写(默认不区分) |
| -file | 从文件中读取输入,逐行匹配 |
这几个参数可以让switch 变得极其强大。举个例子,假设你在处理一堆文件名,想根据扩展名分类:
$filename = "report_2026.pdf"
switch -wildcard ($filename) {
"*.pdf" { "这是一份 PDF 文档" }
"*.docx" { "这是一份 Word 文档" }
"*.ps1" { "这是一个 PowerShell 脚本" }
"*.log" { "这是一份日志文件" }
default { "不认识这个格式" }
}
# 输出:这是一份 PDF 文档或者,你想根据用户输入的内容做更灵活的判断,那-regex 就可以大显身手:
$input = "user@example.com"
switch -regex ($input) {
'^\d+$' { "这是一串纯数字" }
'^[\w.]+@[\w.]+\.[a-z]{2,}$' { "这看起来像一个邮箱地址" }
'^1[3-9]\d{9}$' { "这看起来像一个手机号" }
default { "这是什么东西我不知道" }
}
# 输出:这看起来像一个邮箱地址循环:让计算机去重复那些无聊的事
重复做同一件事,却期待不同的结果——这就是疯狂。
爱因斯坦说,重复做同样的事情是疯狂。但他说这句话的时候,大概没想到有一天人类会造出一种东西,它不仅甘愿重复,而且重复得又快又准,从不喊累。
这就是计算机,而让它重复的工具,就叫做循环。
3.1 循环,为什么需要它?
回想一下我们前面提到的集合:你有一个装了一百个名字的集合,你想对每一个名字都说一句“你好”。如果用变量一个个处理,你得写一百行几乎一模一样的代码。但有了循环,你只需要写一次,然后告诉计算机:“把这件事重复做一百遍。”
循环的本质,就是用一段代码,代替无数段重复的代码。
PowerShell 里有好几种循环,它们各有脾气,适合不同的场景。我们一个一个来认识它们。
3.2 for:带计数器的循环
for 是所有编程语言里最经典的循环。它自带一个计数器,告诉你“现在是第几轮”:
for ($i = 0; $i -lt 5; $i++) {
"这是第 $i 轮"
}括号里的三段话,用分号隔开,读起来是一个完整的故事:
$i = 0:初始化。故事的开头——我们从 0 开始。$i -lt 5:条件。每一轮开始前都要检查:还没到5 吗?是的话就继续。$i++:更新。每一轮结束后,计数器加一。(++是+= 1的简写,来自 C 语言的传统)
for 循环非常适合“我知道要重复多少次”的场景,比如生成一个数列、处理固定长度的数据,或者写一个倒计时。
3.3 foreach:为集合而生
for 虽好,但每次都要手动维护一个计数器,再用它去索引集合,还是有点麻烦。大多数时候,我们真正想说的话是:“对集合里的每一个元素,做点什么”——而不是“对第0 个、第 1 个、第 2 个……做点什么”。
foreach 正是为此而来:
$fruits = "苹果", "香蕉", "西瓜", "草莓"
foreach ($fruit in $fruits) {
"我喜欢吃 $fruit"
}
# 输出:
# 我喜欢吃 苹果
# 我喜欢吃 香蕉
# 我喜欢吃 西瓜
# 我喜欢吃 草莓foreach ($fruit in $fruits) 读起来几乎是一句人话:“对于$fruits 里的每一个 $fruit”。每一轮循环,$fruit 都会自动变成集合里的下一个元素,你完全不需要操心索引是多少。
3.4 while:条件满足就一直转
for 和 foreach 都是“知道终点”的循环。但现实中有时候你并不知道要循环多少次——你只知道一个停下来的条件。这时候就该while 出场了:
$n = 1
while ($n -le 10) {
"$n 的平方是 $($n * $n)"
$n++
}while 的逻辑很简单:只要条件成立,就一直执行大括号里的内容。每一轮结束后再回头检查条件,如果条件不再成立,就停下来。
3.5 do-while 和 do-until:先斩后奏
while 在每一轮开始前检查条件。但有时候你希望循环体至少执行一次,然后再决定要不要继续——比如,先让用户输入一次,再判断输入是否合法。
这就是 do-while 和 do-until 的用武之地:
# do-while:先做,再检查条件,条件为真则继续
do {
$input = Read-Host "请输入一个正整数"
} while ([int]$input -le 0)
# do-until:先做,再检查条件,条件为假则继续(直到条件为真才停)
do {
$input = Read-Host "请输入一个正整数"
} until ([int]$input -gt 0)do-while 和 do-until 是一对镜像:两者的行为完全相同,只是条件的语义相反——前者是“条件为真时继续”,后者是“条件为真时停止”。选哪个用,全看哪个读起来更顺口。
3.6 break 和 continue:中途插手
有时候你在循环的中途,想要提前结束,或者跳过当前这一轮。PowerShell 为此提供了两个关键字:
foreach ($n in 1..10) {
if ($n -eq 3) { continue } # 跳过 3,直接进入下一轮
if ($n -eq 7) { break } # 到7 就停,不再继续
"当前数字:$n"
}
# 输出:
# 当前数字:1
# 当前数字:2
# 当前数字:4
# 当前数字:5
# 当前数字:6把 break 和 continue 的区别记住很简单:continue 是“这轮不行,换下一轮”;break 是“不玩了,直接走人”。
3.7 小结:选哪个循环?
兜了一大圈,我们认识了四种循环。它们并没有绝对的高下之分,只有“合不合适”之别:
| 场景 | 推荐 |
| 已知循环次数,或需要索引 | for |
| 遍历一个集合的每个元素 | foreach |
| 不知道次数,只知道停止条件 | while |
| 至少执行一次,再判断要不要继续 | do-while / do-until |
至此,变量、分支、循环——编程里最基础的三块积木,我们都摸了一遍。如果说变量是“给数据取名字”,分支是“根据情况走不同的路”,那循环就是“把一件事反复做直到满意为止”。三者组合在一起,几乎可以描述现实世界里绝大多数的计算逻辑。
工具已经备齐了。接下来,就是你自己的故事了。
尾声:三块积木,和它们背后的东西
我们花了整整一章,讲了三件事:变量、分支、循环。
但,我想多说几句——不是关于 PowerShell,而是关于这三件事本身所折射出的某种更深层的东西。如果你只是想学会写脚本,这一节可以跳过。但如果你对“为什么”比“怎么做”更感兴趣,那就继续读下去。
4.1 这三件事,已经足够了
先从一个数学事实说起。
1936 年,一个叫艾伦·图灵的英国人提出了一个思想实验:假设有一台极其简单的机器,它能读写纸带上的符号,并根据当前状态决定下一步做什么。这台机器简单到荒谬——但图灵证明了,任何可以被计算的东西,这台机器都能算。
这就是图灵完备性(Turing Completeness)。而一个编程语言要达到图灵完备,所需要的最小零件清单,和我们这一章讲的东西几乎完全重合:能存储数据(变量),能做条件判断(分支),能重复执行(循环)。
换句话说,你现在已经掌握的这三块积木,在理论上足以描述人类迄今为止所能想象到的一切计算过程。不是大部分,是全部。
你手里的,不是三个语法糖。是图灵给这个世界留下的骨架。
4.2 给混沌命名:变量的哲学代价
变量这件事,表面上是技术操作,骨子里是一种认识论姿态:把混沌的现实切割成离散的、可命名的碎片。
数学做的是同一件事。代数里的 ,不是某一个具体的数,而是“任意一个数”的占位符。正是这种抽象能力,让人类得以用有限的符号描述无限的规律——从牛顿的 ,到爱因斯坦的 ,本质上都是在说“不管具体是什么,这些量之间有这样的关系”。
但这里有一个代价,数学家很少提,社会学家倒是反复强调:命名会固化。
一旦你给某件事取了名字,这件事就从流动的现实里被剥离出来,钉进了一个固定的概念框里。$user_type = "普通用户" 这行代码,在程序里无伤大雅;但当同样的逻辑被用到人身上——把人分成“正常”和“异常”,“主流”和“边缘”,“有价值”和“没价值”的时候,这个命名就开始产生真实的伤害。
算法推荐系统就是一个活生生的例子。它把你的每一次点击都存进变量,然后用这些变量描绘出一个“你的画像”,再根据这个画像决定给你看什么。问题在于,你本人是流动的、矛盾的、会变化的;而那个画像是静止的、确定的、趋向于自我强化的。系统给“爱看猫咪视频的你”推更多猫咪视频——于是你看了更多猫咪视频,于是系统更确信你“就是”一个爱看猫咪视频的人。
变量捕捉了现实的一个切面,然后把这个切面误认成了现实的全部。
4.3 非此即彼:分支的暴力
分支的底层,是布尔代数。乔治·布尔在 1854 年出版的《思维规律》里,试图用数学语言重新表达逻辑——而他选择的基本单位,是 和 ,是“真”和“假”,是“是”和“否”。
这套体系极其优雅,也极其有用。正是因为计算机的每一个晶体管都只有“通”和“断”两种状态,布尔代数才得以成为整个数字文明的数学地基。
但布尔代数有一个内置的暴力:它不允许中间地带存在。
if ($isGoodPerson) 这行代码,在执行的瞬间,就要求你给出一个明确的 $true 或者 $false。没有“大概是”,没有“在某些情况下是”,没有“这取决于你的定义”。计算机不接受模糊,因为模糊无法被编译。
这当然是计算机正确运作所必需的特性。但当这种二元思维方式溢出到人类社会的讨论里,就会变成一种智识上的懒惰,甚至是一种武器。
“你支持我们还是反对我们。”“这个人是好人还是坏人。”“这个政策是对的还是错的。”——每一个这样的问题,都在执行一次隐形的强制类型转换,把一个复杂的、多维度的现实,硬塞进一个只有两个值的布尔变量里。被截断的,是那些无法被简单归类的真相。
相反的说法的反面是错误的;但深刻真理的反面,可能是另一个深刻真理。
分支结构是必要的。但一个只会写 if-else、不知道还有光谱存在的程序员——或者社会——是危险的。
4.4 历史的循环,和跳出去的可能
最后说循环。
循环的数学对应物,是数学归纳法和递推关系:你定义好初始状态,定义好每一步从上一步如何推导,然后让这个过程自动展开。斐波那契数列、复利增长、人口模型——很多自然和社会规律,本质上都是循环结构在现实世界的投影。
而人类历史,毫不夸张地说,充满了循环的影子。
经济危机大约每隔几十年爆发一次,原因总是似曾相识:过度杠杆、监管失位、集体非理性。帝国兴衰的故事一遍遍重演,连剧本都懒得改。技术浪潮带来财富,财富带来垄断,垄断带来保守,保守压制下一次技术浪潮——直到有人打破它,再来一轮。
这些循环之所以能持续,是因为它们的“退出条件”从未被真正触发。就像一段忘记写 break 的代码,会一直跑下去,直到外力介入。
但这里有一个让人不舒服的对称:计算机里的无限循环是 bug,而历史里的循环……有时候是被刻意维护的。因为循环对身处其中的某些人是有利的。打破循环意味着重构权力结构,而权力结构从不会自愿重构自己。
你现在已经知道怎么写
break了。问题在于,在真实的世界里,找到那行代码在哪里,要难得多。
4.5 最后……
变量、分支、循环。三块积木,三种思维方式,三面镜子。
它们既是你写脚本的工具,也是你理解世界运作方式的模型。用好它们,但也要时刻记得它们的局限——任何足够强大的工具,都值得被审视。
- 1原文引用,未作修改