如何使C++中的每个循环函数与自定义类一起使用

Ric*_*per 34 c++ foreach list c++11

我是C/C++编程的新手,但我已经用C#编程了1.5年了.我喜欢C#而且我喜欢List类,所以我想在C++中创建一个List类作为练习.

List<int> ls;
int whatever = 123;
ls.Add(1);
ls.Add(235445);
ls.Add(whatever);
Run Code Online (Sandbox Code Playgroud)

该实现类似于任何Array List类.我有一个T* vector存储项目的成员,当这个存储空间即将完全填满时,我会调整它的大小.

请注意,这不是用于生产,这只是一个练习.我很了解vector<T>和朋友们.

现在我想循环浏览列表中的项目.我不喜欢用for(int i=0;i<n; i==).我输入for了视觉工作室,等待Intellisense,它建议我:

for each (object var in collection_to_loop)
{

}        
Run Code Online (Sandbox Code Playgroud)

这显然不适用于我的List实现.我想我可以做一些宏观魔术,但这感觉就像一个巨大的黑客.实际上,最让我烦恼的是传递类似的类型:

#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);
}
Run Code Online (Sandbox Code Playgroud)

我的问题是:有没有办法让这个自定义List类使用foreach-like循环?

Yuu*_*shi 45

首先,for-each循环的语法C++不同于C#(它也称为a range based for loop.它具有以下形式:

for(<type> <name> : <collection>) { ... }
Run Code Online (Sandbox Code Playgroud)

例如,使用a std::vector<int> vec,它将类似于:

for(int i : vec) { ... }
Run Code Online (Sandbox Code Playgroud)

在封面下,这有效地使用了返回迭代器的begin()end()成员函数.因此,要允许自定义类使用for-each循环,您需要提供一个begin()和一个end()函数.这些通常是超载的,返回a iterator或a const_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]; }

    ...
 };
Run Code Online (Sandbox Code Playgroud)

通过这些实现,您可以使用如上所述的基于范围的循环.

  • 我不认为答案是完整的.你隐含地假设List :: iterator有一些功能(在这种情况下你得到"免费",因为迭代器是一个指针.这不是一般情况).迭代器类型的假设是什么?它是operator ++和operator ==.还要别的吗? (3认同)

mas*_*ilo 20

让我们iterable成为一个类型Iterable.然后,为了做

for (Type x : iterable)
Run Code Online (Sandbox Code Playgroud)

编译时,必须有被调用的类型Type,IType并且必须有函数

IType Iterable::begin()
IType Iterable::end()
Run Code Online (Sandbox Code Playgroud)

IType 必须提供功能

Type operator*()
void operator++()
bool operator!=(IType)
Run Code Online (Sandbox Code Playgroud)

整个结构是非常复杂的语法糖类

for (IType it = iterable.begin(); it != iterable.end(); ++it) {
    Type x = *it;
    ...
}
Run Code Online (Sandbox Code Playgroud)

而不是Type,可以使用任何兼容类型(如const TypeType&),这将具有预期的含义(constness,reference-instead-copy等).

由于整个扩展在语法上发生,您还可以稍微更改运算符的声明,例如,*它返回引用或者const IType& rhs根据需要使用!= a .

请注意,for (Type& x : iterable)如果*it不返回引用,则无法使用该表单(但如果它返回引用,则也可以使用副本版本).

另请注意,它operator++()定义了运算符的前缀版本++- 但是除非您明确定义后缀,否则它也将用作后缀运算符++.如果你只提供一个后缀++,那么ranged-for将无法编译,btw.can被声明为operator++(int)(dummy 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);
    }
}
Run Code Online (Sandbox Code Playgroud)

产量

0123456789
Run Code Online (Sandbox Code Playgroud)

您可以使用以下宏伪造每个范围(例如,对于较旧的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);
 }
Run Code Online (Sandbox Code Playgroud)

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

输出:

 1234500000
Run Code Online (Sandbox Code Playgroud)

正如您所看到的,由于其复杂的结构continue,break它将与此一起工作.有关更多C-preprocessor hacking以创建自定义控件结构,请参阅http://www.chiark.greenend.org.uk/~sgtatham/mp/.


Pra*_*ian 7

Intellisense建议的语法不是C++; 或者它是一些MSVC扩展.

C++ 11具有基于范围的for循环,用于迭代容器的元素.您需要为类实现begin()end()成员函数,它们将迭代器返回到第一个元素,并分别返回最后一个元素.当然,这意味着您还需要为您的类实现合适的迭代器.如果你真的想走这条路,你可能想看看Boost.IteratorFacade ; 它减少了自己实现迭代器的痛苦.

之后你就可以这样写:

for( auto const& l : ls ) {
  // do something with l
}
Run Code Online (Sandbox Code Playgroud)

此外,由于您是C++的新手,我想确保您知道标准库有多个容器类.