44

我有一个对象向量,并正在使用 range-for 循环对其进行迭代。我正在使用它从对象中打印一个函数,如下所示:

vector<thisObject> storedValues;
//put stuff in storedValues
for(auto i:storedValues)
{
   cout<<i.function();
}

但我也想打印索引。我想要的输出是:

1: value
2: value
//etc

我打算只使用一个每次都增加的计数器,但这似乎非常低效。有没有更好的办法?

4

6 回答 6

44

你不能。索引是向量的特定概念,而不是集合的通用属性。另一方面,基于范围的循环是一种通用机制,用于迭代任何集合的每个元素。

如果您确实想使用特定容器实现的细节,只需使用普通循环:

for (std::size_t i = 0, e = v.size(); i != e; ++i) { /* ... */ }

重复这一点:基于范围的循环用于操作任何集合的每个元素,其中集合本身并不重要,并且在循环主体中从未提及容器。它只是您工具箱中的另一个工具,您不会被迫将它用于所有事情。相反,如果您想改变集合(例如删除或随机播放元素),或者使用有关集合结构的特定信息,请使用普通循环。

于 2012-09-12T23:46:15.253 回答
21

我创建了一个预处理器宏(@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 年 5 月 28 日更新:使break;语句正常工作
2019 年 1 月 28 日更新for输入宏名称,使该词indexed成为有效的变量名称。我怀疑for_indexed会引起任何冲突。
2020/12/23 更新:大大简化(感谢@Artyer)

于 2017-04-05T00:50:27.990 回答
15

您可以使用range-v3enumerate的视图:

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;
}
于 2020-02-13T14:19:48.650 回答
13

使用range-v3. Range-v3是由 ISO C++ 委员会成员 Eric Niebler 设计和实现的下一代范围库,旨在并有望在未来合并到 C++ 标准中。

通过使用range-v3OP 的问题可以很容易地解决:

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 或更高版本的编译器来编译这段代码,不仅因为结构化绑定语法,而且因为返回类型beginend返回值的函数ranges::v3::view::zip不同。

您可以在此处查看在线示例。的文档在range-v3这里源代码本身托管在这里。如果您使用的是 MSVC 编译器,也可以查看此处。

于 2017-10-22T17:38:13.050 回答
4

老实说,这很简单,只需弄清楚你可以减去地址:)

&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参数进行编译

于 2018-07-13T07:19:55.740 回答
3

这是 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

于 2019-05-14T05:48:46.700 回答