反向迭代器适配器是一个抽象,它反转了迭代器类的顺序。它须要一个双向迭代器。
如何做到这一点…大多数双向容器在 STL 中都包含了一个反向迭代器适配器。其他容器,如原始 C 数组,没有。让我们来看看一些例子:
让我们从本章中一贯利用的 printc() 函数开始:

void printc(const auto & c, const string_view s = "") { if(s.size()) cout << format("{}: ", s); for(auto e : c) cout << format("{} ", e); cout << '\n';}
这个利用基于范围的 for 循环来打印容器的元素。
基于范围的 for 循环纵然与没有迭代器类的原始 C 数组一起事情也可以。以是,我们的 printc() 函数已经可以与 C 数组一起事情:
int main() { int array[]{1, 2, 3, 4, 5}; printc(array, "c-array");}
我们得到这个输出:
c-array: 1 2 3 4 5
我们可以利用 begin() 和 end() 迭代器适配器为 C 数组创建正常的前向迭代器:
auto it = std::begin(array);auto end_it = std::end(array);while (it != end_it) { cout << format("{} ", it++);}
for 循环的输出:
1 2 3 4 5
或者我们可以利用 rbegin() 和 rend() 反向迭代器适配器为 C 数组创建反向迭代器:
auto it = std::rbegin(array);auto end_it = std::rend(array);while (it != end_it) { cout << format("{} ", it++);}
现在我们的输出是相反的:
5 4 3 2 1
我们乃至可以创建一个修正版的 printc(),以相反的办法打印:
void printr(const auto & c, const string_view s = "") { if(s.size()) cout << format("{}: ", s); auto rbegin = std::rbegin(c); auto rend = std::rend(c); for(auto it = rbegin; it != rend; ++it) { cout << format("{} ", it); } cout << '\n';}
当我们用 C 数组调用它:
printr(array, "rev c-array");
我们得到这个输出:
rev c-array: 5 4 3 2 1
当然,这与任何双向 STL 容器一样有效:
vector<int> v{1, 2, 3, 4, 5};printc(v, "vector");printr(v, "rev vector");
输出:
vector: 1 2 3 4 5rev vector: 5 4 3 2 1
它是如何事情的…
一个正常的迭代器类有一个指向第一个元素的 begin() 迭代器,和一个指向末了一个元素之后的 end() 迭代器:
前向迭代器
你通过利用 ++ 运算符递增 begin() 迭代器,直到它达到 end() 迭代器的值来迭代容器。
一个反向迭代器适配器拦截迭代器接口并将其反转,使得 begin() 迭代器指向末了一个元素,end() 迭代器指向第一个元素之前。++ 和 -- 运算符也被反转:
反向迭代器适配器
在反转的迭代器中,++ 运算符递减,-- 运算符递增。
值得把稳的是,大多数双向 STL 容器已经包含了一个通过成员函数 rbegin() 和 rend() 访问的反向迭代器适配器:
vector<int> v;it = v.rbegin();it_end = v.rend();
这些迭代器将以相反的方向操作,适宜许多目的。
利用哨兵迭代未知长度的工具
一些工具没有特定的长度。要理解它们的长度,你须要迭代它们所有的元素。例如,在本章的其他地方,我们已经看到了一个没有特定长度的天生器。一个更常见的例子是 C 字符串。
C 字符串是一个以空字符 '\0' 终止的原始 C 字符数组。
带有其空终止符的 C 字符串
我们一贯在利用 C 字符串,纵然我们没故意识到。任何 C/C++ 中的字面字符串都是 C 字符串:
std::string s = "string";
这里,STL 字符串 s 用字面字符串初始化。字面字符串是 C 字符串。如果我们以十六进制查看各个字符,我们将看到空终止符:
for (char c : "string") { std::cout << format("{:02x} ", c);}
单词 "string" 有六个字母。我们的循环输出显示数组中有七个元素:
73 74 72 69 6e 67 00
第七个元素是空终止符。
循环看到的是字符的原始 C 数组,有七个值。它是一个字符串是一个抽象,对循环不可见。如果我们想让循环将其视为字符串,我们将须要一个迭代器和一个哨兵。
哨兵是一个工具,它发出迭代器的不愿定长度的结束旗子暗记。当迭代器到达数据的末端时,哨兵将与迭代器相等。
为了看看这个是如何事情的,让我们为 C 字符串构建一个迭代器!
如何做到这一点…
要利用 C 字符串的哨兵,我们须要构建一个自定义迭代器。它不须要繁芜,只须要基本功能,以便与基于范围的 for 循环一起利用。
我们将从几个方便的定义开始:
using sentinel_t = const char;constexpr sentinel_t nullchar = '\0';
对付 sentinel_t 的 using 别名是 const char。我们将在我们的类中利用这个作为哨兵。
我们还定义了空字符终止符的常量 nullchar。
现在我们可以定义我们的迭代器类型:
class cstr_it { const char s{};public: explicit cstr_it(const char str) : s{str} {} char operator() const { return s; } cstr_it& operator++() { ++s; return this; } bool operator!=(sentinel_t) const { return s != nullptr && s != nullchar; } cstr_it begin() const { return this; } sentinel_t end() const { return nullchar; }};
这很简短大略。这是基于范围的 for 循环所需的最小必要性。把稳 end() 函数返回 nullchar,operator!=() 重载与 nullchar 进行比较。这便是我们为哨兵所需的全部。
现在我们可以定义一个利用哨兵打印我们的 C 字符串的函数:
void print_cstr(const char s) { cout << format("{}: ", s); for (char c : cstr_it(s)) { std::cout << format("{:02x} ", c); } std::cout << '\n';}
在这个函数中,我们首先打印字符串。然后我们利用 format() 函数将每个字符作为十六进制值打印。
现在我们可以从我们的 main() 函数调用 print_cstr():
int main() { const char carray[]{"array"}; print_cstr(carray); const char cstr{"c-string"}; print_cstr(cstr);}
输出看起来像这样:
array: 61 72 72 61 79c-string: 63 2d 73 74 72 69 6e 67
把稳没有多余的字符,没有空终止符。这是由于我们的哨兵见告 for 循环在看到 nullchar 时停滞。
它是如何事情的…
迭代器类的哨兵部分非常大略。我们可以通过在 end() 函数中返回它来轻松利用空终止符作为哨兵值:
sentinel_t end() const { return nullchar; }
然后,不相等比较运算符可以测试它:
bool operator!=(sentinel_t) const { return s != nullptr && s != nullchar;}
把稳参数只是一个类型(sentinel_t)。参数类型对付函数署名是必要的,但我们不须要值。所有必要的便是将当前迭代器与哨兵进行比较。
每当你有一个没有预定终点进行比较的类型或类时,这种技能都该当很有用。