13

我有一个在 C++ 中经常使用的“foreach”宏,它适用于大多数 STL 容器:

#define foreach(var, container) \
  for(typeof((container).begin()) var = (container).begin(); \
      var != (container).end(); \
      ++var)

(请注意,'typeof' 是 gcc 扩展。)它的使用如下:

std::vector< Blorgus > blorgi = ...;
foreach(blorgus, blorgi) {
  blorgus->draw();
}

我想做一些类似的东西来迭代地图的值。也许称它为“foreach_value”。所以不要写

foreach(pair, mymap) {
  pair->second->foo();
}

我会写

foreach_value(v, mymap) {
  v.foo();
}

我想不出一个可以做到这一点的宏,因为它需要声明两个变量:迭代器和值变量(上面的'v')。我不知道如何在 for 循环的初始化程序中执行此操作,即使使用 gcc 扩展也是如此。我可以在 foreach_value 调用之前声明它,但是它会与同一范围内的 foreach_value 宏的其他实例发生冲突。如果我可以将当​​前行号作为迭代器变量名称的后缀,它会起作用,但我不知道该怎么做。

4

11 回答 11

8

您会在寻找BOOST_FOREACH - 他们已经为您完成了所有工作!

如果您确实想自己动手,您可以在 C++ 中的任何位置声明一个块,这可以解决您的 itr->second 中间存储的范围问题...

// Valid C++ code (which does nothing useful)
{
  int a = 21; // Which could be storage of your value type
}
// a out of scope here
{ 
  int a = 32; // Does not conflict with a above
}
于 2008-09-17T00:59:13.117 回答
5

您可以使用两个循环来执行此操作。第一个声明了迭代器,其名称是容器变量的函数(如果您担心与自己的代码冲突,可以使这个更难看)。第二个声明值变量。

#define ci(container) container ## iter
#define foreach_value(var, container) \
    for (typeof((container).begin()) ci(container) = container.begin(); \
         ci(container) != container.end(); ) \
        for (typeof(ci(container)->second)* var = &ci(container)->second; \
             ci(container) != container.end(); \
             (++ci(container) != container.end()) ? \
                 (var = &ci(container)->second) : var)

通过使用相同的循环终止条件,外循环只发生一次(如果幸运的话,会被优化掉)。此外,如果地图为空,则避免在迭代器上调用 ->second。这与内循环增量中的三元运算符的原因相同;最后,我们只是将 var 保留为最后一个值,因为它不会再次被引用。

您可以内联 ci(container),但我认为它使宏更具可读性。

于 2008-09-17T01:48:56.443 回答
3

STL变换函数也有类似的作用。

论据是(按顺序):

  1. 指定容器开头的输入迭代器
  2. 指定容器结束的输入迭代器
  3. 定义输出位置的输出迭代器(对于就地转换,类似于 for-each,只需传递 #1 中的输入迭代器)
  4. 对每个元素执行的一元函数(函数对象)

对于一个非常简单的示例,您可以通过以下方式将字符串中的每个字符大写:

#include <iostream>
#include <string>
#include <algorithm>
#include <cctype>

int main(int argc, char* argv[]) {
    std::string s("my lowercase string");
    std::transform(s.begin(), s.end(), s.begin(), toupper);
    std::cout << s << std::endl; // "MY LOWERCASE STRING"
}

或者,还有累积函数,它允许在调用函数对象之间保留一些值。accum不会像transform那样修改输入容器中的数据。

于 2008-09-17T02:24:10.603 回答
1

您是否考虑过使用Boost 库?他们foreach实现了一个宏,它可能比你写的任何东西都更健壮......而且还有transform_iterator一个似乎可以用来做你想要的第二次提取部分。

不幸的是,我不能确切地告诉你如何使用它,因为我对 C++ 了解的不够多:)这个谷歌搜索出现了一些有希望的答案:comp.lang.c++.moderatedBoost transform_iterator use case

于 2008-09-17T01:01:11.130 回答
1

Boost::For_each 是迄今为止你最好的选择。有趣的是,它们实际上给您的是宏 BOOST_FOREACH(),然后您可以将其包装并 #define 到您真正想在代码中调用的任何内容。大多数人都会选择旧的“foreach”,但其他商店可能有不同的编码标准,所以这符合这种心态。Boost 还为 C++ 开发人员提供了许多其他好处!很值得使用。

于 2008-09-17T02:53:48.410 回答
1

我创建了一个小的 Foreach.h 助手,其中包含一些 foreach() 变体,包括对局部变量和指针进行操作的变体,还有一个额外的版本,可以防止从循环中删除元素。所以使用我的宏的代码看起来很舒服,如下所示:

#include <cstdio>
#include <vector>
#include "foreach.h"

