我有一个对象向量,并使用 range-for 循环对其进行迭代。我用它来打印对象的函数,如下所示:
vector<thisObject> storedValues;
//put stuff in storedValues
for(auto i:storedValues)
{
cout<<i.function();
}
但我也想打印索引。我想要的输出是:
1: value
2: value
//etc
我本来打算只使用每次都会增加的计数器,但这看起来效率很低。有更好的办法吗?
你不能。 index 是向量的特定概念,而不是集合的通用属性。另一方面,基于范围的循环是一种通用机制,用于迭代any集合的每个元素。
如果您确实想使用特定容器实现的详细信息,只需使用普通循环即可:
for (std::size_t i = 0, e = v.size(); i != e; ++i) { /* ... */ }
重复这一点:基于范围的循环用于操作任何集合的每个元素,其中集合本身并不重要,并且循环体内从未提及容器。它只是您工具箱中的另一个工具,您不必被迫将它用于所有事情。相比之下,如果您想要改变集合(例如删除或随机播放元素),或者使用有关集合结构的特定信息,请使用普通循环。
您可以使用
range-v3的
enumerate
视图:
std::vector<thisObject> storedValues;
for (auto const& [idx, value] : storedValues | ranges::views::enumerate) {
std::cout << idx << ": " << value << '\n';
}
C++20 将在 range-for 循环中引入 额外的初始化:
std::vector<thisObject> storedValues;
for (size_t idx = 0; auto value : storedValues) {
std::cout << idx << ": " << value << '\n';
++idx;
}
我创建了一个预处理器宏(由@Artyer大大简化),它以相对干净的方式为您处理这个问题:
#define for_indexed(...) for_indexed_v(i, __VA_ARGS__)
#define for_indexed_v(v, ...) if (std::size_t v = -1) for (__VA_ARGS__) if ((++v, true))
使用示例:
std::vector<int> v{1, 2, 3};
for_indexed (auto const& item : v) {
if (i > 0) std::cout << ", ";
std::cout << i << ": " << item;
}
要使用不同的循环变量:
for_indexed_v (my_counter, auto const& item : v) ...
在任何非调试版本中都应该优化额外的控制流逻辑。您只剩下相对易于阅读的循环语法。
2020 注意: 使用基于 lambda 的解决方案而不是宏技巧可能会更明智。当然,语法不会那么“干净”,但它的优点是可以被识别为实际的 C++ 语法。选择权在你。
更新2017/05/28:使
break;
语句正确工作for
放入宏名称中,使单词indexed
成为有效的变量名称。我怀疑for_indexed
会引起任何冲突。使用
range-v3
。 Range-v3
是由 ISO C++ 委员会成员 Eric Niebler 设计和实现的下一代范围库,并且预计将来会合并到 C++ 标准中。
通过使用
range-v3
OP的问题可以轻松解决:
using ranges::v3::view::zip;
using ranges::v3::view::ints;
for(auto &&[i, idx]: zip(storedValues, ints(0u))){
std::cout << idx << ": " << i.function() << '\n';
}
您需要一个支持 C++17 或更高版本的编译器来编译这段代码,不仅是为了结构化绑定语法,还为了
begin
和end
函数的返回类型ranges::v3::view::zip
的返回值不同。
您可以在此处查看在线示例。
range-v3
的文档位于此处,源代码本身托管于此处。如果您使用的是 MSVC 编译器,您还可以查看此处。
老实说,这非常简单,只需弄清楚你可以减去地址:)
&i 将引用内存中的地址,并且它会从索引到索引递增 4,因为它保存了定义的向量类型的整数。现在&values[0]引用了第一点,当你减去2个地址时,两者之间的差将分别是0,4,8,12,但实际上它减去了整数类型的大小,通常是4字节。 所以对应起来 0 = 0th int,4 = 1st int, 8 = 2nd int, 12 = 3rd int
这是一个向量
vector<int> values = {10,30,9,8};
for(auto &i: values) {
cout << "index: " << &i - &values[0];
cout << "\tvalue: " << i << endl;
}
这是一个常规数组,几乎是一样的东西
int values[]= {10,30,9,8};
for(auto &i: values) {
cout << "index: " << &i - &values[0];
cout << "\tvalue: " << i << endl;
}
注意这是针对C++11的,如果你使用g++,记得使用-std=c++11参数进行编译
这是 c++17 预处理器宏版本的更新版本。与手动使用索引编写循环相比,Clang 生成相同的调试和优化代码。 MSVC 生成相同的优化代码,但在调试中添加了一些额外的指令。
#define for_index(...) for_index_v(i, __VA_ARGS__)
#define for_index_v(i, ...) if (size_t i##_next = 0; true) for (__VA_ARGS__) if (size_t i = i##_next++; true)
我试图深入研究 MSVC 在调试版本中添加的额外代码,我不太确定它的目的是什么。它在循环开始处添加以下内容:
xor eax, eax
cmp eax, 1
je $LN5@for_i
这将完全跳过循环。使用的示例:https://godbolt.org/z/VTWhgT
升压适配器
indexed
:
#include <boost/range/adaptor/indexed.hpp>
std::vector<int> input {10,20,30,40,50,60,70,80,90};
for (const auto& element : input | boost::adaptors::indexed(0))
{
std::cout << "Element = " << element.value()
<< " Index = " << element.index()
<< std::endl;
}
输出:
Element = 10 Index = 0
Element = 20 Index = 1
Element = 30 Index = 2
Element = 40 Index = 3
...
采用 lambda 的 C++11 模板函数:
template<typename T, typename F>
void with_index(const T& range, F&& f) {
std::size_t i = 0;
for (auto it = std::begin(range); it != std::end(range); ++it)
{
f(*it, i++);
}
}
为一组生成随机数然后迭代它的示例:
#include <cstdio>
#include <random>
#include <set>
struct A
{
int x;
friend constexpr bool operator<(const A& lhs, const A& rhs) {
return lhs.x < rhs.x;
}
friend constexpr bool operator==(const A& lhs, const A& rhs) {
return lhs.x == rhs.x;
}
};
template<typename T, typename F>
void with_index(const T& range, F&& f) {
std::size_t i = 0;
for (auto it = std::begin(range); it != std::end(range); ++it)
{
f(*it, i++);
}
}
int main()
{
std::random_device rd;
std::mt19937 gen(rd());
std::uniform_int_distribution<> dis(0, 500);
std::set<A> as;
for (std::size_t i = 0; i < 100; ++i)
{
as.insert(A{dis(gen)});
}
with_index(as, [](const A& a, std::size_t i) {
printf("%d %lu\n", a.x, i);
});
}
在 C++ 23 中,您可以执行以下操作(枚举现在是标准库的一部分)。
#include <iostream>
#include <vector>
#include <ranges>
int main()
{
std::vector<char> letters = { 'a', 'b', 'c', 'd' };
for (const auto &[index, letter] : std::views::enumerate(letters))
{
std::cout << index << " : " << letter << std::endl;
}
}
输出:
0:a
1:b
2:c
3:d