Why does the Standard define end() as one past the end, instead of at the actual end?
因为:
它避免了对空范围的特殊处理 . 对于空范围, begin() 等于 end() &
它使迭代元素的循环的结束标准变得简单:只要未达到 end() ,循环就会继续 .
22
实际上,如果你认为迭代器没有指向序列的元素而是介于两者之间,并且通过解引用访问右边的下一个元素,很多迭代器相关的东西突然变得更有意义 . 然后"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 给它右边的元素,这是第二个序列的第一个元素 .
即使反向迭代器的“逐个”突然变得明显:反转该序列给出:
+---+---+---+---+
| D | C | B | A |
+---+---+---+---+
^ ^ ^
| | |
rbegin ri rend
(end) (i) (begin)
我在下面的括号中写了相应的非反向(基础)迭代器 . 你看,属于 i (我将其命名为 ri )的反向迭代器仍然指向元素 B 和 C 之间 . 但是由于顺序颠倒了,现在元素 B 就在它的右侧 .
0
因为那时候
size() == end() - begin() // For iterators for whom subtraction is valid
而且你不必像_1052157那样做
// Never mind that this is INVALID for input iterators...
bool empty() { return begin() == end() + 1; }
你不会不小心写 erroneous code 之类的
bool empty() { return begin() == end() - 1; } // a typo from the first version
// of this post
// (see, it really is confusing)
bool empty() { return end() - begin() == -1; } // Signed/unsigned mismatch
// Plus the fact that subtracting is also invalid for many iterators
另外: What would find() return if end() pointed to a valid element? 你真的想要另一个名为 invalid() 的成员返回一个无效的迭代器吗?! 两个迭代器已经足够痛苦了......
然而's no easy way to do this with fully-closed ranges. You'被 <= 困住了 .
在C中支持 < 和 > 操作的唯一一种迭代器是随机访问迭代器 . 如果必须为C中的每个迭代器类编写一个 <= 运算符,那么'd have to make all of your iterators fully comparable, and you'd用于创建功能较少的迭代器的选择较少(如_ C使用全闭范围,例如 std::list 上的双向迭代器,或 iostreams 上的输入迭代器) .
274
使用 end() 指向一个结尾,可以很容易地使用for循环迭代集合:
for (iterator it = collection.begin(); it != collection.end(); it++)
{
DoStuff(*it);
}
当 end() 指向最后一个元素时,循环会更复杂:
iterator it = collection.begin();
while (!collection.empty())
{
DoStuff(*it);
if (it == collection.end())
break;
it++;
}
7 回答
最好的论点是由Dijkstra himself制作的:
当序列退化为空的时,
你仍然需要证明为什么你开始计算零而不是一个,但这不是你问题的一部分 .
当你有任何类型的算法来处理基于范围的构造的多个嵌套或迭代调用时,[开始,结束]约定背后的智慧会一次又一次地得到回报 . 相比之下,使用双闭合范围会产生一个一个接一个,并且非常令人不愉快且噪声很大的代码 . 例如,考虑分区[n0,n1] [n1,n2] [n2,n3) . 另一个例子是标准迭代循环
for (it = begin; it != end; ++it)
,它运行end - begin
次 . 如果两端都是包容性的话,相应的代码将更不易读 - 并想象你如何处理空范围 .最后,我们还可以提出一个很好的论据,为什么计数应该从零开始:对于我们刚刚 Build 的范围的半开公约,如果给出一系列N个元素(比如枚举数组的成员),那么0是自然"beginning",因此您可以将范围写为[0,N),而不会有任何尴尬的偏移或更正 .
简而言之:我们没有在基于范围的算法中看到数字
1
这一事实是[开始,结束]约定的直接结果和动机 .Why does the Standard define end() as one past the end, instead of at the actual end?
因为:
它避免了对空范围的特殊处理 . 对于空范围,
begin()
等于end()
&它使迭代元素的循环的结束标准变得简单:只要未达到
end()
,循环就会继续 .实际上,如果你认为迭代器没有指向序列的元素而是介于两者之间,并且通过解引用访问右边的下一个元素,很多迭代器相关的东西突然变得更有意义 . 然后"one past end"迭代器突然变得明显:
显然
begin
指向序列的开头,end
指向同一序列的结尾 . 解除引用begin
访问元素A
,解除引用end
是没有意义的,因为它没有任何元素权限 . 另外,在中间添加一个迭代器i
并且您立即看到
begin
到i
的元素范围包含元素A
和B
,而i
到end
的元素范围包含元素C
和D
. 解除引用i
给它右边的元素,这是第二个序列的第一个元素 .即使反向迭代器的“逐个”突然变得明显:反转该序列给出:
我在下面的括号中写了相应的非反向(基础)迭代器 . 你看,属于
i
(我将其命名为ri
)的反向迭代器仍然指向元素B
和C
之间 . 但是由于顺序颠倒了,现在元素B
就在它的右侧 .因为那时候
而且你不必像_1052157那样做
你不会不小心写 erroneous code 之类的
另外: What would find() return if end() pointed to a valid element?
你真的想要另一个名为
invalid()
的成员返回一个无效的迭代器吗?!两个迭代器已经足够痛苦了......
哦,还有 see this related post .
另外:
如果
end
在最后一个元素之前,你怎么会 insert() at the true end?!半闭范围的迭代器习语
[begin(), end())
最初基于普通数组的指针算法 . 在该操作模式中,您将拥有传递数组和大小的函数 .当您获得该信息时,转换为半闭范围
[begin, end)
非常简单:要使用全封闭范围,它会更难:
由于指向数组的指针是C中的迭代器(并且语法设计为允许这样做),因此调用
std::find(array, array + size, some_value)
要比调用std::find(array, array + size - 1, some_value)
容易得多 .另外,如果使用半闭区域,可以使用
!=
运算符检查结束条件,因为(如果运算符定义正确)<
暗示!=
.然而's no easy way to do this with fully-closed ranges. You'被
<=
困住了 .在C中支持
<
和>
操作的唯一一种迭代器是随机访问迭代器 . 如果必须为C中的每个迭代器类编写一个<=
运算符,那么'd have to make all of your iterators fully comparable, and you'd用于创建功能较少的迭代器的选择较少(如_ C使用全闭范围,例如std::list
上的双向迭代器,或iostreams
上的输入迭代器) .使用
end()
指向一个结尾,可以很容易地使用for循环迭代集合:当
end()
指向最后一个元素时,循环会更复杂:如果容器为空,
begin() == end()
.C程序员倾向于在循环条件中使用
!=
而不是<
(小于),因此将end()
指向一个非常方便的位置 .