int main()
{
    // make int vector and fill it
    vector<int> k;
    for (int i=0; i<10; ++i) k.push_back(i);

    // show what the upper loop filled
    foreach_ (it, k) printf("%i ",(*it));
    printf("\n");

    // show all of the data, but get rid of 4
    // http://en.wikipedia.org/wiki/Tetraphobia :)
    foreachdel_ (it, k)
    {
        if (*it == 4) it=k.erase(it);
        printf("%i ",(*it));
    }
    printf("\n");

    return 0;
}

输出:

0 1 2 3 4 5 6 7 8 9
0 1 2 3 5 6 7 8 9

我的Foreach.h提供以下宏:

  • foreach() - 用于指针的常规 foreach
  • foreach_() - 局部变量的常规 foreach
  • foreachdel() - foreach 版本,在循环中检查删除,指针版本
  • foreachdel_() - foreach 版本,在循环内检查删除,局部变量版本

他们确实为我工作,我希望他们也能让你的生活更轻松:)

于 2010-11-18T12:34:55.250 回答
1

这个问题有两个部分。您需要以某种方式(1)在映射的(而不是键)上生成一个迭代器(或者更确切地说,一个可迭代的序列),以及(2)使用宏来进行迭代而无需大量样板。

最干净的解决方案是对第 (1) 部分使用Boost Range Adapter ,对第 (2) 部分使用Boost Foreach。您不需要自己编写宏或实现迭代器。

#include <map>
#include <string>
#include <boost/range/adaptor/map.hpp>
#include <boost/foreach.hpp>

int main()
{
    // Sample data
    std::map<int, std::string> myMap ;
    myMap[0] = "Zero" ;
    myMap[10] = "Ten" ;
    myMap[20] = "Twenty" ;

    // Loop over map values
    BOOST_FOREACH( std::string text, myMap | boost::adaptors::map_values )
    {
        std::cout << text << " " ;
    }
}
// Output:
// Zero Ten Twenty
于 2011-08-31T05:48:38.417 回答
0

您可以定义一个模板类,它将 mymap 的类型作为模板参数,并通过重载 * 和 -> 来充当值的迭代器。

于 2008-09-17T00:53:55.717 回答
0
#define foreach(var, container) for (typeof((container).begin()) var = (container).begin(); var != (container).end(); ++var)

C++ 中没有 typeof ......这是如何为你编译的?(它当然不是便携式的)

于 2008-09-17T07:01:24.133 回答
0

foreach_value我根据代码实现了自己的Boost foreach

#include <boost/preprocessor/cat.hpp>
#define MUNZEKONZA_FOREACH_IN_MAP_ID(x)  BOOST_PP_CAT(x, __LINE__)

namespace munzekonza {
namespace foreach_in_map_private {
inline bool set_false(bool& b) {
  b = false;
  return false;
}

}
}

#define MUNZEKONZA_FOREACH_VALUE(value, map)                                  \
for(auto MUNZEKONZA_FOREACH_IN_MAP_ID(_foreach_in_map_it) = map.begin();      \
        MUNZEKONZA_FOREACH_IN_MAP_ID(_foreach_in_map_it) != map.end();)       \
for(bool MUNZEKONZA_FOREACH_IN_MAP_ID(_foreach_in_map_continue) = true;       \
      MUNZEKONZA_FOREACH_IN_MAP_ID(_foreach_in_map_continue) &&               \
      MUNZEKONZA_FOREACH_IN_MAP_ID(_foreach_in_map_it) != map.end();          \
      (MUNZEKONZA_FOREACH_IN_MAP_ID(_foreach_in_map_continue)) ?              \
        ((void)++MUNZEKONZA_FOREACH_IN_MAP_ID(_foreach_in_map_it)) :          \
        (void)0)                                                              \
  if( munzekonza::foreach_in_map_private::set_false(                          \
          MUNZEKONZA_FOREACH_IN_MAP_ID(_foreach_in_map_continue))) {} else    \
  for( value = MUNZEKONZA_FOREACH_IN_MAP_ID(_foreach_in_map_it)->second;      \
        !MUNZEKONZA_FOREACH_IN_MAP_ID(_foreach_in_map_continue);              \
        MUNZEKONZA_FOREACH_IN_MAP_ID(_foreach_in_map_continue) = true)        

例如,您可以像这样在代码中使用它:

#define MUNZEKONZA_FOREACH_VALUE foreach_value

std::map<int, std::string> mymap;
// populate the map ...

foreach_value( const std::string& value, mymap ) {
  // do something with value
}

// change value
foreach_value( std::string& value, mymap ) {
  value = "hey";
}
于 2012-08-15T15:32:30.140 回答
0
#define zforeach(var, container) for(auto var = (container).begin(); var != (container).end(); ++var)

没有 typeof() 所以你可以使用它:

decltype((container).begin()) var 
decltype(container)::iterator var
于 2017-02-01T22:29:01.893 回答