如何简单遍历std::map<...>?

5

我有一个变量,其类型类似于:

map<bool, map<string, pair<string, int> > > items;

我有一个变量,我需要将其传递给不同的函数。

是否有一种更简便的方法来迭代它,而不是重复编写代码:

for (map<bool, map<string, pair<string, int> > >::iterator p = items.begin();
    p != items.end(); p++)
    ...

每一次都需要写类型名称吗?(例如,我是否可以通过宏或模板之类的方式省略类型名称?手动的typedef不算。) 我正在使用Visual C++ 2008。

9
一个布尔键的地图?你确定需要这个吗? - 6502
3
在C++0x(Visual C++ 2010和GCC 4.4+支持),你可以使用基于范围的for循环:for(auto idx: items) {...} - norcalli
@Norcalli:VS2010不支持C++0x,也不支持基于范围的for循环。 - Alexandre C.
1
@Alex,VC++ 2010 实现了 C++0x,但并非全部。范围基于的 for 循环未实现。不过,在这种情况下,可以使用“auto”关键字。 - Ajay
说任何东西“支持C++0x”都是愚蠢的,因为支持的功能各不相同。但是,是的,VC++ 2010支持一些功能。而且,不,范围for循环不是其中之一。 - Lightness Races in Orbit
显示剩余3条评论
8个回答

6

您可以使用BOOST_FOREACH。但是为了清晰起见,您需要使用typedef:

typedef std::map<std::string, std::pair<std::string, int> > inner_map;
typedef std::pair<bool, inner_map> map_entry;

BOOST_FOREACH(map_entry& p, items)
{
    ...
}

我更喜欢简单的typedef和for循环。我把typedef看作变量赋值一样:

typedef std::map<std::string, std::pair<std::string, int> > inner_map;
typedef std::map<bool, inner_map>::iterator map_iterator;

for (map_iterator i = items.begin(); i != items.end(); ++i)
{
    ...
}

这些typedef也可以是私有成员。这种编码风格更清晰,因为您一眼就可以看到涉及的类型。

或者,如果您准备编写一个函数对象,则可以使用普通的std :: for_each。在标准C ++中,我不太喜欢这种方法,因为循环体不再是局部的(但在某些情况下,这可能是一个优势):

struct some_functor
{
    template <typename K, typename V>
    void operator()(std::pair<K, V>& item)
    {
        // In the context below, K is bool and
        // V is map<string, pair<string, int> >
    }
};

之后

std::for_each(items.begin(), items.end(), some_functor());

如果您升级到VS2010,您有以下几种选择:autostd::for_each与lambda表达式(我更喜欢这个)。在C++0x中,从技术上讲,您还可以使用基于范围的for循环(在VS2010中不可用)。
总之,我的建议是:
class meaningful_data
{
    typedef std::map<std::string, std::pair<std::string, int> > inner_map;
    std::map<bool, inner_map> items;

public:
    typedef std::pair<bool, inner_map> value_type;
    typedef std::map<bool, inner_map>::iterator iterator;
    typedef std::map<bool, inner_map>::const_iterator const_iterator;

    iterator begin() { return items.begin(); }
    const_iterator begin() const { return items.begin(); }
    iterator end() { return items.end(); }
    const_iterator end() const { return items.end(); }

    // Add some interface here (as small as possible)
};

然后像这样迭代:

for (meaningful_data::iterator i = d.begin(); i != d.end(); ++i)
{
    ...
}

或者

BOOST_FOREACH(meaningful_data::value_type& i, d)
{
    ...
}

你可能希望封装这样一个复杂类型,至少使用一些typedef(如果inner_map类型应该是公共的,你不必强制使用完整的类)。


好的,看起来BOOST_FOREACH是我需要的,除了Boost有32,000多个文件(我已经犹豫是否要添加一个额外的文件...)和40 MiB压缩,这对于我正在尝试做的事情来说有点荒谬。有没有更“轻量级”的版本?(否则我会尝试自己实现它,但我无法弄清楚他们是如何做到的...) - user541686
@Mehrdad:实现起来非常棘手。由于它是一个宏,所以您不能对参数进行多次评估。请参阅http://www.artima.com/cppsource/foreach.html ,了解他们如何实现它。在我看来,这并不值得麻烦。只需使用typedef即可。 - Alexandre C.
编辑-- BOOST_FOREACH似乎仍然不是我所需的,因为我需要声明键的数据类型!:( - user541686
@Mehrdad:别想让编译器替你推断数据类型。在标准C++中是不可能的。最接近的方法是使用std::for_each和一个带有模板operator()的函数对象。 - Alexandre C.

4
您可以使用标准的 for_each 算法:
#include <algorithm>

struct your_functor {
  template<typename T>
  void operator()(T const &item) {
    // Your loop body here.
  }
}

std::for_each(items.begin(), items.end(), your_functor());

4

我建议使用typedef,这基本上是在说“不,你不能” ;)

