在 range-for 循环中访问索引

问题描述 投票:0回答:9

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

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

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

1: value
2: value
//etc

我本来打算只使用每次都会增加的计数器,但这看起来效率很低。有更好的办法吗?

c++ c++11
9个回答
56
投票

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

如果您确实想使用特定容器实现的详细信息,只需使用普通循环即可:

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

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


39
投票

您可以使用

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;
}

23
投票

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


16
投票

使用

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 编译器,您还可以查看此处


5
投票

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

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


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


3
投票

升压适配器

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
...

2
投票

采用 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);
    });
}

0
投票

在 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

© www.soinside.com 2019 - 2024. All rights reserved.