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

Posted

技术标签:

【中文标题】如何使 C++ 中的 for each 循环函数与自定义类一起使用【英文标题】:How to make the for each loop function in C++ work with a custom class 【发布时间】:2013-05-06 10:12:04 【问题描述】:

我是 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&lt;T&gt; 和朋友们。

现在我想遍历列表中的项目。我不喜欢使用for(int i=0;i&lt;n; i==)。我在 Visual Studio 中输入了for,等待 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 循环一起工作?

【问题讨论】:

您可以为您的类编写一个begin() 和一个end() 成员函数和一个迭代器类型,以使其与foreach 兼容。 【参考方案1】:

iterableIterable 类型。 然后,为了使

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&amp;)代替Type,这将具有预期的含义(常量、引用替代复制等)。

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

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

还要注意operator++() 定义了++ 运算符的前缀 版本——但是它也将用作后缀运算符,除非您明确定义后缀++。如果您只提供一个后缀 ++,则 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

如您所见,continuebreak 可以使用它,这要归功于其复杂的结构。 请参阅http://www.chiark.greenend.org.uk/~sgtatham/mp/ 了解更多用于创建自定义控制结构的 C 预处理器。

【讨论】:

【参考方案2】:

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

【讨论】:

【参考方案3】:

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> nums3, 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';

【讨论】:

【参考方案4】:

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

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

例如,std::vector&lt;int&gt; 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]; 

    ...
 ;

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

【讨论】:

@RicardoPieper 请注意,基于范围的 for 循环仅在最新版本的 Visual Studio (2012) 中可用。 所以它不会在任何其他编译器/IDE 中编译? @RicardoPieper 它将在过去几年的任何版本的 Clang 或 gcc 中编译(我认为 gcc 为 4.7 以上,不确定 Clang 版本)。 好的,这对我来说已经足够了,因为我使用 VS2012 进行编程。我成功实现了迭代器,非常简单。 我认为答案并不完整。您隐含地假设 List::iterator 具有某些功能(在这种情况下,您是“免费”获得的,因为 iterator 是一个指针。这不是一般情况)。迭代器类型的假设是什么?是operator++和operator==。还有什么?【参考方案5】:

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

C++11 有 range-based for loops 用于迭代容器的元素。您需要为您的类实现begin()end() 成员函数,它们将分别返回第一个元素和最后一个元素的迭代器。当然,这意味着您还需要为您的班级实施合适的iterators。如果你真的想走这条路,不妨看看Boost.IteratorFacade;它减少了很多自己实现迭代器的痛苦。

之后你就可以这样写了:

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


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

【讨论】:

您好,您的回答也很好,但遗憾的是我不能接受 2 个回答。并感谢您的链接:D

以上是关于如何使 C++ 中的 for each 循环函数与自定义类一起使用的主要内容,如果未能解决你的问题,请参考以下文章

C++ STL应用与实现26: 如何使用std::for_each以及基于范围的for循环 (since C++11)

C++ STL for_each 的用法?

JAVA中的for-each循环与迭代

C++ 中奇怪的括号符号,看起来有点像 for each 循环

如何使 tabItem 仪表板中的 for 循环或 lapply 循环中的函数闪亮

如何将 for-each 循环应用于字符串中的每个字符?