如何使c++中的for each循环函数与自定义类一起工作

How to make the for each loop function in C++ work with a custom class

本文关键字:自定义 一起 工作 函数 循环 c++ 何使 中的 for each      更新时间:2023-10-16

我是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==)。我在visual studio中输入for,等待智能感知,它建议我这样做:

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

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

首先,C++for-each循环的语法与C#不同(它也称为range based for loop)。它的形式是:

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

实现了这些,就可以像上面那样使用基于范围的循环。

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, reference-instead- copy等)。

由于整个展开是在语法上进行的,因此您还可以稍微更改操作符的声明,例如,根据需要让*it返回引用或让!=取const IType& rhs

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

还要注意operator++()定义了++操作符的前缀版本——但是它也将被用作后缀操作符,除非您显式地定义了后缀++。如果只提供后缀++,则range -for将无法编译。可以声明为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

你可以使用下面的宏来设置range -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);
 }

(如果你的编译器甚至没有auto,请使用declspec或pass type)。

输出:

 1234500000

可以看到,continuebreak由于其复杂的结构可以使用它。请参阅http://www.chiark.greenend.org.uk/~sgtatham/mp/了解更多c预处理器编程,以创建自定义控制结构。

智能感知建议的语法不是c++;或者是MSVC的扩展。

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

之后你可以这样写:

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

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

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

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