为什么标准将end()
定义为结尾的一个,而不是实际结束?
答案 0 :(得分:279)
最好的论据很容易就是Dijkstra himself:
您希望范围的大小是一个简单的区别结束 - 开始;
当序列退化为空的时,包括下限更“自然”,并且因为替代(排除下限)将需要存在“one-before” - 开始的“哨兵价值。
你仍然需要证明为什么你开始计算零而不是一,但这不是你问题的一部分。
[开始,结束]约定背后的智慧一次又一次地得到回报,当你有任何类型的算法处理多个嵌套或迭代调用基于范围的结构时,这些结构自然地链接。相比之下,使用双闭范围将导致一个接一个,并且非常令人不愉快且噪声很大。例如,考虑分区[ n 0 , n 1 )[ n 1 , n 2 )[ n 2 , n <子> 3 子>)。另一个例子是标准迭代循环for (it = begin; it != end; ++it)
,它运行end - begin
次。如果两端都是包容性的话,相应的代码将更不易读 - 并想象你如何处理空范围。
最后,我们还可以提出一个很好的论据,为什么计数应该从零开始:对于我们刚刚建立的范围的半开公约,如果给出一系列 N 元素(比如说)枚举数组的成员,然后0是自然的“开始”,这样你就可以将范围写为[0, N ),没有任何尴尬的偏移或修正。
简而言之:我们在基于范围的算法中看不到数字1
这一事实是[开始,结束]约定的直接结果和动机。
答案 1 :(得分:75)
实际上,如果你认为迭代器没有将指向序列的元素而介于之间,那么很多迭代器相关的东西突然变得更有意义了,并且取消引用访问正确的下一个元素。然后,“一个过去的结束”迭代器突然变得立竿见影:
+---+---+---+---+
| 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
之间。但是由于序列的反转,现在元素{{1}}就在它的右侧。
答案 2 :(得分:72)
为什么标准将end()
定义为结尾的一个,而不是实际结束?
因为:
begin()
等于
end()
&amp; end()
,就会继续。答案 3 :(得分:60)
因为那时
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
另外:如果find()
指向有效元素,end()
会返回什么?
你真的想要另一个名为invalid()
的成员返回一个无效的迭代器吗?!
两个迭代器已经足够痛苦了......
哦,请参阅this相关帖子。
如果end
位于最后一个元素之前,那么您将如何 insert()
真正结束?
答案 4 :(得分:22)
半闭范围[begin(), end())
的迭代器惯用法最初基于普通数组的指针算法。在该操作模式中,您将拥有传递数组和大小的函数。
void func(int* array, size_t size)
如果您拥有该信息,转换为半封闭范围[begin, end)
非常简单:
int* begin;
int* end = array + size;
for (int* it = begin; it < end; ++it) { ... }
要使用全封闭范围,更难:
int* begin;
int* end = array + size - 1;
for (int* it = begin; it <= end; ++it) { ... }
由于指向数组的指针是C ++中的迭代器(并且语法设计为允许这样做),因此调用std::find(array, array + size, some_value)
比调用std::find(array, array + size - 1, some_value)
要容易得多。
另外,如果您使用半封闭范围,则可以使用!=
运算符检查结束条件,因为(如果您的运算符定义正确)<
隐含!=
}。
for (int* it = begin; it != end; ++ it) { ... }
然而,使用全闭范围并没有简单的方法。你被<=
困住了。
在C ++中支持<
和>
操作的唯一一种迭代器是随机访问迭代器。如果必须为C ++中的每个迭代器类编写一个<=
运算符,则必须使所有迭代器完全可比,并且创建功能较少的迭代器(例如双向迭代器)的选择较少如果C ++使用完全闭合范围,则std::list
或对iostreams
进行操作的输入迭代器)。
答案 5 :(得分:8)
当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++;
}
答案 6 :(得分:0)
begin() == end()
。 !=
而不是<
(小于),因此
让end()
指向一个非常方便的位置。