使用 for

问题描述 投票:0回答:11

我有一个

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
11个回答
71
投票

如果您想要带有

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';
}

59
投票

C++11 range-based-for 使用取消引用的迭代器的类型作为自动推导的“光标”类型。这里,它是表达式

*map.begin()
的类型。
并且由于
QMap::iterator::operator*()
返回对值(类型为
QString &
)的引用,因此无法使用该方法访问该键。

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

  • 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';
}

还有另一个包装纸这里


36
投票

对于对优化感兴趣的人,我尝试了几种方法,做了一些微观基准测试,我可以得出结论: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 风格在此基准测试中表现非常相似


21
投票

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

编辑:

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

{
    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());
}

13
投票

在“旧”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 链接


4
投票

从 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<const QString&, const QString&>
,如QKeyValueIterator文档中部分指定的那样。

成员变量

map
是映射的隐式共享副本,以避免与临时值一起使用时出现分段错误。因此,只要您不在循环内修改映射,则只会产生很小的恒定开销。


上面的例子使用了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';
}

4
投票
KDAB 的

Ivan Čukić 有一篇博客文章,解释了如何使用

C++17 结构化绑定
迭代 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';
} 

3
投票

从 Qt 6.4 开始,您可以使用方法

asKeyValueRange()
,如下所示:

for(auto pair : extensions.asKeyValueRange()) {
    pair.first;    // key access
    pair.second;   // value access
}

这甚至适用于奇特的结构化绑定

for(auto& [key, value] : extensions.asKeyValueRange()) {
    fout << key << "," << value << '\n';
}

2
投票

另一种方便的方法,来自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
方法,如果您只对读取值感兴趣,而不需要键(使用
Qt
s
foreach
和 c++11):

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

1
投票

我用了这样的东西来达到我自己的结果。以防万一有人需要单独的键和值。

{
   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;
   }; 
}  

0
投票

您可以将 QMap::keyValueBeginQMap::keyValueEnd (自 Qt 5.10 起)包装到一个范围中,然后使用基于范围的

for
循环来迭代该范围。此代码也适用于 constrvalue(例如临时)对象。

注意:需要 C++17 编译器。(也可以在 C++11 中实现此功能,但此处未完成。)

template<typename T> class KeyValueRange {
private:
    T iterable;
public:
    KeyValueRange(T &iterable) : iterable(iterable) { }
    KeyValueRange(std::remove_reference_t<T> &&iterable) noexcept : iterable(std::move(iterable)) { }
    auto begin() const { return iterable.keyValueBegin(); }
    auto end() const { return iterable.keyValueEnd(); }
};

template <typename T> auto asKeyValueRange(T &iterable) { return KeyValueRange<T &>(iterable); }
template <typename T> auto asKeyValueRange(const T &iterable) { return KeyValueRange<const T &>(iterable); }
template <typename T> auto asKeyValueRange(T &&iterable) noexcept { return KeyValueRange<T>(std::move(iterable)); }

像这样使用它:

for (auto [key, value] : asKeyValueRange(map)) {
  fout << key << "," << value;
}
© www.soinside.com 2019 - 2024. All rights reserved.