如何让C++中的foreach循环与自定义类一起使用

53

我是C/C++编程的新手,但我已经在C#上编程1.5年了。我喜欢C#和List类,所以我想尝试在C++中创建一个List类来练习。

List<int> ls;
int whatever = 123;
ls.Add(1);
ls.Add(235445);
ls.Add(whatever);

实现类似于任何Array List类。我有一个T*向量成员,其中存储项目,当该存储即将被完全填充时,我会重新调整大小。
请注意,这不能用于生产,这只是一次练习。我非常了解vector和其他相关内容。
现在我想遍历我的列表项。我不喜欢使用for(int i=0;i
for each (object var in collection_to_loop)
{

}        

这显然无法使用我的List实现。我想我可以通过一些宏魔法来解决,但这感觉像是一个巨大的hack。实际上,最让我困扰的是像那样传递类型:

#define foreach(type, var, list)\
int _i_ = 0;\
##type var;\
for (_i_ = 0, var=list[_i_]; _i_<list.Length();_i_++,var=list[_i_]) 

foreach(int,i,ls){
    doWork(i);
}

我的问题是:有没有办法使这个自定义的List类与foreach-like循环一起工作?

6
你可以为你的类编写一个 begin() 和一个 end() 成员函数以及一个迭代器类型,这样就可以使它与 foreach 兼容。 - yngccc
5个回答

69
首先,C++for-each 循环的语法与 C# 不同(也称为 范围 for 循环)。它的形式为:
for(<type> <name> : <collection>) { ... }

举个例子,对于一个std::vector<int> vec,它会是这样的:

for(int i : vec) { ... }

在底层,这实际上使用了begin()end()成员函数,它们返回迭代器。因此,为了使您的自定义类能够利用for-each循环,您需要提供一个begin()和一个end()函数。通常会重载这些函数,返回一个iteratorconst_iterator。实现迭代器可能会很棘手,但对于类似于向量的类来说并不太难。
template <typename T>
struct List
{
    T* store;
    std::size_t size;
    typedef T* iterator;
    typedef const T* const_iterator;

    ....

    iterator begin() { return &store[0]; }
    const_iterator begin() const { return &store[0]; }
    iterator end() { return &store[size]; }
    const_iterator end() const { return &store[size]; }

    ...
 };

实现这些后,您可以像上面那样使用范围基础循环。


请注意,基于范围的for循环仅适用于Visual Studio的最新版本(2012年版)。 - Yuushi
那么它无法在任何其他编译器/集成开发环境中编译吗? - Ricardo Pieper
1
@RicardoPieper 它可以在过去几年的任何版本的Clang或gcc中编译(我认为是从4.7开始的gcc,不确定Clang版本)。 - Yuushi
3
我认为答案不完整。你暗示了List::iterator具有某些功能(在这种情况下,因为iterator是一个指针,所以你免费获得了这些功能)。但这不是一般情况。对于迭代器类型,有哪些假设?它是否有operator++和operator==等功能,还有其他的吗? - Uri London
已经过去4年了,但我仍然想知道相同的事情。我认为@masterxilo的答案很清楚地解释了这个问题。然而,为了简便起见,我将保留这个作为被接受的答案。 - Ricardo Pieper
显示剩余2条评论

49

iterable成为Iterable类型。 然后,为了使

for (Type x : iterable)

在编译时,必须存在名为TypeIType的类型,并且必须存在相应的函数。

IType Iterable::begin()
IType Iterable::end()

IType必须提供以下函数:

Type operator*()
void operator++()
bool operator!=(IType)

整个结构实际上是复杂的语法糖,用于类似以下代码的表达:
for (IType it = iterable.begin(); it != iterable.end(); ++it) {
    Type x = *it;
    ...
}

在此处,任何兼容类型(如const TypeType&)都可以用来代替Type,这将产生预期的影响(如constness、引用而非复制等)。

由于整个扩展是语法上的,因此您还可以稍微更改运算符的声明,例如使*it返回一个引用或者根据需要使用const IType& rhs作为!=的参数。

请注意,如果*it不返回引用,则无法使用for (Type& x : iterable)形式(但如果它返回引用,则也可以使用复制版本)。

还要注意,operator++()定义了前缀版本的++运算符--但是除非您明确定义后缀++,否则它也将用作后缀运算符。如果只提供后缀++,则范围循环将无法编译,其中btw。可以将其声明为operator++(int)(虚拟int参数)。


最小工作示例:

#include <stdio.h>
typedef int Type;

struct IType {
    Type* p;
    IType(Type* p) : p(p) {}
    bool operator!=(IType rhs) {return p != rhs.p;}
    Type& operator*() {return *p;}
    void operator++() {++p;}
};

const int SIZE = 10;
struct Iterable {
    Type data[SIZE];

    IType begin() {return IType(data); }
    IType end() {return IType(data + SIZE);}
};

Iterable iterable;

int main() {
    int i = 0;
    for (Type& x : iterable) {
        x = i++;
    }
    for (Type x : iterable) {
        printf("%d", x);
    }
}

输出

0123456789

您可以使用以下宏来模拟范围for循环(例如对于旧的C++编译器):
 #define ln(l, x) x##l // creates unique labels
 #define l(x,y)  ln(x,y)
 #define for_each(T,x,iterable) for (bool _run = true;_run;_run = false) for (auto it = iterable.begin(); it != iterable.end(); ++it)\
     if (1) {\
         _run = true; goto l(__LINE__,body); l(__LINE__,cont): _run = true; continue; l(__LINE__,finish): break;\
         } else\
            while (1)   \
                if (1) {\
                    if (!_run) goto l(__LINE__,cont);/* we reach here if the block terminated normally/via continue */   \
                    goto l(__LINE__,finish);/* we reach here if the block terminated by break */\
                }   \
                else\
                l(__LINE__,body): for (T x = *it;_run;_run=false) /* block following the expanded macro */                         

 int main() {
     int i = 0;
     for_each(Type&, x, iterable) {
         i++;
         if (i > 5) break;
         x = i;
     }
     for_each(Type, x, iterable) {
         printf("%d", x);
     }
     while (1);
 }

如果您的编译器甚至没有auto,请使用declspec或传递IType。

输出:

 1234500000

正如您所看到的,由于其复杂的结构,continuebreak可以与此一起使用。请访问http://www.chiark.greenend.org.uk/~sgtatham/mp/了解更多关于C预处理器的黑客技巧,以创建自定义控制结构。


9
Intellisense建议的语法不是C++,可能是一些MSVC扩展。
C++11有基于范围的for循环用于迭代容器中的元素。您需要为您的类实现begin()end()成员函数,它们将分别返回指向第一个元素和最后一个元素之后的迭代器。当然,这意味着您还需要为您的类实现适当的迭代器。如果您真的想走这条路,您可以看一下Boost.IteratorFacade;它可以减少自己实现迭代器时的许多痛苦。
之后,您就可以编写如下代码:
for( auto const& l : ls ) {
  // do something with l
}

另外,由于你是C++的新手,我想确保你知道标准库有几个容器类。


你的回答也非常好,但很遗憾我不能接受两个答案。还有感谢提供的链接 :D - Ricardo Pieper

2

C++语言的语法中没有for_each循环功能。您需要使用c++11或使用模板函数std::for_each

#include <vector>
#include <algorithm>
#include <iostream>

struct Sum {
    Sum() { sum = 0; }
    void operator()(int n) { sum += n; }

    int sum;
};

int main()
{
    std::vector<int> nums{3, 4, 2, 9, 15, 267};

    std::cout << "before: ";
    for (auto n : nums) {
        std::cout << n << " ";
    }
    std::cout << '\n';

    std::for_each(nums.begin(), nums.end(), [](int &n){ n++; });
    Sum s = std::for_each(nums.begin(), nums.end(), Sum());

    std::cout << "after:  ";
    for (auto n : nums) {
        std::cout << n << " ";
    }
    std::cout << '\n';
    std::cout << "sum: " << s.sum << '\n';
}

0

正如@yngum所建议的那样,您可以通过在集合上定义begin()end()方法来返回自定义迭代器,从而使VC++ for each扩展与任意集合类型一起使用。然后,您的迭代器必须实现必要的接口(解引用运算符、递增运算符等)。我曾经为遗留代码包装了所有MFC集合类,这需要一些工作,但是可以完成。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接