我应该如何以相反的顺序循环遍历 C++ 容器的元素?

ein*_*ica 4 c++ iteration reverse containers idioms

假设我是一个新手 C++ 程序员。我有一个 C++ 容器;比如说,一个向量:

std::vector<int> vec { 12, 34, 56, 78 };
Run Code Online (Sandbox Code Playgroud)

我知道我可以用一个简单的循环遍历所有元素:

for(std::vector<int>::size_type i = 0; i < vec.size(); i++) {
    std::cout << vec[i] << '\n';
}
Run Code Online (Sandbox Code Playgroud)

也许我什至对现代 C++ 有了一些了解,所以我知道我可以使用 ranged-for 循环:

for(auto x : vec) {
    std::cout << x << '\n';
}
Run Code Online (Sandbox Code Playgroud)

但是现在,我想以相反的顺序迭代元素。基于范围的 for 循环不会这样工作。对于普通循环,我必须小心并避免下溢,所以也许是这样的?:

for(std::vector<int>::size_type i = 0; i < vec.size(); i++) {
    std::cout << vec[vec.size() - i] << '\n';
}
Run Code Online (Sandbox Code Playgroud)

但是 - 我不喜欢循环计数器的意思与我们所看到的相反。但是,如果我开始ivec.size()-1,我将在最后一个元素之后的风险溢。所以我需要这样做,也许?

for(std::vector<int>::size_type i = vec.size(); i > 0 ; i--) {
    std::cout << vec[i - 1] << '\n';
}
Run Code Online (Sandbox Code Playgroud)

嗯,感觉也不对。我应该使用哪些习语来进行反向迭代,哪些是安全的(即不易出错)、美观且简洁的?

笔记:

  • 我试图将标题表述得尽可能简单(而不是说“反向迭代容器”)。
  • 这个问题的启发,其中一个朴素的反向迭代循环有一个错误。
  • 希望具有的元素容器的副本,并扭转和叠代的常用方法。
  • 我没有在上面的循环中使用auto&const auto&,因为新手编码人员通常不知道它们。

ein*_*ica 5

好吧,首先,关于你的两个片段:部分问题是它们对于实际的新手来说有点容易出错 - 整数下溢,在比较中逐一,忘记了i意味着什么并将其用作普通索引等。所以我肯定会推荐其他东西。此外,这些片段可能会被vec.size()多次调用,如果编译器优化得不够好,这将意味着一堆多余的工作。

选项 1:使用迭代器

可以反向迭代并且使用一对迭代的容器(std::rbeginstd::rend,以及它们的变体恒定),这表示元件的容器的顺序的逆转。这是它的样子:

for(auto it = std::crbegin(vec); it != std::crend(vec); it++) {
    std::cout << *it << '\n';
}
Run Code Online (Sandbox Code Playgroud)

我将这个选项作为第一个选项,因为它(大部分)与 C++98 兼容。我们没有std::rbegin()std::crbegin()那可是我们确实有一个rbegin()对方法std::vectorstd::crbegin()在 C++11 中引入

选项 2:使用 C++11(及更高版本)ranged-for 循环

您可以按摩您的容器 - 无需复制它(尽管可能需要支付一些时间),以便您可以在 ranger for 循环中使用结果。这个 SO 问题的答案描述了几种方法,启用以下代码:

auto reverse_view = /* magic involving vec; and not making a copy */
for(auto x : reverse_view) {
    std::cout << *it << '\n';
}
Run Code Online (Sandbox Code Playgroud)

它们涉及使用“基础结构”库(即 Boost),或者编写几行代码,在 an 中返回一个迭代器对std::pair- 这足以让 C++ 在ranged-for 循环中使用

选项 3:使用 ranged-for 和 C++20 的范围支持

最后,在 C++20 中,这一切都变得更容易了——有了范围支持和std::ranges::reverse_view

auto reverse_view = std::ranges::reverse_view{vec};
for (const auto& x : reverse_view) {
    std::cout << x << '\n';
}
Run Code Online (Sandbox Code Playgroud)

性能说明

在某些情况下,反向迭代可能会很昂贵 - 因为向后移动或找到容器的末端并不总是微不足道或免费的。想想一个单向列表(其中每个元素都带有一个指向下一个元素的指针)——无论何时你想倒退,你都需要遍历整个列表直到你的当前元素才能知道前一个元素所在的位置。并非所有的容器都像向量...

  • _想想一个 std::list - 你必须遍历整个列表才能到达那里_:https://en.cppreference.com/w/cpp/container/list/end:复杂性:常量。我认为,一般来说,所有的容器都会支持对`end`的快速访问。 (2认同)