【问题标题】:Iterating over a QMap with for使用 for 遍历 QMap
【发布时间】:2012-01-21 00:24:18
【问题描述】:

我有一个 QMap 对象,我正在尝试将其内容写入文件。

QMap<QString, QString> extensions;
//.. 

for(auto e : extensions)
{
  fout << e.first << "," << e.second << '\n';
}  

为什么会得到:error: 'class QString' has no member named 'first' nor 'second'

e 不是 QPair 类型吗?

【问题讨论】:

    标签: c++ qt c++11 qmap


    【解决方案1】:

    如果您想要带有 first 和 second 的 STL 样式,请执行以下操作:

    for(auto e : extensions.toStdMap())
    {
      fout << e.first << "," << e.second << '\n';
    }
    

    如果您想使用 Qt 提供的功能,请执行以下操作:

    for(auto e : extensions.keys())
    {
      fout << e << "," << extensions.value(e) << '\n';
    }
    

    【讨论】:

    • toStdMap() 和 keys() 方法将复制地图的全部内容,或者它的键是相当足智多谋的。我想知道为什么它被选为最佳答案并获得了如此多的选票
    • @FPGAwarrior 相反,它seems that toStdMap() 与QMap 的大小是线性的,所以它应该有所作为。
    • 也更喜欢现在可用的for(auto e : qAsConst(extensions.keys())),因为这样可以避免容器的深层复制。
    • @FPGAwarrior 谁说QMap 在内部将密钥存储在QList 中?我敢打赌,这个列表是在请求时根据地图的内部结构构建的。
    • @rubenvb 我的错。我对实施有误。 QMap 的内部表示是一棵名为 QMapNodeBase 的黑红树,keys() 方法确实在线性时间内迭代。我会删除我之前的错误评论。
    【解决方案2】:

    C++11 range-based-for 使用解引用迭代器的类型作为自动推导的“游标”类型。在这里,它是表达式*map.begin() 的类型。
    而且由于QMap::iterator::operator*() 返回对值的引用(QString &amp; 类型),因此无法使用该方法访问该键。

    你应该使用文档中描述的迭代器方法之一,但你应该避免使用

    • keys() 因为它涉及创建一个键列表,然后搜索每个键的值,或者,
    • toStdMap() 因为它将所有地图元素复制到另一个,

    这不是最理想的。


    您还可以使用包装器将QMap::iterator 作为auto 类型:
    template<class Map>
    struct RangeWrapper {
        typedef typename Map::iterator MapIterator;
        Map &map;
    
        RangeWrapper(Map & map_) : map(map_) {}
    
        struct iterator {
            MapIterator mapIterator;
            iterator(const MapIterator &mapIterator_): mapIterator(mapIterator_) {}
            MapIterator operator*() {
                return mapIterator;
            }
            iterator & operator++() {
                ++mapIterator;
                return *this;
            }
            bool operator!=(const iterator & other) {
                return this->mapIterator != other.mapIterator;
            }
        };
        iterator begin() {
            return map.begin();
        }
        iterator end() {
            return map.end();
        }
    };
    
    // Function to be able to use automatic template type deduction
    template<class Map>
    RangeWrapper<Map> toRange(Map & map)
    {
        return RangeWrapper<Map>(map);
    }
    
    // Usage code
    QMap<QString, QString> extensions;
    ...
    for(auto e : toRange(extensions)) {
        fout << e.key() << "," << e.value() << '\n';
    }
    

    还有另一个包装器here。

    【讨论】:

      【解决方案3】:

      对于对优化感兴趣的人,我尝试了几种方法,做了一些微基准测试,我可以得出结论,STL 风格的方法明显更快。

      我试过用这些方法添加整数:

      • QMap::values()
      • Java 样式迭代器(如文档中所述)
      • STL 样式迭代器(也按照文档中的建议)

      我已经将它与 QList/QVector 的整数相加进行了比较

      结果:

      Reference vector :   244  ms
      Reference list :     1239  ms
      
      QMap::values() :     6504  ms
      Java style iterator :    6199  ms
      STL style iterator :     2343  ms
      

      感兴趣的人的代码:

      #include <QDateTime>
      #include <QMap>
      #include <QVector>
      #include <QList>
      #include <QDebug>
      
      void testQMap(){
          QMap<int, int> map;
          QVector<int> vec;
          QList<int> list;
      
          int nbIterations = 100;
          int size = 1000000;
          volatile int sum = 0;
      
          for(int i = 0; i<size; ++i){
              int randomInt = qrand()%128;
              map[i] = randomInt;
              vec.append(randomInt);
              list.append(randomInt);
          }
      
      
          // Rererence vector/list
          qint64 start = QDateTime::currentMSecsSinceEpoch();
          for(int i = 0; i<nbIterations; ++i){
              sum = 0;
              for(int j : vec){
                  sum += j;
              }
          }
          qint64 end = QDateTime::currentMSecsSinceEpoch();
          qDebug() << "Reference vector : \t" << (end-start) << " ms";
      
          qint64 startList = QDateTime::currentMSecsSinceEpoch();
          for(int i = 0; i<nbIterations; ++i){
              sum = 0;
              for(int j : list){
                  sum += j;
              }
          }
          qint64 endList = QDateTime::currentMSecsSinceEpoch();
          qDebug() << "Reference list : \t" << (endList-startList) << " ms";
      
          // QMap::values()
          qint64 start0 = QDateTime::currentMSecsSinceEpoch();
          for(int i = 0; i<nbIterations; ++i){
              sum = 0;
              QList<int> values = map.values();
              for(int k : values){
                  sum += k;
              }
          }
          qint64 end0 = QDateTime::currentMSecsSinceEpoch();
          qDebug() << "QMap::values() : \t" << (end0-start0) << " ms";
      
      
          // Java style iterator
          qint64 start1 = QDateTime::currentMSecsSinceEpoch();
          for(int i = 0; i<nbIterations; ++i){
              sum = 0;
              QMapIterator<int, int> it(map);
              while (it.hasNext()) {
                  it.next();
                  sum += it.value();
              }
          }
          qint64 end1 = QDateTime::currentMSecsSinceEpoch();
          qDebug() << "Java style iterator : \t" << (end1-start1) << " ms";
      
      
          // STL style iterator
          qint64 start2 = QDateTime::currentMSecsSinceEpoch();
          for(int i = 0; i<nbIterations; ++i){
              sum = 0;
              QMap<int, int>::const_iterator it = map.constBegin();
              auto end = map.constEnd();
              while (it != end) {
                  sum += it.value();
                  ++it;
              }
          }
          qint64 end2 = QDateTime::currentMSecsSinceEpoch();
          qDebug() << "STL style iterator : \t" << (end2-start2) << " ms";
      
      
          qint64 start3 = QDateTime::currentMSecsSinceEpoch();
          for(int i = 0; i<nbIterations; ++i){
              sum = 0;
              auto end = map.cend();
              for (auto it = map.cbegin(); it != end; ++it)
              {
                  sum += it.value();
              }
          }
          qint64 end3 = QDateTime::currentMSecsSinceEpoch();
      
          qDebug() << "STL style iterator v2 : \t" << (end3-start3) << " ms";
      }
      

      2017 年 7 月编辑:我在我的新笔记本电脑(Qt 5.9、i7-7560U)上再次运行了这段代码,并得到了一些有趣的变化

      Reference vector :   155  ms 
      Reference list :     157  ms
      QMap::values():      1874  ms 
      Java style iterator: 1156  ms 
      STL style iterator:  1143  ms
      

      STL 风格和 Java 风格在这个基准测试中的表现非常相似

      【讨论】:

      • 这是最好的答案,值得被选中。每个人都可以在他们的系统上轻松测试您说的是真话。我的值与您的值非常相似,我想知道为什么 stl 迭代器几乎快两倍。 Qt 文档误导性地指出“[Java 风格的迭代器] 它们比 STL 风格的迭代器使用起来更方便,代价是效率稍低。”。 100% 略高于 imo
      • 感谢您的友好评论 =)
      • @Fezvez 我已在您的代码中添加了 const 迭代器,令人惊讶的是,与非成本迭代器相比,const 迭代器的运行时间增加了,后者为每个迭代器访问调用分离 gist.github.com/ejahandar/3b5db3a7f3d1e746103671ef003d4fe5
      • 这确实很令人惊讶,我不知道为什么会这样
      • 最佳答案,但我更喜欢以下语法:for (auto it = map.constBegin(), end = map.constEnd(); it != end; ++it)
      【解决方案4】:

      QMap::iterator 使用 key() 和 value() - 可以在 documentation for Qt 4.8 或 documentation for Qt-5 中轻松找到。

      编辑:

      基于范围的 for 循环会生成与此类似的代码(请参阅 CPP reference):

      {
          for (auto __begin = extensions.begin(), __end = extensions.end();
                  __begin != __end; ++__begin) {
              auto e = *__begin; // <--- this is QMap::iterator::operator*()
              fout << e.first << "," << e.second << '\n';
          }
      } 
      

      QMap::iterator::iterator*() 等价于 QMap::iterator::value(),并且不给出对。

      最好的写法是不使用基于范围的 for 循环:

      auto end = extensions.cend();
      for (auto it = extensions.cbegin(); it != end; ++it)
      {
          std::cout << qPrintable(it.key()) << "," << qPrintable(it.value());
      }
      

      【讨论】:

        【解决方案5】:

        在“旧”C++ 中,使用 Qt,您可以这样做:

        QMap< QString, whatever > extensions;
        //...
        foreach( QString key, extensions.keys() )
        {
            fout << key << "," << extensions.value( key ) << '\n';
        }
        

        我这里没有 C++11 编译器,但也许以下方法可以工作:

        for( auto key: extensions.keys() )
        {
            fout << key << "," << extensions.value( key ) << '\n';
        }
        

        您也可以使用迭代器,如果您更喜欢使用它们,请查看 hmuelners 链接

        【讨论】:

        • 感谢您参考在“旧”C++ 中执行此操作。我们中的一些人被困在狗窝里了;-)
        • 我怀疑这是最有效的“旧”方式。更有可能:for(QMap&lt;QString, whatever&gt;::iterator it = map.begin(); it != map.end(); it++) {... }
        • 您的方式可能稍微高效一些,尽管我的方式更容易编写和阅读。此外,在较大的程序中,优化文件、UI 和数据库操作通常比优化循环更有效..
        【解决方案6】:

        KDAB 的 Ivan Čukić 有一个 blog post,它解释了如何在不复制容器的情况下使用 C++17 structured bindings 迭代 QMap:

        template <typename T>
        class asKeyValueRange
        {
        public:
            asKeyValueRange(T &data) : m_data{data} {}
        
            auto begin() { return m_data.keyValueBegin(); }
        
            auto end() { return m_data.keyValueEnd(); }
        
        private:
            T &m_data;
        };
        
        ...
        
        QMap<QString, QString> extensions;
        
        for (auto [key, value]: asKeyValueRange(extensions))
        {
            fout << key << ',' << value << '\n';
        } 
        

        【讨论】:

        • 这很好,只要地图不是临时值,在这种情况下它会生成一个错误的segfault
        【解决方案7】:

        从 Qt 5.10 开始,您可以使用简单的包装类来使用基于范围的 for 循环,但仍然可以访问映射条目的键和值。

        将以下代码放在源文件顶部或包含的标题中:

        template<class K,class V>
        struct QMapWrapper {
            const QMap<K,V> map;
            QMapWrapper(const QMap<K,V>& map) : map(map) {}
            auto begin() { return map.keyValueBegin(); }
            auto end()   { return map.keyValueEnd();   }
        };
        

        要遍历所有条目,您可以简单地编写:

        QMap<QString, QString> extensions;
        //.. 
        
        for(auto e : QMapWrapper(extensions))
        {
          fout << e.first << "," << e.second << '\n';
        }
        

        e 的类型将是 std::pair&lt;const QString&amp;, const QString&amp;&gt;,正如在 QKeyValueIterator documentation 中部分指定的那样。

        成员变量map 是映射的隐式共享副本,以避免与temporary values 一起使用时出现分段错误。因此,只要您不在循环内修改地图,这只会有很小的固定开销。


        上面的例子使用了class template argument deduction,它是在C++17中引入的。如果您使用的是旧标准,则必须在调用构造函数时指定 QMapWrapper 的模板参数。在这种情况下,工厂方法可能有用:

        template<class K,class V>
        QMapWrapper<K,V> wrapQMap(const QMap<K,V>& map) {
            return QMapWrapper<K,V>(map);
        }
            
        

        这被用作:

        for(auto e : wrapQMap(extensions))
        {
          fout << e.first << "," << e.second << '\n';
        }
        

        【讨论】:

        • 这很好,只要地图不是临时值,在这种情况下它会生成一个错误的segfault
        • @kiruahxh 好点。我将成员变量更改为隐式共享副本以避免此问题。
        【解决方案8】:

        我使用了类似的方法来实现我自己的结果。以防万一有人单独需要键和值。

        {
           QMap<int,string> map; 
        
           map.insert(1,"One");
           map.insert(2,"Two");
           map.insert(3,"Three");
           map.insert(4,"Four");   
        
           fout<<"Values in QMap 'map' are:"<<endl;
           foreach(string str,map)
           {
             cout<<str<<endl;
           };
        
        
           fout<<"Keys in QMap 'map' are:"<<endl;
           foreach(int key,map.keys())
           {
             cout<<key<<endl;
           }; 
        }  
        

        【讨论】:

        • 问题是你想在 for(each) 循环中检索某个键的值...
        【解决方案9】:

        另一个方便的方法,来自QMap Docs。它允许显式访问键和值(Java 风格的迭代器):

        QMap<QString, QString> extensions;
        // ... fill extensions
        QMapIterator<QString, QString> i(extensions);
        while (i.hasNext()) {
            i.next();
            qDebug() << i.key() << ": " << i.value();
        }
        

        如果您希望能够覆盖,请改用QMutableMapIterator。

        还有另一种方便的 Qt 方法,如果您只对读取值感兴趣,没有键(使用 Qts foreach 和 c++11):

        QMap<QString, QString> extensions;
        // ... fill extensions
        foreach (const auto& value, extensions)
        {
            // to stuff with value
        }
        

        【讨论】:

        • 我们也需要钥匙!
        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 2020-06-17
        • 1970-01-01
        • 2013-02-20
        • 2011-03-18
        相关资源
        最近更新 更多