哨兵
为了处理范围,我们必须引入一个新术语,哨兵,它代表范围的结束。
在编程中,哨兵是标记结束或终止的特殊值。典型的例子是
-
空终止符 '0' 作为字符序列的结尾(例如,用于字符串文字)
-
nullptr 标记链表的结尾
-
-1 标记非负整数列表的结尾
在范围库中,哨兵定义范围的结尾。在 STL 的传统方法中,哨兵将是结束迭代器,它通常与迭代集合的迭代器具有相同的类型。
要求结束迭代器必须与定义范围开始的迭代器具有相同的类型,并用于遍历元素,这导致了一些缺陷。创建一个结束迭代器可能代价很高,甚至根本不可能
-
如果我们想使用C字符串或字符串文字作为范围,我们首先必须通过遍历字符来计算结束迭代器,直到找到'\0'。因此,在使用字符串作为范围之前,我们已经完成了第一次迭代。然后处理所有的角色将需要第二次迭代。
-
一般来说,如果我们用某个值来定义一个范围的结束,这个原则就适用了。如果我们需要一个end迭代器来处理范围,我们首先必须遍历while范围以找到它的结束。
-
有时迭代两次(一次查找结束,一次处理范围内的元素)是不可能的。这适用于使用纯输入迭代器的范围,例如将读取的输入流用作范围。为了计算输入的结束(可能是EOOF),我们已经必须读取输入。再次读取输入是不可能的,或者将产生不同的值。
将结束迭代器泛化为哨兵解决了这个难题。c++ 20范围支持不同类型的哨兵(结束迭代器)。它们可以发出信号直到'\0'、直到EOF或任何其他值。它们甚至可以表示没有结束来定义无限的范围,嘿,迭代迭代器,检查自己是否有结束。
注意,在c++ 20之前,我们也可以有这种类型的哨兵,但要求它们具有与迭代器相同的类型。一个例子是输入流迭代器:默认构造的istream迭代器<>用于创建流结束迭代器,以便您可以使用算法处理来自流的输入,直到文件结束或发生错误
std: :for_each(std::istream_iterator<int>{std::cin},
std::istream_iterator<int>{},
[](int val) {
std::cout<<val <<'\n';
});
因此,通过放宽哨兵(结束迭代器)现在必须与迭代迭代器具有相同类型的要求,我们获得了几个好处:
-
我们可以跳过在开始处理之前找到结束的需要(我们处理值并在迭代时一起找到结束)。
-
对于结束迭代器,我们可以使用禁止导致未定义行为的操作的类型(例如调用操作符*,因为结束迭代器没有值)。当我们试图在编译时解引用ain end迭代器时,可以使用它来发出错误信号。
-
定义结束迭代器变得更容易。
struct NullTerm {
bool operator== (auto pos) const {
return *pos == '\0'; }
};
int main() {
const char* rawString = "hello world";
for (auto pos = rawString; pos !=Nu1lTerm{};++pos)
{ std::cout <<''<< *pos;
}
std::cout <<'\n';
std::ranges::for_each(rawString,
NullTerm{},
[](char c){
std::cout <<''<<c;
});
std::cout <<'\na';
}
该程序有以下输出
hello world
hello world
在程序中,我们首先定义一个结束迭代器,它将结束定义为具有等于“0”的值:
struct NullTerm{
bool operator== (auto pos) const {
return *pos == '\0'; }
};
注意,我们在这里结合了c++ 20的一些其他新特性
- 定义运算符==时,我们使用 auto 作为参数类型。这样,我们就可以使运算符 == 与任意类型的对象 pos 进行比较(前提是将其值与正文中的字符进行比较是有效的)。
- ·我们只将运算符 == 定义为成员函数,尽管算法通常将迭代器与哨兵与运算符 != 进行比较。在这里,我们受益于这样一个事实,即 C++20 现在可以将运算符 != 映射到运算符 ==,操作数的任意顺序。
开启掘金成长之旅!这是我参与「掘金日新计划 · 2 月更文挑战」的第 N 天,点击查看活动详情”