为什么标准迭代器范围[开始,结束]而不是[开始,结束]?

时间:2012-04-01 09:40:25

标签: c++ stl iterator standards

为什么标准将end()定义为结尾的一个,而不是实际结束?

7 个答案:

答案 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

您会立即看到begini的元素范围包含元素AB,而iend的元素范围C包含元素Di。解除引用 +---+---+---+---+ | D | C | B | A | +---+---+---+---+ ^ ^ ^ | | | rbegin ri rend (end) (i) (begin) 给出了它的元素,这是第二个序列的第一个元素。

即使反向迭代器的“逐个”突然变得明显:反转该序列给出:

i

我在下面的括号中写了相应的非反向(基础)迭代器。你看,属于ri的反向迭代器(我将其命名为B仍然指向元素CB之间。但是由于序列的反转,现在元素{{1}}就在它的右侧。

答案 2 :(得分:72)

为什么标准将end()定义为结尾的一个,而不是实际结束?

因为:

  1. 避免对空范围进行特殊处理。对于空范围,begin()等于 end()&amp;
  2. 它使得遍历元素的循环的结束标准变得简单:循环简单 只要未达到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)

  1. 如果容器为空,begin() == end()
  2. C ++程序员倾向于在循环条件中使用!=而不是<(小于),因此 让end()指向一个非常方便的位置。