为什么将标准定义end()
为末尾而不是末尾?
为什么将标准定义end()
为末尾而不是末尾?
Answers:
最好的论据是Dijkstra本人提出的:
你想要的范围的大小是一个简单的区别结束 - 开始 ;
当序列退化为空序列时,包含下界更“自然”,并且还因为替代方案(不包括下界)将需要存在“一个从头开始”的前哨值。
您仍然需要说明为什么从零开始而不是从1开始计数,但这不是问题的一部分。
[begin,end)约定背后的智慧一次又一次地获得回报,当您拥有某种类型的算法来处理对基于范围的构造的多个嵌套或迭代调用时,这种调用自然会链接。相比之下,使用双封闭范围会产生异样的结果以及极其不愉快和嘈杂的代码。例如,考虑分区[ 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
基于范围的算法中我们没有看到数字的事实是[begin,end)约定的直接结果和动机。
begin
和分别end
视为int
带有0
和的N
,则非常适合。可以说,这是!=
比传统更为自然的条件<
,但是直到我们开始考虑更通用的系列之前,我们才发现这一点。
++
-incrementable迭代器模板step_by<3>
,该模板将具有最初宣传的语义。
!=
时应该使用<
,那是一个错误。顺便说一句,通过单元测试或断言很容易发现错误之王。
其实,很多的迭代器相关的东西突然更有道理,如果你考虑不迭代器指向在该序列的元素,但在两者之间,与非关联访问的下一个元素的权利吧。然后,“ one end end”迭代器突然变得有意义:
+---+---+---+---+
| A | B | C | D |
+---+---+---+---+
^ ^
| |
begin end
显然begin
指向序列的开头,并end
指向同一序列的结尾。取消引用begin
访问element 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
在它的右边。
foo[i]
)是位置()之后的缩写,i
。考虑一下,我想知道一种语言对于“位置i之后的项目”和“位置i之后的项目”使用单独的运算符是否有用,因为许多算法都处理成对的相邻项目,并说“ “位置i两侧的项目”可能比“位置i和i + 1的项目”干净。
begin[0]
(假设有一个随机访问迭代器)将访问element 1
,因为0
在我的示例序列中没有元素。
start()
在类中定义一个函数来启动特定过程或其他任何功能时,如果它与已经存在的过程发生冲突将很烦人)。
因为那
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()
返回无效的迭代器?
两个迭代器已经足够痛苦了……
哦,请参阅此相关文章。
如果在end
最后一个元素之前,您将如何insert()
走到最后呢?
半封闭范围的迭代器习惯用法[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 ++中的每个迭代器类编写一个运算符,则必须使所有迭代器具有完全可比性,并且创建功能较弱的迭代器(如on上的双向迭代器std::list
或输入迭代器)的选择更少。iostreams
如果C ++使用的是全封闭范围,则对进行操作)。
begin() == end()
。!=
而不是<
(少于),因此,将end()
指针指向一个末端是方便的。