这个参数来自著名计算机科学家Dijkstra(Dijkstra算法的发明者)的一份小型三页笔记。在其中,他阐述了我们可能从零开始索引的原因,并且故事始于尝试迭代自然数序列(指数轴上的0、1、2、3等序列)。
有4种可能的方式来索引2、3、...、12。
a.) 2 <= i < 13
b.) 1 < i <= 12
c.) 2 <= i <= 12
d.) 1 < i < 13
他提到a.)和b.)的优点是两个边界之差等于序列中元素的数量。他还提到,如果两个序列相邻,则一个序列的上限等于另一个序列的下限。他说这并不能帮助决定使用a.)还是b.),所以他将重新开始思考。
他立即从列表中删除b.)和d.),因为如果我们从零开始一个自然序列,它们将具有超出自然数(-1)的边界,这是“丑陋”的。他补充说,我们更喜欢使用
<=
作为下限——留下a.)和c.)。
对于一个空集,他指出,在b.)和c.)中,其上限将为-1,这也是“丑陋”的。
所有这些观察结果都导致了用a.)来表示自然数序列的惯例,事实上,这就是大多数人编写遍历数组的
for
循环的方式:
for(int i = 0; i < size; ++i)
。我们包括下限(
i <= 0
),并排除上限(
i < size
)。
如果您要使用类似
for(int i = 0; i <= iterations - 1; ++i)
来执行
i
次迭代,您可以看到在空集的情况下他所指的“丑陋”。对于零次迭代,
iterations - 1
将为
-1
。
因此,按照惯例,我们使用 a.),由于数组索引从零开始,我们在 for
循环中使用 i = 0
开始一个巨大的数字。然后,我们推理出简约性 - 如果没有其他原因要以不同的方式执行不同的操作,最好以完全相同的方式执行它们。
现在,如果我们使用 a.) 来进行基于 1 的索引而不是基于 0 的索引,则会得到 for(int i = 1; i < size + 1; ++i)
。这个 + 1
看起来有点“丑陋”,所以我们更喜欢用 i = 0
开始我们的范围。
总之,你应该使用
for(int i = 0; i < iterations; ++i)
的循环语句进行
iterations
次迭代。像
for(int i = 1; i <= iterations; ++i)
这样的语句可以说是相当易懂且可行,但是否有任何好理由添加不同的循环方法来迭代
iterations
次呢?只需使用与索引数组相同的模式即可。换句话说,使用
0 <= i < size
。更糟糕的是,基于
1 <= i <= iterations
的循环并没有所有 Dijkstra 提出的支持使用
0 <= i < iterations
作为一种惯例的理由。
你不用太担心。实际上,Dijkstra 本人曾经和几乎所有认真编程的人一样想过同样的问题。调整你的风格就像一个热爱自己手艺的工匠站在的基础上。追求简洁和按照其他人(包括你自己——数组的循环!)编写代码的方式都是值得追求的健康的事情。
由于这个约定,当我看到
for(i = 1
时,我注意到与约定的偏离。然后我会更加谨慎地处理那段代码,认为
for
内部的逻辑可能依赖于从
1
而不是
0
开始。虽然这只是微小的差别,但没有理由在如此广泛使用的约定中添加这种可能性。如果你碰巧有一个很大的
for
体,这个抱怨就变得不那么微小了。
要理解为什么从1开始没有意义,考虑将论点自然地推向结论——“但对我来说很有意义的”这一论点:你可以从任何一个数字开始循环变量i!如果我们从常规中解放出来,为什么不循环 for(int i = 5; i <= iterations + 4; ++i)
?或者 for(int i = -5; i > -iterations - 5; --i)
?只需要像大多数程序员在大多数情况下所做的那样,并在有充分理由时才采取与众不同的方式——不同之处表明了你的代码中的for
的主体包含了一些不寻常的内容。使用标准方法,我们知道for
要么是从0开始索引/排序/执行算术运算的序列,要么是连续执行某种逻辑iterations
次。
请注意这种惯例是多么普遍。在 C++ 中,每个标准容器都在
[start, end)
之间迭代,这对应于上面的 a.)。在那里,他们这样做是为了使结束条件可以是
iter != end
,但我们已经按照一种方式进行逻辑操作,并且这种方式没有立即的缺点,自然地流入了“为什么要以两种不同的方式进行操作,当我们已经在这种情况下以这种方式进行操作?”的论点中。在他的小论文中,Dijkstra 还提到了一种称为 Mesa 的语言,它可以使用特定的语法执行 a.)、b.)、c.) 或 d.)。他声称,在那里,a.) 在实践中获胜,而其他方法则与错误的原因相关联。然后,他哀叹 FORTRAN 索引从 1 开始,而 PASCAL 则通过惯例采用了 c.)。
[begin, end)
,而你的范围是(begin, end]
。 - NathanOliver