否则,如果您切换到支持C++0x中定义的auto的编译器,您可以这样说:

for (auto p = items.begin(); p != items.end(); ++p) // ...

(哦,顺便说一下,我还建议使用++p来避免复制迭代器)

1
我可能错了,这有点离题,但是我们不能假设任何体面和现代的编译器在标准类型中当返回值未被使用时会将任何 x++ 替换为 ++x 吗? - ereOn
2
@ereOn:我认为,为什么不使用++x呢?当然,编译器可能会对其进行优化,但也可能不会。为什么要让它做我不想要的事情,依赖于编译器将其转换为我想要的东西,而我可以一开始就告诉它我想要什么呢? :) - Magnus Hoff
@ere0n - 这取决于你所说的标准类型是什么意思。通常情况下,++x 可能与 x++ 完全不同,因此如果结果未使用,编译器无法假设它们是相同的。但是,它可以消除带有 x++ 的迭代器副本(即使它具有副作用,也明确允许这样做)。 - ltjax
推荐使用typedef(对于OP:typedef解决方案有什么问题吗?)+1,如果可以的话,再+1赞成++p。尽管语言名称为C ++,但在C ++中的标准习惯用法是始终使用++c,除非您真的非常需要c ++的返回值。这与C中的标准习惯用法完全相反,即除非您真的非常需要++c的返回值,否则应使用c ++。在C ++代码中随意使用c ++是程序员是C ++新手的明显标志。 - David Hammen
@Magnus Hoff:您说得完全正确。我也尽可能使用前缀形式。我只是想确保我的信念是正确的:如果可能的话,最好使用++x而不是x++,但如果不这样做也不会有太大代价。感谢您的澄清。 - ereOn

2

1

你可以编写自己的算法函数。

template<class C>
void do_what_I_want_to_do(C& c)
{
    for (C::iterator i = c.begin(); i != c.end(); ++c)
    {
        // do something
    }
}

do_what_I_want_to_do(items);

这可能对你来说是一个改进,也可能不是。


0

当然,可以使用typedef:

typedef std::map<std::string, std::pair<std::string, int> > Item;
typedef Item::const_iterator                                ItemCItr;

typedef std::map<bool, Item>                                ItemMap;
typedef ItemMap::const_iterator                             ItemMapItr;

for (ItemMapItr it = m.begin(), end = m.end(); it != end; ++it)
{
  const Item & item = it->second;

  for (ItemItr jt = item.begin(), jend = item.end(); jt != jend; ++jt)
  {
     /* ... */
  }
}

@Nick:哦,好的,那就算了。我认为这仍然是一般情况下最漂亮的解决方案,在C++0x之外,但我很感激这不是OP想要的。 - Kerrek SB

0

在看到所有的“不,你不能这样做”的答案之后,我花了一些时间试图找到至少一个部分解决方法。

这个版本几乎可以工作,但需要注意的是,每次对迭代器的引用也需要对容器进行引用。可能实际使用它不是一个好主意(因为堆分配和其他事情),但我还是想分享一下:

#include <map>
#include <iostream>
using namespace std;

template<typename T>
bool _end(T& src, void *iterator = NULL)
{ return static_cast<typename T::iterator>(iterator) < src.end(); }

template<typename T>
struct _IterateHelper
{
    typename T::iterator *pIterator;
    _IterateHelper(T& dummy, void *&p)
    { this->pIterator = static_cast<typename T::iterator *>(p); }
    ~_IterateHelper() { delete pIterator; }
};

template<typename T>
_IterateHelper<T> _iterateHelper(T& dummy, void *&p)
{ return _IterateHelper<T>(dummy, p); }

template<typename T>
bool _iterate(T& container, void *&iterator)
{
    typename T::iterator *&p =
        reinterpret_cast<typename T::iterator *&>(iterator);
    if (iterator == NULL) { p = new typename T::iterator(container.begin()); }
    else { ++*p; }
    return *p != container.end();
}

