本篇主要内容如下:
数组下标为何从 0 开始?
来自大神的论证:为何编号应从零开始?
戴克斯特拉的论证手迹
数组下标为何从 0 开始?
bucket = key % n;但如果从 1 开始,就得写成 bucket = (key % n) + 1。y * w + x;而如果用 1 起始索引,公式就变成 y * w + x - w,明显更复杂。ord() 能返回字符的 ASCII 码。用 0 起始索引时,字符 c 的位置就是 ord(c) - ord('A');若用 1 起始索引,则必须写成 ord(c) - ord('A') + 1。事实上,从 1 开始计数才是一种历史的“意外”:人类语言早在发明“零”这个概念之前,就需要用“第一”、“第二”这样的序数词了。这种习惯延续下来,反而给现代技术带来了一些混乱。一个典型的例子就是:人们常误以为 1800 年代属于“18 世纪”,但实际上,19 世纪指的是从 1801 年 1 月 1 日到 1900 年 12 月 31 日这段时间——这正是“从 1 开始计数”造成的认知偏差。
来自大神的论证:为何编号应从零开始?
编程考古:艾兹赫尔·W·戴克斯特拉(Edsger W. Dijkstra):才华横溢、个性鲜明且坚持主见
若想表示自然数序列 2, 3, …, 12(避免使用那令人讨厌的三个点“…”),我们有以下四种区间表示方式可选:
a)
这些表示法之间有没有优劣之分?答案是肯定的。
首先可以注意到,选项 a) 和 b) 有一个优点:上下界之差正好等于子序列的长度。由此还可推出:在这些约定下,两个相邻子序列的边界恰好相接——前一个的上界等于后一个的下界。这些观察固然正确,却仍不足以让我们在 a) 与 b) 之间做出选择。因此,我们不妨换个角度重新思考。
自然数中存在一个最小值(即 0 或 1,取决于定义;此处按现代数学惯例,自然数包含 0)。如果采用 排除下界 的方式(如 b) 和 d) 所示),那么当我们要表示“从最小自然数开始”的子序列时,所写出的下界就会落入“非自然数”的范围——这显然很别扭。因此,下界应采用“≤”形式,即倾向于 a) 或 c)。
再考虑上界:当我们处理一个从最小自然数开始、但长度逐渐缩短直至为空的序列时,若坚持包含上界(如 c) 所示),那么空序列的上界将被迫取一个“不存在”的值(比如 -1),这同样不自然。因此,上界应采用“<”形式,即倾向于 a) 或 d)。
综合以上两点,唯一同时满足“下界包含、上界排除”的选项是 a)。因此,a) 是最合理的选择。
附注:施乐帕洛阿尔托研究中心(Xerox PARC)开发的编程语言 Mesa,曾为上述四种区间表示法都提供了专门语法。然而大量实践经验表明,除 a) 外的其他三种用法常常导致代码笨拙且容易出错。正因如此,Mesa 程序员如今被强烈建议不要使用其余三种特性。我之所以提及这一实践证据(尽管其权威性有限),是因为有些人对未经实际验证的理论结论总感不安。
接下来的问题是:对于一个长度为
若遵循上述最优的区间约定(即 a)),那么:
因此,我们应当让序号从 0 开始:一个元素的序号(下标)就等于它前面有多少个元素。这个看似微小的选择,实则蕴含深刻意义——它提醒我们:经过几个世纪之后,是时候真正把“零”视为一个最自然的数了。
附注:许多编程语言在设计时并未充分重视这一细节。例如,FORTRAN 的数组下标永远从 1 开始;ALGOL 60 和 Pascal 采用了上述 c) 的闭区间约定;而较晚出现的 SASL 语言竟又回归 FORTRAN 的做法,将序列定义为“正整数上的函数”。实在令人惋惜!
本文的写作缘起于最近发生的一件事:一位数学系同事(非计算机科学家)情绪激动地指责几位年轻的计算机学者“过于咬文嚼字”,只因他们习惯性地从 0 开始编号。在他看来,这种刻意采用最合理约定的做法竟成了一种挑衅。(顺便一提,连我在文中使用的“End of Remark”这类标记也被视为挑衅;但这类约定其实很有用——我曾听说有学生因为默认题目只印在第一页底部,结果差点考试不及格。)
对此,我想引用安东尼·杰伊(Antony Jay)的一句话作为结语:
“在体制化的信仰中,正如在其他宗教里一样,异端者必须被驱逐,并非因为他很可能错了,而是因为他有可能是对的。”
Plataanstraat 55671 AL NUENEN, 荷兰1982 年 8 月 11 日艾兹赫尔·W·戴克斯特拉(Edsger W. Dijkstra)教授Burroughs 公司研究员


