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

How should I loop over the elements of a C++ container in reverse order?

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

std::vector<int> vec { 12, 34, 56, 78 };

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

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

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

for(auto x : vec) {
    std::cout << x << '\n';
}

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

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

但是 - 我不喜欢循环计数器的含义与我们正在查看的相反。但是,如果我在 vec.size()-1 处开始 i,我将面临在最后一个元素之后发生下溢的风险。所以我可能需要这样做?

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

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

备注:

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

选项 1:使用迭代器

您可以使用一对迭代器(std::rbegin and std::rend 及其常量变体)对容器进行反向迭代,它们表示容器元素顺序的反转。这是它的样子:

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

我首先选择了这个选项,因为它(大部分)与 C++98 兼容。那时我们没有 std::rbegin()std::crbegin(),但我们确实有 std::vectorrbegin() 方法。 std::crbegin() 是在 C++11 中引入的

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

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

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

它们涉及使用 "infrastructural" 库(即 Boost),或者编写几行代码 return std::pair 中的迭代器对 - 这对于 C++ 来说已经足够了在 ranged-for loop.

中使用

选项 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';
}

性能说明

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