抽象代码:分号的艺术
C 语言里,分号是语句的句号。规则人人都懂:句子写完,分号收尾。
但分号其实是”语句终止符”,它并没有规定自己必须站在句尾。于是有了下面这个程序——每个分号都搬家搬到了下一行的行首:
1 |
|
第一眼:int i = 0 后面没有分号,这能编译?——能。真机运行,输出干干净净的 0 到 9:
1 | 0 |
原理:把分号从句尾搬到句首
关键在于理解分号到底”属于谁”。C 编译器眼里,换行就是普通的空白,int i = 0 下一行行首的那四个分号,其中第一个分号恰好补上了上一句缺的句号,剩下三个分号是三条合法的空语句(empty statement,什么都不做)。
也就是说,编译器看到的其实是:
1 | int i = 0; |
行首的分号串,一半是补票(终止上一句),一半是空语句(装饰性缩进)。分号从句尾搬到了行首,顺手客串了缩进的角色——这段代码里每个 ;;;; 的数量还刻意不同,嵌套越深分号越多,真就当成缩进用了。
文件末尾还贴心地附上了”正常人版本”的对照注释,两相对比,最有喜剧效果:
1 | // int main(int argc, char* arg[]) |
(顺便,注释里的 x-- > 0 就是那个著名的”趋向运算符”——下划线完形填空那篇里,这个梗甚至被做成了宏。)
进阶版:整个程序只剩四个分号
同一思想的另一个变体更极端——一个 hello world,把分号省到极致:
1 |
|
真机运行,输出:
1 | hello world |
逐句拆解这套”分号接力”:第一行 ;;;;; 是五个空语句;第二行 printf("hello world") 没有——它靠下一行行首的第一个分号收尾;return 0 同理,靠再下一行的行首分号收尾。**每一行的行首分号,都在替上一行还债,同时给本行埋下新的债。**整个程序像一场分号的接力赛,最后一棒由 } 前的分号冲线。
正经话
空语句是合法语句,分号归属看词法不看排版——这两条规则都是真的,所以这套写法完全符合标准。顺便说,它的精神续作在隔壁:让下一行行首的分号变成”缩进标记”、配合 while(x --> 0) 的视觉梗,思路一脉相承——都是在玩”分隔符到底分隔了什么“这个最底层的问题。
系列其他文章见抽象代码宣言。
