Итерация по QMap для
У меня есть объект 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
?
Ответы
Ответ 1
Если вы хотите стиль STL с first
и second
, выполните следующие действия:
for(auto e : extensions.toStdMap())
{
fout << e.first << "," << e.second << '\n';
}
Если вы хотите использовать то, что предлагает Qt, сделайте следующее:
for(auto e : extensions.keys())
{
fout << e << "," << extensions.value(e) << '\n';
}
Ответ 2
С++ 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 ↦
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';
}
Существует еще одна обертка здесь.
Ответ 3
Для людей, заинтересованных в оптимизации, я пробовал несколько подходов, сделал несколько микро-тестов, и я могу заключить, что стиль STL-подхода значительно быстрее.
Я попытался добавить целые числа с этими методами:
- QMap:: значения()
- Итератор стиля 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";
}
Edit July 2017: Я снова запустил этот код на своем новом ноутбуке (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 имеют очень похожие показатели в этом тесте
Ответ 4
В "старом" С++, используя Qt, вы сделали бы это следующим образом:
QMap< QString, whatever > extensions;
//...
foreach( QString key, extensions.keys() )
{
fout << key << "," << extensions.value( key ) << '\n';
}
У меня нет компилятора С++ 11, но, возможно, следующее будет работать:
for( auto key: extensions.keys() )
{
fout << key << "," << extensions.value( key ) << '\n';
}
Вы также можете использовать итераторы, посмотрите ссылку hmuelners, если вы предпочитаете использовать их
Ответ 5
QMap:: iterator использует key() и value(), которые можно легко найти в документации для Qt 4.8 или для Qt-5.
Edit:
Цикл, основанный на диапазоне, генерирует коды, подобные этому (см. ссылка 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() и не дает пары.
Лучший способ записать это без использования диапазона для цикла:
auto end = extensions.cend();
for (auto it = extensions.cbegin(); it != end; ++it)
{
std::cout << qPrintable(it.key()) << "," << qPrintable(it.value());
}
Ответ 6
Я использовал что-то вроде этого, чтобы достичь собственного результата. На всякий случай кому-то нужны ключи и значения отдельно.
{
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;
};
}
Ответ 7
Другим удобным способом, из QMap Docs. Он допускает явный доступ к ключу и значению (итератор Java-Style):
QMap<QString, QString> extensions;
// ... fill extensions
QMapIterator<QString, QString> i(extensions);
while (i.hasNext()) {
i.next();
qDebug() << i.key() << ": " << i.value();
}
Если вы хотите перезаписать, используйте QMutableMapIterator
вместо этого.
Существует еще один удобный метод Qt
, если вы только заинтересованы в чтении значений без ключей (используя Qt
foreach
и С++ 11):
QMap<QString, QString> extensions;
// ... fill extensions
foreach (const auto& value, extensions)
{
// to stuff with value
}