C++ 为什么标准迭代器范围是[begin,end]而不是[begin,end]?
为什么标准将C++ 为什么标准迭代器范围是[begin,end]而不是[begin,end]?,c++,stl,iterator,standards,C++,Stl,Iterator,Standards,为什么标准将end()定义为结束后的一个,而不是实际结束时的一个?为什么标准将end()定义为结束后的一个,而不是实际结束时的一个? 因为: 它避免了对空范围的特殊处理。对于空范围,begin()等于 end()& 它使遍历元素的循环的结束条件变得简单:循环简单 只要未到达end(),就继续 因为那时 size() == end() - begin() // For iterators for whom subtraction is valid 而且你不必做像这样尴尬的事情 // Neve
end()
定义为结束后的一个,而不是实际结束时的一个?为什么标准将end()
定义为结束后的一个,而不是实际结束时的一个?
因为:
begin()
等于
end()
&end()
,就继续size() == end() - begin() // For iterators for whom subtraction is valid
而且你不必做像这样尴尬的事情
// Never mind that this is INVALID for input iterators...
bool empty() { return begin() == end() + 1; }
而且您不会意外地编写错误代码,比如
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
另外:如果end()
指向有效元素,那么find()
将返回什么?是否确实需要另一个名为
invalid()
的成员,该成员返回无效的迭代器?!两个迭代器已经够痛苦的了 哦,还有见相关帖子
也:
如果
结尾
在最后一个元素之前,您将如何在真正的结尾插入()
- 您希望范围的大小是一个简单的差异端点−开始
- 当序列退化为空序列时,包含下界更为“自然”,这也是因为备选方案(不包括下界)要求存在“开始前一个”的哨兵值
你仍然需要证明为什么你从零开始计数,而不是一,但这不是你问题的一部分
背后的智慧当你有任何一种算法处理多个嵌套或迭代调用的基于范围的结构时,一次又一次地重复它,这是自然的。相反,使用一个双闭的范围会引起一个非常讨厌和嘈杂的代码。例如,考虑一个分区[n0,N1 ] [N1,n2 ]。[n2,n3)。另一个例子是(it=begin;it!=end;++it)
的标准迭代循环,它运行end-begin
次。如果两端都包含在内,相应的代码可读性就会差得多——想象一下您将如何处理空范围
最后,我们还可以提出一个很好的论点,为什么计数应该从零开始:根据我们刚刚建立的范围的半开放约定,如果给你一个N个元素的范围(比如枚举数组的成员),那么0是自然的“开始”,这样你就可以将范围写为[0,N),没有任何令人尴尬的偏移或校正
简言之:在基于范围的算法中,我们没有在任何地方看到数字1
,这一事实是[begin,end]约定的直接结果和动机。由于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++;
}
半封闭范围的迭代器习惯用法[begin(),end())
最初是基于普通数组的指针算法。在这种操作模式下,您将拥有传递数组和大小的函数
void func(int* array, size_t size)
当您有以下信息时,转换到半封闭范围非常简单:
int* begin;
int* end = array + size;
for (int* it = begin; it < end; ++it) { ... }
然而,在完全封闭的范围内,没有简单的方法来做这件事。事实上,如果你认为迭代器不指向序列的元素,而是在中间,那么迭代器相关的东西会突然变得更有意义。迭代器突然有了直接的意义:
+---+---+---+---+
| A | B | C | D |
+---+---+---+---+
^ ^
| |
begin end
<代码> >开始> 指向序列的起始点,<代码>结束>代码>指向同一序列的结尾。取消代码<代码>开始< /代码>访问元素<代码> A<代码>,取消引用<代码>结束>代码没有意义,因为在元素G中添加了一个迭代器<代码> 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
位于其右侧
如果容器为空,begin()==end()
C++程序员倾向于使用!=
而不是,我猜“因为这是标准所说的”不会削减它,对吧?:)@LuchianGrigore:当然不会。这会削弱我们对(背后的人)的尊重标准。我们应该认为标准做出选择是有原因的。我想,这个解释也值得你注意:简言之,计算机不像人那样算数。但如果你对为什么人们不像计算机那样算数感到好奇,我建议你深入研究人类发现有一个数字的问题mber比1小。因为只有一种方法可以生成“最后一个”,它通常不便宜,因为它必须是真实的。生成“you fall of the end of the cliff”总是便宜的,许多可能的表示都可以。(void*)“ahhhhh”就可以了。典型的C代表l
+---+---+---+---+
| A | B | C | D |
+---+---+---+---+
^ ^ ^
| | |
begin i end
+---+---+---+---+
| D | C | B | A |
+---+---+---+---+
^ ^ ^
| | |
rbegin ri rend
(end) (i) (begin)