48

我是 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存储项目的成员,当这个存储空间即将被完全填满时,我会调整它的大小。

请注意,这不是在生产中使用的,这只是一个练习。我vector<T>和朋友都很清楚。

现在我想遍历我的列表中的项目。我不喜欢用for(int i=0;i<n; i==)。我for在 Visual Studio 中输入,等待 Intellisense,它建议我这样做:

for each (object var in collection_to_loop)
{

}        

这显然不适用于我的 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);
}

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

4

5 回答 5

60

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

for(<type> <name> : <collection>) { ... }

因此,例如,使用std::vector<int> vec,它会是这样的:

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

在幕后,这有效地使用了返回迭代器的begin()andend()成员函数。因此,为了让您的自定义类能够利用for-each循环,您需要提供一个begin()和一个end()函数。这些通常是重载的,返回 aniterator或 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]; }

    ...
 };

实现这些后,您可以使用上述基于范围的循环。

于 2013-05-12T04:10:33.407 回答
40

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

Type可以使用任何兼容类型(例如const Typeor )来代替Type&,这将具有预期的含义(常量性、引用代替复制等)。

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

请注意,如果不返回引用,则不能使用for (Type& x : iterable)表单(但如果返回引用,您也可以使用复制版本)。*it

另请注意,它operator++()定义了运算符的前缀版本++- 但是它也将用作后缀运算符,除非您明确定义后缀++。如果您只提供一个 postfix ++,则 ranged-for 将不会编译,btw.can 可以声明为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

您可以使用以下宏伪造 ranged-for-each(例如,对于较旧的 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);
 }

(如果您的编译器甚至没有自动功能,请使用 declspec 或传递 IType)。

输出:

 1234500000

正如您所看到的,continue并且break由于其复杂的结构而可以使用它。请参阅http://www.chiark.greenend.org.uk/~sgtatham/mp/了解更多 C-preprocessor hacking 以创建自定义控制结构。

于 2016-04-13T18:38:54.633 回答
8

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

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

之后,您将能够编写以下内容:

for( auto const& l : ls ) {
  // do something with l
}

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

于 2013-05-12T04:09:22.783 回答
2

for_eachC++在其语法中没有循环功能。您必须使用 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';
}
于 2013-05-12T04:13:10.577 回答
0

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

于 2013-05-12T04:13:39.467 回答