最好的论据是Dijkstra自己提出的:
>
您希望范围的大小是一个简单的差尾 ;−nbsp;开始;
当序列退化为空序列时,包含下限更为“自然”,这也是因为替代方案(排除下限)需要存在“开始前一个”的哨兵值。
你仍然需要解释为什么你从零开始计算而不是从一开始计算,但这不是你的问题的一部分。
当您有任何类型的算法处理对基于范围的构造的多个嵌套或迭代调用时,[begin,end)约定背后的智慧会一次又一次地得到回报,这些构造是自然连锁的。 相反,使用双闭范围将会招致off-by-one,以及非常不愉快和嘈杂的代码。 例如,考虑一个分区[N0,N1)[N1,N2)[N2,N3)。另一个例子是(it=begin;it!=end;++it)的标准迭代循环,它运行
end-begin
次。如果两端都包含,相应代码的可读性就会差得多--想象一下您如何处理空范围。
最后,我们还可以很好地论证为什么计数应该从零开始:根据我们刚刚建立的范围的半开约定,如果给出了一个N个元素的范围(比如枚举数组的成员),那么0就是自然的“开始”,这样您就可以将范围写成[0,N),而不需要任何笨拙的偏移或校正。
简而言之:在基于范围的算法中,我们并没有到处看到数字1
,这一事实是[begin,end)约定的直接后果和动机。
实际上,如果考虑到迭代器不是指向序列的元素,而是指向序列之间的元素,通过取消引用访问正好指向序列的下一个元素,那么很多与迭代器相关的东西突然变得更有意义了。 然后,“One past End”迭代器突然有了立竿见影的意义:
+---+---+---+---+
| A | B | C | D |
+---+---+---+---+
^ ^
| |
begin end
显然begin
指向序列的开头,而end
指向同一序列的结尾。 取消引用begin
访问元素a
,取消引用end
没有意义,因为没有元素对它具有权限。 另外,在中间添加迭代器i
会给出
+---+---+---+---+
| A | B | C | D |
+---+---+---+---+
^ ^ ^
| | |
begin i end
您会立即看到,从begin
到i
的元素范围包含元素a
和b
,而从i
到end
的元素范围包含元素c
和d
。 取消引用i
给出了它的元素权限,即第二个序列的第一个元素。
就连反向迭代器的“一次递减(off-by-one)”也会突然变得很明显:
+---+---+---+---+
| D | C | B | A |
+---+---+---+---+
^ ^ ^
| | |
rbegin ri rend
(end) (i) (begin)
我在下面的括号中编写了相应的非反向(基本)迭代器。 您可以看到,属于i
(我将其命名为ri
)的反向迭代器仍然指向元素b
和c
之间。 但是,由于颠倒了顺序,现在元素b
位于它的右侧。
为什么标准将end()
定义为一个在结束之后,而不是在实际结束时?
因为:
begin()
等于end()
& end()
,循环就会继续。