【问题标题】:How should I loop over the elements of a C++ container in reverse order? [duplicate]我应该如何以相反的顺序循环 C++ 容器的元素? [复制]
【发布时间】:2020-06-13 15:21:24
【问题描述】:

假设我是一个新手 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';
}

但是现在,我想以相反的顺序迭代元素。基于范围的 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';
}

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

注意事项:

  • 我试图使标题尽可能简单(而不是说“反向迭代容器”)。
  • this question 的启发,其中一个幼稚的反向迭代循环存在错误。
  • 我确实想用元素制作容器的副本,然后以通常的方式反转和迭代。
  • 我没有在上面的循环中使用auto&amp;const auto&amp;,因为新手编码人员通常不知道它们。

【问题讨论】:

  • @Nick:问题及其答案属于至少一种(如果不是 3 种)语言的早期时代。我觉得你的建议没有道理。但实际上: 4. 再看一遍,这实际上根本不是关于 C++ 的问题!这个问题完全是关于 C# 的。有一个关于 C++ 的答案,但它并没有真正回答这个问题。
  • @Nick:是的,这是真的。如果您将此问题标记为对那个问题的欺骗,我将不得不接受。
  • @Nick:哎呀。我讨厌结​​束自己的问题。完成。

标签: c++ iteration containers reverse idioms


【解决方案1】:

嗯,首先,关于你的两个 sn-ps:部分问题是它们对于实际的新手来说有点容易出错 - 整数下溢,在比较中一个接一个,忘记了 @987654326 @ 表示并将其用作普通索引等。所以我肯定会推荐其他东西。此外,这些 sn-ps 可能会多次调用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';
}

它们涉及使用“基础设施”库(即 Boost),或编写几行代码以在 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';
}

性能说明

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

【讨论】:

  • 想想一个 std::list - 你必须遍历整个列表才能到达那里en.cppreference.com/w/cpp/container/list/end:复杂性:常数。我认为,总的来说,所有容器都会支持快速访问end
  • @PaulSanders:见编辑。
  • std::forward_list::iteratorForwardIterator,而不是 BidirectionalIterator,你不能反转它
  • @PaulSanders:另外——我的失态是你从不使用std::*list*...
  • @einpoklum 不用担心。这算不上重大错误。
猜你喜欢
  • 1970-01-01
  • 2019-12-16
  • 1970-01-01
  • 2016-07-06
  • 1970-01-01
  • 2019-03-10
  • 2020-01-16
  • 2015-12-09
  • 2021-12-30
相关资源
最近更新 更多