使用索引与迭代器将向量迭代到倒数第二个元素
Iterating a vector to second to last element with index vs iterator
从 C++11std::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()
是未签名的。因此,如果argv
恰好是空的,argv.size() - 1
将是一个非常大的数字,您实际上将访问数组中所有这些无效元素,从而导致未定义的行为。
对于迭代器,如果argv.begin() == argv.end()
,则无法从end()
获取以前的迭代器,因为没有来自end()
的先前迭代器。所有end() - 1
、prev(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 + ' '; });
- 如何找到数组中值倒数第二次出现的索引
- 如何打印第一个和最后一个元素的和,然后打印第二个和倒数第二个元素的总和,依此类推
- 使用索引与迭代器将向量迭代到倒数第二个元素
- 期望_DEATH的倒数是多少
- 如何获取列表中的倒数第二个元素
- 对于我扩展此程序来计算最高10x10矩阵的倒数的最简单方法是什么
- C++迭代到倒数第二个元素
- C++ 安全倒数到零"auto"整型
- 为什么我需要绑定姿势矩阵的倒数来计算动画
- 收藏中倒数第二的模板;reverse_iterator无法编译
- 如何从multi_index_container获取倒数第二个元素
- STL列表访问倒数第二个元素
- 如何在C++中找到向量中倒数第二个元素
- 在c++中不改变倒数第二位的情况下移动整数中的位
- 在迭代器中检查是否倒数第二
- 如何找到六位数的倒数第二位
- 从文件中重新读取int到数组中,倒数第二个元素总是错误的
- 获取 4 位整数的倒数第二个值
- 检查std::map的迭代器是否指向倒数第二个元素
- c++ map:第一次调用map[a],如何初始化map[a].倒数第二