繁体   English   中英

使用索引与迭代器将向量迭代到倒数第二个元素

[英]Iterating a vector to second to last element with index vs iterator

从 C++11 std::vector的开头迭代到倒数第二个元素时,首选样式是什么?

std::vector<const char*> argv;
std::string str;

是否应该使用这种更具 C++ 风格的方法

for (const auto& s: decltype(argv)(argv.begin(), argv.end()-1)) {
    str += std::string(s) + ' ';
}

还是应该首选更传统的方式?

for (size_t i = 0; i < argv.size() - 1; ++i) {
    str += std::string(argv[i]);
}

请不要这样写:

for (const auto& s: decltype(argv)(argv.begin(), argv.end()-1)) {

首先,当你回顾它时,没有人(包括你)会理解这一点。 其次,由于decltype(argv)是一个vector ,这是复制一大堆元素......所有这一切仅仅是因为你想避免迭代其中一个? 那太浪费了。

它还有另一个问题,这是您的第二个选项所共有的。

这个:

for (size_t i = 0; i < argv.size() - 1; ++i) {

问题要微妙得多,因为size()unsigned 因此,如果argv恰好为空,则argv.size() - 1将是一个非常大的数字,并且您实际上将访问数组的所有这些无效元素,从而导致未定义的行为。

对于迭代器,如果argv.begin() == argv.end()那么你就不能得到以前的迭代器end() ,因为没有来自以前的迭代end() 所有end() - 1prev(end())--end()已经是未定义的行为。 那时,我们甚至无法推断循环会做什么,因为我们甚至没有有效范围。


我的建议是:

template <typename It>
struct iterator_pair {
    It b, e;

    It begin() const { return b; }
    It end() const { return e; }
};

// this doesn't work for types like raw arrays, I leave that as
// an exercise to the reader
template <typename Range>
auto drop_last(Range& r) 
    -> iterator_pair<decltype(r.begin())>
{
    return {r.begin(), r.begin() == r.end() ? r.end() : std::prev(r.end())};
}

这让您可以:

for (const auto& s : drop_last(argv)) { ... }

这是有效的(避免额外的副本),避免了未定义的行为( drop_last()总是给出一个有效的范围),并且从名称中可以很清楚它的作用。

我发现第一个选项读起来有点笨拙,但由于这是个人喜好的问题,我提出了一种避免手写循环的替代方法(并假设argv.size() >= 1 ),其中可能会更好,因为它减少了拼写错误和索引错误的可能性。

#include <numeric>

std::string str;

if (!argv.empty())
    str = std::accumulate(argv.begin(), std::prev(argv.end()), str);

我的建议是在您的项目中包含指南支持库,如果不是更通用的范围库,然后使用gsl::span (等待 C++20 获取它,因为std::span可能有点长) 或类似访问您想要的子范围。

此外,它可能很小,但它足够复杂以保证它自己的功能:

template <class T>
constexpr gsl::span<T> drop_last(gsl::span<T> s, gsl::span<T>::index_type n = 1) noexcept
{ return s.subspan(0, std::min(s.size(), n) - n); }

for (auto s : drop_last(argv)) {
    // do things
}

实际上,强烈建议查看范围和视图以提高效率(更少的间接性,无复制)和解耦(被调用者不再需要知道使用的确切容器)。

另一种选择是将std::for_each与 lambda 一起使用。

std::for_each(argv.begin(), std::prev(argv.end()), [&](const auto& s){ str += s; });

看起来您正在尝试输出容器中的元素,它们之间有一个空格。 另一种写法是:

const char* space = "";     // no space before the first item
for (const char* s : argv) {
    str += space;
    str += s;
    space = " ";
}

要解决您的具体示例,您可以使用 Google Abseil。

来自str_join.h头文件的代码示例:

   std::vector<std::string> v = {"foo", "bar", "baz"};
   std::string s = absl::StrJoin(v, "-");
   EXPECT_EQ("foo-bar-baz", s);

EXPECT_EQ 是谷歌测试宏,这意味着s等于"foo-bar-baz"

我将扮演魔鬼的拥护者并说只需将argv.size()int并使用常规 for 循环。 这可能在 99% 的情况下都有效,易于阅读,并且不需要深奥的 C++ 知识即可理解。

for(int i=0; i<(int)argv.size() - 1; i++)

万一您的容器有超过 20 亿个元素,您可能会提前知道这一点,在这种情况下,只需使用size_t并使用特殊情况检查argv.size() == 0以防止下溢。

我建议使用std::prev(v.end())而不是v.end()-1 这更符合习惯,也适用于其他容器。

这是一个演示这个想法的main功能。

int main()
{
   std::set<int> s = {1, 2, 3, 4};
   for (const auto& item: decltype(s)(s.begin(), std::prev(s.end())))
   {
      std::cout << item << std::endl;
   }

   std::vector<int> v = {10, 20, 30};
   for (const auto& item: decltype(v)(v.begin(), std::prev(v.end())))
   {
      std::cout << item << std::endl;
   }
}

请注意,上面的代码构造了临时容器。 当容器很大时,构造一个临时容器将是一个性能问题。 对于这种情况,请使用简单的for循环。

for (auto iter = argv.begin(); iter != std::prev(argv.end()); ++iter )
{
   str += *iter + ' ';
}

或使用std::for_each

std::for_each(argv.begin(), std::prev(argv.end()),
              [](std::string const& item) { str += item + ' '; });

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM