10. 数据类和容器

经过前面的章节,我们应该对Qt基本使用和相关机制特性有了一定的了解,相信认真看过例程代码,修改练习过的同学应该可以写出一些简单的界面。

接下来的几章,我们一起学习Qt更复杂的一些类,利用这些类来帮我们实现特定的一些需求,这些属于非UI类, 比如说数据处理,文件存储等等,这些也是一个应用所必须的。

这章主要讲解下Qt中的数据类和容器。

10.1. 数据类型

Qt是基于C++, 因此C++中所有的语法和数据类型在Qt中都是被支持的, 但是Qt中也定义了一些属于自己的数据类型, 接下来将介绍一下这些基础的数据类型。

10.1.1. 基本数据类型

QT基本数据类型定义在 <QtGlobal> 头文件中,QT基本数据类型如下表所示(部分):

类型

数据类型

含义

typedef

QFunctionPointer

这是typedef的类型void (*)(),它指向不带参数且返回void的函数的指针

typedef

QtMessageHandler

指向具有以下签名的函数的指针的typedef:void myMessageHandler((QtMsgType,const QMessageLogContext&,const QString&)

enum

QtMsgType { QtDebugMsg, …}

该枚举描述了可以发送到消息处理程序(QtMessageHandler)的消息

typedef

qint8

signed char 有符号8位数据

typedef

qint16

signed short 16位数据类型

typedef

qint32

signed int 32位有符号数据类型

typedef

qint64

long long int 64位有符号数据类型

typedef

qintptr

指针类型 根据系统类型不同而不同,32位系统为qint32、64位系统为qint64

typedef

qlonglong

long long int

typedef

qptrdiff

根据系统类型不同而不同

typedef

qreal

double

typedef

quint8

unsigned char 无符号8位数据类型

typedef

quint16

unsigned short 无符号16位数据类型

typedef

quint32

unsigned int 无符号32位数据类型

typedef

quint64

unsigned long long

typedef

quintptr

整数类型,用于以无符号整数表示指针(用于哈希等)

typedef

qulonglong

unsigned long long int

typedef

uchar

unsigned char

typedef

uint

unsigned int

typedef

ulong

unsigned long

typedef

ushort

unsigned short

<QtGlobal>中除了定义上面的数据类型,还定义了很多函数和宏(列举一些):

返回值

函数

含义

T

qAbs(const T &t)

绝对值函数

const T &

qMax(const T &a, const T &b)

检索两个给定对象的最大值

const T &

qMin(const T &a, const T &b)

检索两个给定对象的最小值

还有很多宏,这些申明的宏使程序员能够向其应用程序添加编译器或平台特定的代码,而其他宏则是用于较大操作的便捷宏, 比如Q_PROCESSOR_ARM,定义是否为ARM处理器编译应用程序,Qt当前支持可选的ARM版本:Q_PROCESSOR_ARM_V5,Q_PROCESSOR_ARM_V6和Q_PROCESSOR_ARM_V7。

了解更多的宏或者函数参考下:https://doc.qt.io/qt-5/qtglobal.html

10.1.2. 字符串类型

我们先来了解一个概念 隐式共享

例如:我们定义一个str1,再定义一个str2等于str1。 定义这两个变量的时候,计算机内部会对这两个变量值进行存储,由于str1比较大,str2和str1内容相同, 就会造成资源的浪费。

string str1("hellohellohellohellohellohellohellohellohellohellohellohellohellohello");
string str2 = str1;

于是Qt中就提供了一种隐士共享,共享类由指向共享数据块的指针组成,该数据块包含引用计数和数据。

QString str1("hellohellohellohellohellohellohellohellohellohellohellohellohellohello");
QString str2 = str1;

当我们用str2 = str1,operator=() 就会进行隐式共享对象分配,这时计算机内部就不会再完全重新开辟一个空间来存储str2, 而是进行浅层拷贝,也就是只拷贝一个指针,这个指针指向str1的数据,此时str1和str2就共用一个数据块。 浅层拷贝的时候,引用计数会自加,而当对象取消引用共享数据时,引用计数就减少。当参考计数变为零时,共享数据将被删除。

当我们需要对str2进行修改的时候,直接修改是不可行的,这个时候我们就需要将对象与共享块分离,通常称为写时复制(深层拷贝)。

共享的好处是程序不需要不必要地复制数据,从而减少了内存使用量并减少了数据复制。可以轻松分配对象,将其作为函数参数发送以及从函数返回。 隐式共享主要发生在幕后。目前我们只需要了解这个概念,以及明白我们下面讲到的类都是隐式共享的可以了。

10.1.2.1. QBitArray

QBitArray是一个数组,它可以访问单个位并提供可在整个位数组上工作的运算符(AND,OR,XOR和NOT)。

下面的代码构造一个QBitArray,其中包含200位初始化为false(0)的位:

QBitArray bit(200);
qDebug()<<bit;

QBitArray使用基于0的索引,就像C++数组一样,可以使用 operator[] 访问特定索引位置的位:

bit.resize(3);
bit[0] = true;
bit[1] = false;
bit[2] = true;
qDebug()<<bit;

同样使用 testBit() 和 setBit() 访问QBitArray中的位比使用 operator[] 效率更高:

bit.setBit(0, false);
bit.setBit(1, true);
bit.setBit(2, false);
qDebug()<<bit;

QBitArray支持位运算,如下面的示例:

QBitArray bit_x(5);
bit_x.setBit(3, true);// x: [ 0, 0, 0, 1, 0 ]

QBitArray bit_y(5);
bit_y.setBit(4, true);// y: [ 0, 0, 0, 0, 1 ]

bit_x |= bit_y;// x: [ 0, 0, 0, 1, 1 ]
qDebug()<<bit_x<<bit_y;
bit_x &= bit_y;// x: [ 0, 0, 0, 0, 1 ]
qDebug()<<bit_x<<bit_y;

QBitArray区分数组null和empty;分别通过isNull()和isEmpty()来判断

  • isNull() 如果此位数组为null,则返回true;否则返回false;

  • isEmpty() 此位数组的大小是否为0,是返回true;否则返回false。

qDebug()<<QBitArray().isNull();
qDebug()<<QBitArray().isEmpty();
qDebug()<<QBitArray(3).isNull();
qDebug()<<QBitArray(3).isEmpty();

10.1.2.2. QByteArray

QByteArray可用于存储原始字节(包括’0’和传统的以’0’结尾的8位字符串)。使用QByteArray比使用const char*更为方便。 QByteArray在主要以下两种情况中使用:需要存储原始二进制数据时和在内存保护至关重要时(例如,对于嵌入式Linux使用Qt)。

初始化QByteArray的一种方法就是将一个 const char * 传递给其构造函数。

QByteArray byte("Hello");
qDebug()<<byte.data();
qDebug()<<byte.size();

byte大小为5,数据为 “Hello”,数组byte末尾还保留了一个额外的 “0” 字符。

resize()可以重新设置数组的大小,并按字节数重新初始化数据字节。 QByteArray使用基于0的索引,就像C ++数组一样,可以使用 operator[] 访问特定索引位置的字节,

如果只读,我们也可以使用at()访问指定的字节,left()、right()或mid()则可以一次读取多个字节:

byte.resize(5);
byte[0] = 0x3c;
byte[1] = 0xb8;
byte[2] = 0x64;
byte[3] = 0x18;
byte[4] = 0xca;

qDebug()<<byte;
qDebug()<<byte[3];
qDebug()<<byte[4];

for (int i = 0; i < byte.size();i++) {
    qDebug()<<byte.at(i);
}

qDebug()<<byte.right(2);
qDebug()<<byte.left(2);
qDebug()<<byte.mid(2,2);

QByteArray可以包含 “0”, rize()函数总会计算”0”,但是date()和constData()返回结果总是以”0”作为结束标志:

qDebug()<<byte.right(2);
qDebug()<<byte.left(2);
qDebug()<<byte.mid(2,2);

QByteArray byte1("ca\0r\0t");
qDebug()<<byte1.size();                     // Returns 2.
qDebug()<<byte1.data();
qDebug()<<byte1.constData();                // Returns "ca" with terminating \0.

QByteArray byte2("ca\0r\0t", 3);
qDebug()<<byte2.size();                     // Returns 3.
qDebug()<<byte2.constData();                // Returns "ca\0" with terminating \0.

QByteArray byte3("ca\0r\0t", 4);
qDebug()<<byte3.size();                     // Returns 4.
qDebug()<<byte3.constData();                // Returns "ca\0r" with terminating \0.
for(int i=0; i<byte3.size();i++)
{
    qDebug()<<byte3.at(i);
}

const char cart[] = {'c', 'a', '\0', 'r', '\0', 't'};
QByteArray byte4(QByteArray::fromRawData(cart, 6));
qDebug()<<byte4.size();                     // Returns 6.
qDebug()<<byte4.constData();                // Returns "ca\0r\0t" without terminating \0.

QByteArray提供以下用于修改字节数据的基本功能:追加append(),前置添加prepend(),插入insert(),替代replace()和移除remove()。 如下面的示例,分别输出 androck androck and rockrock & rock

QByteArray byte5("and");
qDebug()<<byte5;
byte5.prepend("rock ");
qDebug()<<byte5;
byte5.append(" roll");
qDebug()<<byte5;
byte5.replace(5, 3, "&");
qDebug()<<byte5;

indexOf()或lastIndexOf()提供查找QByteArray中所有出现的特定字符或子字符串,返回字符或子字符串的索引位置(如果找到的话)。 contains()用于检查QByteArray是否包含特定字符或子字符串,count()则可以用来统计特定字符或子字符串在字节数组中出现的次数。

QByteArray byte6("We must be <b>bold</b>, very <b>bold</b>");
int j = 0;
while ((j = byte6.indexOf("<b>", j)) != -1) {
    qDebug() << "Found <b> tag at index position " << j;
    ++j;
}

if(byte6.contains("b"))
{
    qDebug()<<byte6.count("b");
}

QByteArray还支持数据类型转换以及大小写转换:

QByteArray byte7("0f");
bool ok;
qDebug()<<byte7.toInt(&ok,10);
qDebug()<<byte7.toInt(&ok,16);
qDebug()<<byte7.toLong(&ok,10);
qDebug()<<byte7.toUpper();

10.1.2.3. QChar

QString字符串中的字符都是QChar类型的,QChar也是一个类,使用UTF-16编码表示字符。 QChar的主要接口函数参考下:https://doc.qt.io/qt-5/qchar.html,就不一一介绍。

10.1.2.4. QString

除了QByteArray,Qt还提供了QString类来存储字符串数据。大多数时候我们都使用QString。 它存储16位Unicode字符,可以轻松地在应用程序中存储US-ASCII(ANSI X3.4-1986)和Latin-1(ISO 8859-1)。

QString使用也包含QByteArray中部分方法:

QString str = "Hello";
static const QChar data[4] = {0x0055, 0x006e, 0x10e3, 0x03a3};
QString str1(data, 4);

QString str2;
str2.resize(4);

str[0] = QChar('U');
str[1] = QChar('n');
str[2] = QChar(0x10e3);
str[3] = QChar(0x03a3);

QString str3="asfcfrvfbgkiocmewiocmiq";
for (int i = 0; i<str.size(); i++)
{
    if (str3.at(i) >= QChar('a')  && str3.at(i) <= QChar('f'))
    qDebug()  << "Found character in range [a-f]:"<<str3.at(i);
}

// 比较两个字符串是否相等
QString str4="extern";
if (str4 == "auto")
    qDebug()<<"str is auto";
else if(str4 == "extern")
    qDebug()<<"str is extern";
else
    qDebug()<<str4;

// 字符串的替换,添加等
QString str5("and");
qDebug()<<str5;
str5.prepend("rock ");
qDebug()<<str5;
str5.append(" roll");
qDebug()<<str5;
str5.replace(5, 3, "&");
qDebug()<<str5;

// 字符串的截取等
qDebug()<<str5.toUpper();
qDebug()<<str5.toLower();
qDebug()<<str5.count("l");
qDebug()<<str5.length();
qDebug()<<str5.left(5).size();
qDebug()<<str5.mid(2,8);
qDebug()<<str5.append(str5);

QString str6 = "We must be <b>bold</b>, very <b>bold</b>";
j=0;
while ((j = str6.indexOf("<b>", j)) != -1) {
    qDebug()  << "Found <b> tag at index position:"<<j;
    ++j;
}

字符串和数值之间的转换:

QString str7 = "56.5";
qDebug()<<str7.toInt();
qDebug()<<str7.toDouble();
qDebug()<<str7.toFloat();
qDebug()<<str7.toLong();
qDebug()<<QString::number(53);
qDebug()<<QString::number(4.4156);
qDebug()<<QString::number(4.4156,'f',2);

QString str8 = "F0F0";
bool OK;
int val = str8.toInt(&OK,16);
qDebug()<<val;
qDebug()<<QString::number(val,2);
qDebug()<<QString::number(val,8);
qDebug()<<QString::number(val,10);
qDebug()<<QString::number(val,16);

8位字符串和Unicode字符串之间转换,以QByteArray的形式返回字符串

qDebug()<<str8.toLocal8Bit();
qDebug()<<str8.toLatin1();
qDebug()<<str8.toUtf8();
  • toLatin1()返回Latin-1(ISO 8859-1)编码的8位字符串。

  • toUtf8()返回UTF-8编码的8位字符串。UTF-8是US-ASCII(ANSI X3.4-1986)的超集,它通过多字节序列支持整个Unicode字符集。

  • toLocal8Bit()使用系统的本地编码返回8位字符串。

QString也提供了fromLatin1(),fromUtf8()和fromLocal8Bit()对应的转换函数。

字符串的拆分和拼接,除了前面提到的append(),prepend(),insert(),replace()和remove(),indexOf()或lastIndexOf()以及trimped()和simplified()。 常见的操作还有如下方式:

QString str9 = "To get an upper - or lowercase version of a string use toUpper() or toLower().";
QStringList list= str9.split(" ");
int count=0;
for(int i=0; i<list.count();i++)
{
    qDebug()<<list.at(i);
    if(list.at(i)== "or")
    {
        qDebug()<< "'or' times:"<<++count;
    }
}

QString name ="xiao fang";
int age=22;
qDebug()<<QString("%1 is %2 years old").arg(name).arg(age);

字符串的查询和比较包含,startsWith()或endssWith(),contains(),count(), 以及重载的操作符operator <(),operator <=(),operator ==(),operator> =(),operator +()等等。

10.1.2.5. QStringList

QStringList继承自QList <QString>,提供字符串列表,QList的所有功能也适用于QStringList。 例如可以使用isEmpty()测试列表是否为空,并且可以调用诸如append(),prepend(),insert(),replace(),removeAll(),removeAt(),removeFirst(), removeLast()和removeOne()之类的函数来修改QStringList。此外,QStringList提供了一些便利功能,这些功能使处理字符串列表更加容易:

QStringList默认构造函数会创建一个空列表。我们可以使用initializer-list构造函数创建包含元素的列表, 并通过insert() append()或者重载的 operator + =()和 operator <<()将字符串添加到列表中:

QStringList fonts = { "Arial", "Helvetica", "Times" };
fonts << "Courier" << "Verdana";
fonts += "chinese";

要遍历QStringList,可以使用索引位置或QList的Java样式和STL样式的迭代器类型:

for (int i = 0; i < fonts.size(); ++i)
qDebug()<< fonts.at(i).toLocal8Bit().constData();

QStringListIterator javaStyleIterator(fonts);
while (javaStyleIterator.hasNext())
qDebug() << javaStyleIterator.next().toLocal8Bit().constData();
QStringList::const_iterator constIterator;
for (constIterator = fonts.constBegin(); constIterator != fonts.constEnd();++constIterator)
qDebug() << (*constIterator).toLocal8Bit().constData();

QStringList提供了几个函数,使您可以操纵列表的内容。您可以使用join()函数将字符串列表中的所有字符串连接为单个字符串(带有可选的分隔符), 也可以使用split()要将字符串分解为字符串列表,split的参数可以是单个字符,字符串,QRegularExpression(正则表达式)。 QString列表还提供了filter()函数,该函数使您可以提取一个新列表,该列表仅包含那些包含特定子字符串(或与特定正则表达式匹配)的字符串:

QString str_list = fonts.join(", ");// str == "Arial, Helvetica, Times, Courier"
qDebug() <<  str_list;
QStringList list_font = str_list.split(',');// list: ["Arial", "Helvetica", "Times", "Courier"]
for (int i = 0; i < list_font.size(); ++i)
    qDebug() <<  list_font.at(i);

QStringList monospacedFonts = fonts.filter(QRegularExpression("Courier|Fixed"));

contains()函数告诉你的列表中是否包含给定的字符串,而的indexOf()函数和lastIndexOf()会返回给定字符串中第一次(最后一次)出现的索引。 replaceInStrings()函数依次对字符串列表中的每个字符串调用QString :: replace。

QStringList files;
files << "$QTDIR/src/moc/moc.y"
    << "$QTDIR/src/moc/moc.l"
    << "$QTDIR/include/qconfig.h";
for (int i = 0; i < files.size(); ++i)
    qDebug() <<  files.at(i);
files.replaceInStrings("$QTDIR", "/usr/lib/qt");
for (int i = 0; i < files.size(); ++i)
    qDebug() <<  files.at(i);

10.2. 容器

Qt库提供了一组基于通用模板的容器类,这些类可用于存储指定类型的数据项,例如,如果您需要一个可调整大小的QString数组,请使用QVector <QString>。

Qt中这些容器类的设计比STL(standard template library)容器更轻,更安全且更易于使用。如果您不熟悉STL,或者更喜欢以Qt方式进行操作,则可以使用这些类而不是STL类。 容器类也都是 隐式共享 的,它们是可重入的,并且已针对速度,低内存消耗和最小的内联代码扩展进行了优化,从而生成了较小的可执行文件。 此外,在所有用于访问它们的线程都将它们用作只读容器的情况下,它们是线程安全的。

容器类是基于模板的类,例如常用的QList <T>,T是一个具体的类,可以是int,float等简单类,也可以是QString、QDate类。T必须是一个可赋值的类型, 即T必须提供一个默认构造函数,一个可复制的构造函数和一个赋值运算符。

Qt的容器分为关联容器类和关联容器类,顺序容器有:QList,QLinkedList,QVector,QStack和QQueue。简单介绍:

  • QList是最适合使用的类型。尽管它是作为数组列表实现的,但它提供了非常快的前置和追加操作。

  • 如果您确实需要一个链表,请使用QLinkedList。

  • 如果希望项目占据连续的内存位置,请使用QVector。

  • QStack和QQueue是提供LIFO和FIFO语义的便捷类。

关联容器有:QMap,QMultiMap,QHash,QMultiHash和QSet。 “多”容器方便地支持与单个键关联的多个值。“散列”容器通过使用散列函数而不是对排序集进行二进制搜索来提供更快的查找。

要遍历存储在容器中的项目,Qt中提供了两种类型的迭代器(iterator):Java样式的迭代器和STL样式的迭代器。 Java样式的迭代器更易于使用并提供高级功能,而STL样式的迭代器效率更高,并且可以与Qt和STL的通用算法一起使用。 Qt还提供了一个foreach关键字,用于遍历容器内的所有项。 .. 作为特殊情况,QCache和QContiguousCache类在有限的缓存存储中提供了对象的有效哈希查找

关于容器更多的描述参考下Qt官方文档:https://doc.qt.io/qt-5/containers.html

10.2.1. QList <T>

QList 类是最常用的容器类,它用连续的空间存储存储一个列表数据,可以通过索引访问的给定类型(T)的值。

在内部,QList是使用数组实现的,从而确保基于索引的访问非常快。 可以使用QList::append()和QList::prepend()将项目添加到列表的任一端,也可以使用QList :: insert()将它们插入到中间。 与其他任何容器类相比,QList经过了高度优化,可以在可执行文件中扩展为尽可能少的代码。 前面提到的QStringList继承自QList <QString>。

// QList<T> 定义一个元素类型是T的列表,定义时可以初始化列表数据和大小

// 创建一个整数列表:
QList<int> integerList ={1,2,3,4,5,6,7,8,9};

// 创建一个字符列表:
QList<QChar> dateList;

// 创建一个字符串列表
QList<QString> list = {"one", "two", "three"};

// 将字符串 "four" 和 "five" 添加到字符串列表list末尾
list << "four" << "five";

// 在字符串列表尾部添加元素
list.append("six");

// 在字符串列表头部添加元素
list.prepend("zero");

// 如果字符串列表list的第一个元素是 "one",将其改为 "Robert"
if (list[0] == "one")
    list[0] = "Robert";

// 遍历字符串列表list,打印输出
for (int i = 0; i< list.size(); ++i) {
        qDebug() << list.at(i);;
}

// 获取 "three" 在字符串列表list中第一次出现的位置
int index = list.indexOf("three");
if (index != -1)
   qDebug() << "第一次出现three的位置: " << index;

10.2.2. QVector <T>

QVector 类是提供动态数组的模板类,它将给定类型的值的数组存储在内存中的相邻位置,并提供基于索引的快速访问。 与其他容器相比,QVector 在随机访问元素时具有出色的性能,同时在尾部添加和删除元素时仍能保持较高的效率。

// 创建一个整数向量,可以看成整型数组
QVector<int> integerVector={1,2,3,4,5,6,7,8,9};

// 创建一个长度为10的字符串向量
QVector<QString> vector1(10);
// 创建一个长度为10的字符串向量,初始值为"Pass"
QVector<QString> vector2(10, "Pass");

// 创建一个字符串向量vector,如果向量的第一个元素是"zero",将其改为"one"
QVector<QString> vector = {"zero", "two", "three"};
if (vector[0] == "zero")
    vector[0] = "one";

// 遍历向量
for (int i = 0; i< vector.size();++i) {
        qDebug() << vector.at(i);
}

// 获取"two"第一次出现的位置
int index = vector.indexOf("two");
if (index != -1)
    qDebug() << "第一次出现two的位置是: " << index;

10.2.3. QVarLengthArray <T,Prealloc>

QVarLengthArray 提供了一个低级别的可变长度数组, 在速度特别重要的地方,可以使用它代替QVector。 C++语言不支持栈上的可变长度数组,但是我们可以使用下面的办法在堆上实现:

// 定义一个函数,输入参数为一个整数n
int myfunc(int n)
{
    // 动态分配一个大小为n+1的整型数组,存储在table指针中
    int *table = new int[n + 1];

    // 其他操作...

    // 从table数组中获取n位置的元素,存储在ret变量中
    int ret = table[n];

    // 释放table指针所指向的内存空间,返回ret
    delete[] table;
    return ret;
}

但是,如果从应用程序的内部循环非常频繁地调用myfunc(),则堆分配可能是减慢速度的主要来源。 为此,Qt添加QVarLengthArray类型来实现可分配的数据类型。初始化QVarLengthArray时,会分配一系列栈空间,如果我们重新设置数组的长度,它将自动使用堆替换。 我们可以使用下面程序:

// 定义一个函数,输入参数为一个整数n
int myfunc(int n)
{
    // 使用QVarLengthArray来创建一个大小为n+1的整型数组,最大容量为1024
    QVarLengthArray<int, 1024> array(n + 1);

    // 其他操作...

    // 返回数组中第n个元素的值
    return array[n];
}

QVarLengthArray与QVector一样,提供了可调整大小的数组数据结构。这两类之间的主要区别是:

  • QVarLengthArray的API更底层,并且缺少QVector的某些功能。

  • 如果值是基本类型,则QVarLengthArray不会初始化内存。(QVector会初始化)

  • QVarLengthArray不提供隐式共享,这减少了开销,因此该类通常会产生稍微更好的性能,尤其是在紧密循环中。

10.2.4. QStack <T>

QStack 是一种基于栈(Stack)的高级数据结构,QStack容器是一种后进先出(LIFO)的数据结构,即最后一个进入堆栈的元素将最先被移除。

堆栈是后进先出(LIFO)结构,使用push()将项目添加到堆栈的顶部,并使用pop()从顶部检索项目。 在top()函数可以访问到的最上方项目而不删除。

// 创建一个整数类型的栈(QStack)对象,名为stack
QStack<int> stack;

// 使用push()压入元素
stack.push(1);
stack.push(2);
stack.push(3);

// 使用top()查看栈顶元素
qDebug() << "Top element:" << stack.top();

// 使用size()查看堆栈大小
qDebug() << "Stack size:" << stack.size();

// 使用pop()移除并返回栈顶元素
int poppedValue = stack.pop();
qDebug() << "Popped value:" << poppedValue;

// 使用isEmpty()检查堆栈是否为空
qDebug() << "堆栈是否空的:" << (stack.isEmpty() ? "Yes" : "No");

// 使用operator<<压入元素
stack << 4 << 5;

// 当栈不为空时执行循环,输出并弹出栈顶的元素
while (!stack.isEmpty())
   qDebug() << stack.pop();

qDebug() << "堆栈是否空的:" << (stack.isEmpty() ? "Yes" : "No");

QStack继承自QVector,QVector的所有功能也适用于QStack,QStack提供push(),pop()和top()三个方便的功能,可轻松实现LIFO语义。

10.2.5. QQueue <T>

QQueue 类是提供队列的通用容器, 队列是先进先出(FIFO)结构。

使用enqueue()将项目添加到队列的尾部,并使用dequeue()从项目头检索项目, head()函数提供了访问头项目而不删除数据。QQueue继承自QList,因此可以方便地利用QList的功能实现队列的基本操作。

// 创建一个空的int型 QQueue对象,名为queue
QQueue<int> queue;

// 入队
queue.enqueue(1);
queue.enqueue(2);
queue.enqueue(3);

// 获取队列大小
qDebug() << "队列大小:" << queue.size();

// 检查队列是否为空
qDebug() << "队列是否为空:" << queue.isEmpty();

// 查看队列的头部元素
qDebug() << "队列的头部元素:" << queue.head();

// 出队元素
int firstElement = queue.dequeue();
qDebug() << "出队元素:" << firstElement;

// 获取队列中的第一个和最后一个元素
qDebug() << "First element:" << queue.first();
qDebug() << "Last element:" << queue.last();

// 检查队列是否包含特定值
qDebug() << "检查队列是否包含特定值 3:" << queue.contains(3);

// 出队所有元素
while (!queue.isEmpty())
   qDebug() << queue.dequeue();

QQueue提供enqueue(),dequeue()和head()三个便利功能,可轻松实现FIFO语义。

10.2.6. QSet <T>

QSet 类是是Qt的通用容器类之一,俗称一个集合,提供基于哈希表的集合的模板类, 它以未指定的顺序存储值,并提供非常快速的值查找,在内部QSet <T>被实现为QHash。

// 创建一个字符串类型的集合(QSet)对象,名为set
QSet<QString> set;

// 向集合中插入字符串"one" 、"three"、"seven"
set.insert("one");
set.insert("three");
set.insert("seven");

// 使用流操作符连续插入字符串"twelve"、"fifteen"、"nineteen"到集合中
set  << "twelve" << "fifteen" << "nineteen";

// 判断集合中是否包含字符串"fifteen",如果包含则输出"set contains fifteen"
if (set.contains("fifteen"))
    qDebug()<<"set contains fifteen";

// 使用foreach循环遍历集合中的字符串,并输出其值
foreach (const QString &value, set)
    qDebug()  << value;

10.2.7. QMap <Key,T>

QMap 类是一个模板类,提供基于红黑树的字典。它存储(键,值)对,并提供与键关联的值的快速查找。 通常,每个键都与一个值相关联。QMap以密钥顺序存储其数据;如果顺序无关紧要,则QHash是更快的选择。

QMap和QHash提供了非常相似的功能。不同之处在于:

  • QHash提供比QMap平均更快的查找。

  • 在QHash上进行迭代时,这些项是任意排序的。使用QMap时,项目始终按键排序。

  • QHash的键类型必须提供operator ==()和全局qHash(键)功能。QMap的键类型必须提供operator <()来指定总顺序。

// 创建一个空的QString到int的映射(QMap)
QMap<QString, int> map;
map["one"] = 1;        //在映射中插入或修改键为"one"的元素,其值为1
map["three"] = 3;      //在映射中插入或修改键为"three"的元素,其值为3
map["seven"] = 7;      //在映射中插入或修改键为"seven"的元素,其值为7
map.insert("twelve", 12);  //使用insert()函数在映射中插入或修改键为"twelve"的元素,其值为12

//使用[],从映射中获取键为"thirteen"的元素的值,并输出
int num1 = map["three"];
qDebug()<<num1;

//使用value()从映射中获取键为"thirteen"的元素的值,并输出
int num2 = map.value("three");
qDebug()<<num2;

// 使用contains()函数检查映射中是否存在键为"twelve"的元素。
// 如果存在则执行下面的代码块,将timeout的值设置为映射中键为"twelve"的元素的值
int timeout = 30;
if (map.contains("twelve"))
    timeout = map.value("twelve");
qDebug()<< "timeout = " << timeout;

// 创建一个QMapIterator对象mapIterator_java,用于遍历映射map,输出所有元素的键和值。
QMapIterator<QString, int> mapIterator_java(map);
while (mapIterator_java.hasNext()) {
    mapIterator_java.next();
    qDebug() << mapIterator_java.key() << ": " << mapIterator_java.value();
}

// 创建一个const_iterator常量迭代器,用也于遍历映射map
QMap<QString, int>::const_iterator mapIterator_stl = map.constBegin();
while (mapIterator_stl != map.constEnd()) {
    qDebug() << mapIterator_stl.key() << ": " << mapIterator_stl.value();
    ++mapIterator_stl;
}

// 创建一个迭代器i,用于遍历映射map,找到three元素为止
QMap<QString, int>::iterator i = map.find("three");
while (i != map.end() && i.key() == "three") {
    qDebug() << i.value();
    ++i;
}

// 使用foreach循环遍历映射中的每个值
foreach (int value, map)
    qDebug() << value;

10.2.8. QMultiMap <Key,T>

QMultiMap 继承自QMap并对其功能进行了扩展,使其能够存储多个值, 多值映射是一种允许使用同一键的多个值的映射。

// 声明三个QMultiMap类型的变量,QMultiMap是一种可以存储键值对的容器,并且可以存储重复的键。
QMultiMap<QString, int> map1, map2, map3;

map1.insert("plenty", 100);    // 在map1中插入键为"plenty",值为100的元素
map1.insert("plenty", 2000);   // 在map1中再次插入键为"plenty",值为2000的元素,这不会覆盖之前的元素,而是在相同键下插入新的元素
qDebug()<<map1.size();

map2.insert("plenty", 5000);  // 在map2中插入键为"plenty",值为5000的元素。
qDebug()<<map2.size();

// 使用QMultiMap的operator+将map1和map2合并,结果存储在map3中
// 如果两个map中存在相同的键,那么在map3中该键对应的值将是两个map中该键对应的所有值的集合
map3 = map1 + map2;
qDebug()<<map3.size();

// 获取在map3中键为"plenty"的所有值,并将这些值存储到列表中
// 然后遍历valueslist,并输出其中的每个值
QList<int> valueslist = map3.values("plenty");
for (int i = 0; i< valueslist.size(); ++i)
    qDebug() << valueslist.at(i);

10.2.9. QHash <Key,T>

QHash 类是提供基于散列表的字典的模板类,它具有与QMap几乎相同的API, 它存储(键,值)对,并提供与键关联的值的非常快速的查找。

QHash<QString, int> hash;
hash["one"] = 1;
hash["three"] = 3;
hash["seven"] = 7;
hash.insert("twelve", 12);
qDebug() << hash["thirteen"];
qDebug() << hash.value("thirteen");

if (hash.contains("twelve"))
    qDebug() << hash.value("twelve");

qDebug() << hash.value("twelve", 30);

QHashIterator<QString, int> hashIterator_java(hash);
while (hashIterator_java.hasNext()) {
    hashIterator_java.next();
    qDebug() << hashIterator_java.key() << ": " << hashIterator_java.value();
}

QHash<QString, int>::const_iterator hashIterator_stl = hash.constBegin();
while (hashIterator_stl != hash.constEnd()) {
    qDebug() << hashIterator_stl.key() << ": " << hashIterator_stl.value();
    ++hashIterator_stl;
}

foreach (int value, hash)
    qDebug() << value;

操作和QMap几乎相同。

10.2.10. QMultiHash <Key,T>

QMultiHash 类是方便的QHash子类,它提供多值哈希,其操作和QMultiMap类似。

10.3. 迭代器

迭代器提供了一种统一的方法来访问容器中的项目。Qt的容器类提供了两种类型的迭代器:Java样式的迭代器和STL样式的迭代器。 当容器中的数据由于对非const成员函数的调用而被修改或与隐式共享副本分离时,两种类型的迭代器均无效。

10.3.1. Java类型的迭代器

Java类型的迭代器是Qt 4中的新增功能,并且是Qt应用程序中使用的标准迭代器。 它们比STL样式的迭代器更方便使用,但代价是效率略低,其API以Java的迭代器类为模型。

对于每个容器类,都有两种Java类型的迭代器数据类型:一种提供只读访问,另一种提供读写访问。

容器类

只读迭代器

读写迭代器

QList <T>,QQueue <T>

QListIterator <T>

QMutableListIterator <T>

QLinkedList <T>

QLinkedListIterator <T>

QMutableLinkedListIterator <T>

QVector <T>,QStack <T>

QVectorIterator <T>

QMutableVectorIterator <T>

QSet <T>

QSetIterator <T>

QMutableSetIterator <T>

QMap <Key,T>,QMultiMap <Key,T>

QMapIterator <Key,T>

QMutableMapIterator <Key,T>

QHash <Key,T>,QMultiHash <Key,T>

QHashIterator <Key,T>

QMutableHashIterator <Key,T>

与STL风格的迭代器相比Java风格的迭代器点之间的项目,而不是直接在项目。 因此,它们要么指向容器的最开始(在第一个项目之前),要么指向容器的最末端(在最后一个项目之后),或者指向两个项目之间。 下图将有效的迭代器位置显示为包含四个项目的列表的红色箭头:

iterators001

例如我们使用迭代器遍历QList < QString >的所有元素:

QList<QString> listiterator;
listiterator << "A" << "B" << "C" << "D";

//向后迭代
QListIterator<QString> listiterator_java1(listiterator);
while (listiterator_java1.hasNext())
    qDebug() << listiterator_java1.next();

//向前迭代
QListIterator<QString> listiterator_java2(listiterator);
listiterator_java2.toBack();
while (listiterator_java2.hasPrevious())
    qDebug() << listiterator_java2.previous();

它的工作方式如下: 迭代的QList传递给QListIterator构造函数。此时,迭代器listiterator_java1位于列表中第一个项目的前面(项目“ A”之前)。 然后我们调用hasNext()来检查迭代器之后是否有一个项目。如果存在,我们调用next()跳过该项目。next()函数返回它跳过的项目。

对于第二个迭代器listiterator_java2,首先调用toBack()将迭代器移动到列表中的最后一项之后,然后再调用previous去检查前一个项目。

next()和previous()的示意如下图:

iterators002

QListIterator常用的api接口:

函数

作用

toFront()

将迭代器移到列表的最前面(在第一项之前)

toBack()

将迭代器移到列表的末尾(最后一项之后)

hasNext()

返回true如果迭代器是不是在名单的后面

next()

返回下一项并将迭代器前进一个位置

peekNext()

返回下一项而不移动迭代器

hasPrevious()

true如果迭代器不在列表的最前面,则返回

previous()

返回上一个项目,并将迭代器后退一个位置

peekPrevious()

返回上一项而不移动迭代器

QListIterator不提供在迭代时从列表中插入或删除项目的功能,为此,必须使用QMutableListIterator。 下面的示例演示了mutableiterator插入删除项目和对项目值进行更改设置。

 //mutableiterator插入删除项目
 QList<int> mutableiterator;
 mutableiterator << 1 << 2 << 3 << 4;

 // 删除不能被2整除的
 QMutableListIterator<int> mutableiterator_java1(mutableiterator);
 while (mutableiterator_java1.hasNext()) {
     if (mutableiterator_java1.next() % 2 != 0)
         mutableiterator_java1.remove();
 }
 while (mutableiterator_java1.hasPrevious())
     qDebug() << mutableiterator_java1.previous();

 QList<int> mutableiterator1;
 mutableiterator1 << 129 << 1 << 150 << 4;

 // 修改值小于128的项
 QMutableListIterator<int> mutableiterator_java3(mutableiterator1);
 while (mutableiterator_java3.hasNext()) {
     if (mutableiterator_java3.next() < 128)
         mutableiterator_java3.setValue(128);
 }
 while (mutableiterator_java3.hasPrevious())
     qDebug() << mutableiterator_java3.previous();


// 值都乘2
 QMutableListIterator<int> mutableiterator_java4(mutableiterator1);
 while (mutableiterator_java4.hasNext())
     mutableiterator_java4.next() *= 2;

 while (mutableiterator_java4.hasPrevious())
     qDebug() << mutableiterator_java4.previous();

QLinkedList,QVector和QSet的迭代器类具有与QList完全相同的API,QMapIterator略微不同:

// 删除有City
QMutableMapIterator<QString, QString> mutableiterator_java5(mapiterator);
while (mutableiterator_java5.hasNext()) {
    if (mutableiterator_java5.next().key().endsWith("City"))
        mutableiterator_java5.remove();
}
while (mutableiterator_java5.hasPrevious())
    qDebug() << mutableiterator_java5.peekPrevious().key() << ':' << mutableiterator_java5.previous().value();
qDebug() << "";

QMap<int, QWidget *> mapiterator1;
QHash<int, QWidget *> hashiterator;

QMapIterator<int, QWidget *> mapiterator_java5(mapiterator1);
while (mapiterator_java5.hasNext()) {
    mapiterator_java5.next();
    hash.insert(i.key(), i.value());
}

QMapIterator也提供toFront(),toBack(),hasNext(),next(),peekNext(),hasPrevious(),previous()和peekPrevious()。 通过对next(),peekNext(),previous()或peekPrevious()返回的对象调用key()和value()来提取键和值分量。 除此QMapIterator还提供直接在迭代器上操作的key()和value()函数,并返回迭代器跳到其上方的最后一项的键和值。 如果要遍历具有相同值的所有项目,则可以使用findNext()或findPrevious()。

10.3.2. STL类型的迭代器

自Qt 2.0发行以来,已经可以使用 STL类型 的迭代器。它们与Qt和STL的通用算法兼容,并针对速度进行了优化。 对于每个容器类,有两种STL样式的迭代器类型:一种提供只读访问,另一种提供读写访问。 应尽可能使用只读迭代器,因为它们比读写迭代器快。

容器类

只读迭代器

读写迭代器

QList<T>, QQueue<T>

QList<T>::const_iterator

QList<T>::iterator

QLinkedList<T> QLinkedList<T>::const_iterator

QLinkedList<T>::iterator

QVector<T>, QStack<T>

QVector<T>::const_iterator

QVector<T>::iterator

QSet<T>

QSet<T>::const_iterator

QSet<T>::iterator

QMap<Key, T>, QMultiMap<Key, T>

QMap<Key, T>::const_iterator

QMap<Key, T>::iterator

QHash<Key, T>, QMultiHash<Key, T>

QHash<Key, T>::const_iterator

QHash<Key, T>::iterator

STL迭代器的API以数组中的指针为模型,,类似指针的操作和访问。

//遍历,转换成小写
QList<QString> listiteratorstl1;
listiteratorstl1 << "A" << "B" << "C" << "D";
QList<QString>::iterator listindex1;
for (listindex1 = listiteratorstl1.begin(); listindex1 != listiteratorstl1.end(); ++listindex1)
{
    *listindex1 = (*listindex1).toLower();
    qDebug() << *listindex1;
}

//反向迭代器遍历,转换成小写,加reverse的迭代器,是相向的
QList<QString> listiteratorstl2;
listiteratorstl2 << "A" << "B" << "C" << "D";
QList<QString>::reverse_iterator listindex2;
for (listindex2 = listiteratorstl2.rbegin(); listindex2 != listiteratorstl2.rend(); ++listindex2){
    *listindex2 = listindex2->toLower();
    qDebug() << *listindex2;
}

//只读迭代器的遍历
QList<QString>::const_iterator listindex3;
for (listindex3 = listiteratorstl2.constBegin(); listindex3 != listiteratorstl2.constEnd(); ++listindex3)
    qDebug() << *listindex3;

与Java样式的迭代器不同,STL样式的迭代器直接指向项。容器的begin()函数返回一个迭代器,该迭代器指向容器中的第一项。 容器的end()函数将迭代器返回到假想项目,该假想项目位于容器中最后一个项目之后的位置。 end()标记无效位置;绝对不能取消引用,它通常用于循环的中断条件中。 如果列表为空,则begin()等于end(),因此我们从不执行循环。

下图为STL迭代器的示意图:

iterators003

STL样式的迭代器的API:

api

作用

*i

返回当前项目

++i

将迭代器前进到下一个项目

i += n

按n项目推进迭代器

–i

将迭代器后退一个项目

i -= n

将迭代器按n项目后移

i - j

返回迭代器i和之间的项目数j

对于QMap和QHash,”*”运算符返回项目的值组成部分。如果要检索密钥,请在迭代器上调用key()。 为了对称起见,迭代器类型还提供了一个value()函数来检索该值。

QMap<int, int> map;
map.insert(12, 12);
map.insert(13, 13);
map.insert(2000, 2000);
map.insert(5000, 5000);
QMap<int, int>::const_iterator i;
for (i = map.constBegin(); i != map.constEnd(); ++i)
    qDebug() << i.key() << ':' << i.value();

10.3.3. foreach关键字

如果只想按顺序遍历容器中的所有项目,则可以使用Qt的foreach关键字。 关键字是Qt特定于C ++语言的添加,并使用预处理器实现:

QList<QString> strlist = {"one", "two", "three"};
    QString str;
    foreach (str, strlist)
        qDebug() << str;

    foreach (const QString &str, strlist)
        qDebug() << str;

    foreach (const QString &str, strlist) {
        if (str.isEmpty())
            break;
        qDebug() << str;
}

使用QMap和QHash时,将自动foreach访问(key,value)对的value组件,因此不要在容器上调用values()。 如果要遍历键和值,则可以使用迭代器(速度更快),也可以获取键,也可以使用它们来获取值:

//迭代器遍历
QListIterator<QString> strindex(strlist);
while (strindex.hasNext())
    qDebug() << strindex.next();

QMap<QString, int> strmap;
strmap["one"] = 1;
strmap["three"] = 3;
strmap["seven"] = 7;
strmap.insert("twelve", 12);
foreach (const QString &str, strmap.keys())
    qDebug() << str << ':' << strmap.value(str);

QMultiMap<QString, int> strmultimap;
strmultimap.insert("twelve", 12);
strmultimap.insert("plenty", 12);
strmultimap.insert("plenty", 2000);
strmultimap.insert("plenty", 5000);
foreach (const QString &str, strmultimap.uniqueKeys()) {
    foreach (int i, strmultimap.values(str))
        qDebug() << str << ':' << i;
}

10.4. 例程说明

本章节有两个例程:QData和QContainer,在配套例程lubancat_qt_tutorial_code/Containers目录下。

  • DataType演示了Qt中一些基本数据类的使用,

  • QContainer演示了Qt中容器类和迭代器的使用;

10.4.1. 编译构建

  • Ubuntu 20.04上选择 Desktop Qt 5.15.2 GCC 64bit 套件,编译运行;

  • 如果是在LubanCat上运行,选择 Lubancat_rk_debian10 只编译,该编译套件(kits)配置参考下 这里

10.4.2. 运行测试

在PC上直接点击编译并运行程序,结果如下:

result001
result001

在鲁班猫上运行,需要编译出可执行程序,然后通过SCP或者NFS将编译好的程序拷贝到板卡上运行。