前段时间,我和同事讨论了如何在STL映射中插入值。I prefer map[key] = value;因为它让人感觉自然,读起来清晰,而他更喜欢地图。插入(std:: make_pair(关键字,值))。

我只是问了他,我们都不记得为什么插入更好,但我相信这不仅仅是一个风格偏好,而是有一个技术原因,如效率。SGI STL引用简单地说:“严格地说,这个成员函数是不必要的:它只是为了方便而存在。”

有人能告诉我原因吗,还是我只是在做梦?


当前回答

下面是另一个例子,如果键值存在,操作符[]会覆盖键值,但是.insert不会覆盖键值。

void mapTest()
{
  map<int,float> m;


  for( int i = 0 ; i  <=  2 ; i++ )
  {
    pair<map<int,float>::iterator,bool> result = m.insert( make_pair( 5, (float)i ) ) ;

    if( result.second )
      printf( "%d=>value %f successfully inserted as brand new value\n", result.first->first, result.first->second ) ;
    else
      printf( "! The map already contained %d=>value %f, nothing changed\n", result.first->first, result.first->second ) ;
  }

  puts( "All map values:" ) ;
  for( map<int,float>::iterator iter = m.begin() ; iter !=m.end() ; ++iter )
    printf( "%d=>%f\n", iter->first, iter->second ) ;

  /// now watch this.. 
  m[5]=900.f ; //using operator[] OVERWRITES map values
  puts( "All map values:" ) ;
  for( map<int,float>::iterator iter = m.begin() ; iter !=m.end() ; ++iter )
    printf( "%d=>%f\n", iter->first, iter->second ) ;

}

其他回答

map::insert()的一个问题是,如果键已经存在于映射中,它不会替换某个值。我见过Java程序员编写的c++代码,他们希望insert()的行为与Java中的Map.put()相同,其中值被替换。

当你写作的时候

map[key] = value;

没有办法知道你是否替换了value for key,或者你是否用value创建了一个新键。

Map::insert()只创建:

using std::cout; using std::endl;
typedef std::map<int, std::string> MyMap;
MyMap map;
// ...
std::pair<MyMap::iterator, bool> res = map.insert(MyMap::value_type(key,value));
if ( ! res.second ) {
    cout << "key " <<  key << " already exists "
         << " with value " << (res.first)->second << endl;
} else {
    cout << "created key " << key << " with value " << value << endl;
}

对于我的大多数应用程序,我通常不关心我是在创建还是替换,所以我使用更容易阅读的map[key] = value。

关于std::map还有一点需要注意:

关联(nonExistingKey);将在map中创建一个新条目,键指向初始化为默认值的nonExistingKey。

当我第一次看到它时(当我的头撞到一个讨厌的遗留bug时),这把我吓坏了。没想到会这样。对我来说,这看起来像一个get操作,我没有预料到“副作用”。当从你的地图获取时,更倾向于map.find()。

如果你的应用程序是速度关键,我会建议使用[]操作符,因为它创建了原始对象的总共3个副本,其中2个是临时对象,迟早会被销毁为。

但是在insert()中,创建了原始对象的4个副本,其中3个是临时对象(不一定是“临时对象”),并销毁。

这意味着额外的时间: 1. 一个对象的内存分配 2. 一个额外的构造函数调用 3.一个额外的析构函数调用 4. 一个对象是内存释放

如果你的对象很大,构造函数是典型的,析构函数可以释放大量的资源,以上的点更重要。至于可读性,我认为两者都很公平。

同样的问题也出现在我的脑海中,但不是关于可读性,而是关于速度。 下面是一个示例代码,通过它我了解了我提到的要点。

class Sample
{
    static int _noOfObjects;

    int _objectNo;
public:
    Sample() :
        _objectNo( _noOfObjects++ )
    {
        std::cout<<"Inside default constructor of object "<<_objectNo<<std::endl;
    }

    Sample( const Sample& sample) :
    _objectNo( _noOfObjects++ )
    {
        std::cout<<"Inside copy constructor of object "<<_objectNo<<std::endl;
    }

    ~Sample()
    {
        std::cout<<"Destroying object "<<_objectNo<<std::endl;
    }
};
int Sample::_noOfObjects = 0;


int main(int argc, char* argv[])
{
    Sample sample;
    std::map<int,Sample> map;

    map.insert( std::make_pair<int,Sample>( 1, sample) );
    //map[1] = sample;
    return 0;
}

事实上,std::map insert()函数不会覆盖与键相关的值,这允许我们像这样编写对象枚举代码:

string word;
map<string, size_t> dict;
while(getline(cin, word)) {
    dict.insert(make_pair(word, dict.size()));
}

当我们需要将不同的非唯一对象映射到范围0..N的某个id时,这是一个非常常见的问题。这些id可以稍后使用,例如,在图算法中。在我看来,使用操作符[]的替代选项看起来可读性较差:

string word;
map<string, size_t> dict;
while(getline(cin, word)) {
    size_t sz = dict.size();
    if (!dict.count(word))
        dict[word] = sz; 
}