template<typename T>
typename T::iterator & I(T& container, void *&pIterator)
{ return *static_cast<typename T::iterator *>(pIterator); }

#define FOR_EACH(state, container) \
    void *state = NULL; \
    for (_iterateHelper(container, state); _iterate(container, state); )


int main()
{
    map<string, string> m;
    items["a"] = "b";
    items["1"] = "2";
    FOR_EACH(p, items)
        cout << I(items, p)->first << ": " << I(items, p)->second << endl;
}

你的宏泄漏了一个局部变量到环境范围中,这可能会导致问题。此外,以下划线大写字母开头的标识符保留给编译器和标准库,请勿定义此类标识符。 - Kerrek SB
@Kerrek: (1) 这可以很容易地通过 { } 进行范围限定。 (2) 我认为这是一些理论上可以制作的“标准库”的一部分,这就是为什么我选择了下划线。 :P 但是,是的,我知道这个问题。目标不是真正将其投入生产,而只是尝试并查看发生了什么。 - user541686

0

Qt提供了自己的foreach实现,所以我只是对std::map进行了重新编写 - 基本上只是一个简单的修改(->second)。在MSVC和gcc上进行了测试。

struct ForeachBaseBase {};

template <typename T1, typename T2>
class ForeachBase: public ForeachBaseBase
{
public:
    inline ForeachBase(const std::map<T1,T2>& t): c(t), brk(0), i(c.begin()), e(c.end()){}
    const std::map<T1,T2> c;
    mutable int brk;
    mutable typename std::map<T1,T2>::const_iterator i, e;
    inline bool condition() const { return (!brk++ && i != e);}
};

template <typename T1, typename T2> inline std::map<T1,T2> *pMForeachPointer(const std::map<T1,T2> &) { return 0; }

template <typename T1, typename T2> inline ForeachBase<T1,T2> pMForeachBaseNew(const std::map<T1,T2>& t)
{ return ForeachBase<T1,T2>(t); }

template <typename T1, typename T2>
inline const ForeachBase<T1,T2> *pMForeachBase(const ForeachBaseBase *base, const std::map<T1,T2> *)
{ return static_cast<const ForeachBase<T1,T2> *>(base); }


#if defined(Q_CC_MIPS)
/*
   Proper for-scoping in MIPSpro CC
*/
#  define MAP_FOREACH(variable,container)                                                             \
    if(0){}else                                                                                     \
    for (const ForeachBaseBase &_container_ = pMForeachBaseNew(container);                \
         pMForeachBase(&_container_, true ? 0 : pMForeachPointer(container))->condition();       \
         ++pMForeachBase(&_container_, true ? 0 : pMForeachPointer(container))->i)               \
        for (variable = pMForeachBase(&_container_, true ? 0 : pMForeachPointer(container))->i->second; \
             pMForeachBase(&_container_, true ? 0 : pMForeachPointer(container))->brk;           \
             --pMForeachBase(&_container_, true ? 0 : pMForeachPointer(container))->brk)

#elif defined(Q_CC_DIAB)
// VxWorks DIAB generates unresolvable symbols, if container is a function call
#  define MAP_FOREACH(variable,container)                                                             \
    if(0){}else                                                                                     \
    for (const ForeachBaseBase &_container_ = pMForeachBaseNew(container);                \
         pMForeachBase(&_container_, (__typeof__(container) *) 0)->condition();       \
         ++pMForeachBase(&_container_, (__typeof__(container) *) 0)->i)               \
        for (variable = pMForeachBase(&_container_, (__typeof__(container) *) 0)->i->second; \
             pMForeachBase(&_container_, (__typeof__(container) *) 0)->brk;           \
             --pMForeachBase(&_container_, (__typeof__(container) *) 0)->brk)

#else
#  define MAP_FOREACH(variable, container) \
    for (const ForeachBaseBase &_container_ = pMForeachBaseNew(container); \
         pMForeachBase(&_container_, true ? 0 : pMForeachPointer(container))->condition();       \
         ++pMForeachBase(&_container_, true ? 0 : pMForeachPointer(container))->i)               \
        for (variable = pMForeachBase(&_container_, true ? 0 : pMForeachPointer(container))->i->second; \
             pMForeachBase(&_container_, true ? 0 : pMForeachPointer(container))->brk;           \
             --pMForeachBase(&_container_, true ? 0 : pMForeachPointer(container))->brk)
#endif // MSVC6 || MIPSpro

#define mforeach MAP_FOREACH

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接