在迭代地图时,它或它是++?

时间:2011-08-03 13:08:43

标签: c++ iterator iteration stdmap

显示如何迭代std::map的示例通常是这样的:

MapType::const_iterator end = data.end(); 
for (MapType::const_iterator it = data.begin(); it != end; ++it)

即。它使用++it代替it++。有什么理由吗?如果我改用it++会不会有任何问题?

6 个答案:

答案 0 :(得分:20)

it++返回前一个迭代器的副本。由于不使用此迭代器,因此这很浪费。 ++it返回对递增迭代器的引用,避免复制。

请参阅Question 13.15以获得更全面的解释。

答案 1 :(得分:11)

进行测试,我制作了三个源文件:

#include <map>

struct Foo { int a; double b; char c; };

typedef std::map<int, Foo> FMap;

### File 1 only ###

void Set(FMap & m, const Foo & f)
{
  for (FMap::iterator it = m.begin(), end = m.end(); it != end; ++it)
    it->second = f;
}

### File 2 only ###

void Set(FMap & m, const Foo & f)
{
  for (FMap::iterator it = m.begin(); it != m.end(); ++it)
    it->second = f;
}

### File 3 only ###

void Set(FMap & m, const Foo & f)
{
  for (FMap::iterator it = m.begin(); it != m.end(); it++)
    it->second = f;
}

### end ###

使用g++ -S -O3,GCC 4.6.1编译后,我发现版本2和3生成了相同的程序集,而版本1只在一条指令cmpl %eax, %esi vs cmpl %esi, %eax

所以,请选择并使用适合您风格的任何内容。前缀增量++it可能是最好的,因为它最准确地表达了您的要求,但不要挂断它。

答案 2 :(得分:7)

使用预增量运算符与后增量运算符有轻微的performance advantage。在设置使用迭代器的循环时,您应该选择使用预增量:

for (list<string>::const_iterator it = tokens.begin();
    it != tokens.end();
    ++it) { // Don't use it++
    ...
}

当您考虑如何实施两种运营商时,原因就会浮出水面。预增量非常简单。但是,为了使后增量起作用,您需要首先制作对象的副本,对原始对象执行实际增量,然后返回副本:

class MyInteger {
private:
    int m_nValue;

public:
    MyInteger(int i) {
        m_nValue = i;
    }

    // Pre-increment
    const MyInteger &operator++() {
        ++m_nValue;
        return *this;
    }

    // Post-increment
    MyInteger operator++(int) {
        MyInteger clone = *this; // Copy operation 1
        ++m_nValue;
        return clone; // Copy operation 2
    }
}

如您所见,增量后实现涉及两个额外的复制操作。如果所讨论的物体体积庞大,这可能非常昂贵。话虽如此,一些编译器可能足够智能,可以通过优化来完成单个复制操作。关键是后增量通常涉及比预增量更多的工作,因此习惯将“++”放在迭代器之前而不是之后是明智的。

(1)归功于链接网站。

答案 3 :(得分:6)

从逻辑的角度来看 - 它是相同的,无关紧要 here

为什么使用前缀one - 因为它更快 - 它更改迭代器并返回其值,而后缀创建临时对象,递增当前迭代器,然后返回临时对象(同一迭代器的副本,在递增之前) )。由于没有人在这里观察这个临时对象(返回值),所以它(逻辑上)是相同的。

很有可能,编译器会对此进行优化。


另外 - 实际上,对于任何类型,这应该是这样的。但它应该是。任何人都可以重载operator++ - 后缀和前缀,它们可以有副作用和不同的行为。

嗯,这是一件可怕的事情,但仍有可能。

答案 4 :(得分:1)

它不会导致任何问题,但使用++it更为正确。对于小类型,使用++ii++并不是真正的问题,而是使用“大”类:

operator++(type x,int){
    type tmp=x; //need copy
    ++x;
    return tmp;
}

编译器可能会优化其中一些,但很难确定。

答案 5 :(得分:1)

正如其他答案所说,除非它不能在上下文中起作用,否则更喜欢它。对于小型类型的容器进行迭代,它确实没什么区别(或者如果编译器优化它就没有区别),但是对于大型容器,它可以通过节省制作副本的成本而产生差异。

没错,您可能会在特定情况下知道该类型足够小,因此您不必担心它。但是稍后,团队中的其他人可能会将容器的内容更改为重要的位置。另外,我认为最好让自己养成一个好习惯,只有在你知道必须的时候才增加后期。