Qt—容器类

x33g5p2x  于2022-03-28 转载在 其他  
字(23.1k)|赞(0)|评价(0)|浏览(251)

Qt库提供了一组通用的基于模板的容器类( container classes)。这些容器类可以用来存储指定类型的项目(items),例如,如果需要一个 QString类型的大小可变的数组,那么可以使用QVector< QString>。在《C十+ Primer》一书中作者就强力推荐使用vector类型和迭代器来取代一般的数组,除非vector无法达到必要的速度要求时才使用数组。与STL(Standard Template Library,C++的标准模板库)中的容器类相比,Qt中的这些容器类更轻量、更安全,也更容易使用。如果不熟悉STL或者更喜欢使用Qt way来进行编程,那么就可以使用这些容器类来代替STL的类。本节内容可以在帮助中参考Container Classes关键字。

Qt的容器介绍

Qt提供了一些顺序容器:QList,QLinkedList , QVector , QStack 和QQueue。因为这些容器中的数据都是一个接一个线性存储的,所以称为顺序容器。对于大多数应用程序而言,使用最多而且最好用的是QList,尽管它是一个数组列表,但是可以快速在其头部和尾部进行添加操作。如果需要使用一个链表,那么可以使用QLinkedList ;如果希望数据项可以占用连续的内存空间,那么可以使用QVector。而 QStack 和QQueue分别提供了后进先出(LIFO)和先进先出(FIFO)语义。
Qt还提供了一些关联容器:QMap, QMultiMap,QHash,QMultiHash和 QSet。因为这些容器存储的是<键,值>对,比如QMap<Key,T>,所以称为关联容器。其中,Multi容器用来支持一个键多个值的情况。表对常用的容器类进行了介绍。另外,还有QCache和 QContiguousCache,它们提供了对缓存存储中对象的高效散列查找。

下面对最常用的QList和QMap进行介绍,其他几个容器可以参照这二个进行操作,因为他们的接口函数很相似

新建项目,模板选择Qt控制台应用(Qt Console Application),项目名称为mycontainers。这里只是为了演示容器类的使用,所以没有使用图形界面,这样只需要建立控制台程序就可以了。完成后将main.cpp文件更改如下:

#include <QCoreApplication>
#include <QList>
#include <QDebug>
int main(int argc, char *argv[])
{
    QCoreApplication a(argc, argv);
    QList<QString> list;
    list << "aa" << "bb" << "cc"; // 插入项目
    if(list[1] == "bb") list[1] = "ab";
    list.replace(2, "bc");        // 将“cc”换为“bc”
    qDebug() << "the list is: ";  // 输出整个列表
    for(int i=0; i<list.size(); ++i){
        qDebug() << list.at(i);   // 现在列表为aa ab bc
    }
    list.append("dd");            // 在列表尾部添加
    list.prepend("mm");           // 在列表头部添加
    QString str = list.takeAt(2); // 从列表中删除第3个项目,并获取它
    qDebug() << "at(2) item is: " << str;
    qDebug() << "the list is: ";
    for(int i=0; i<list.size(); ++i)
    {
        qDebug() << list.at(i);   // 现在列表为mm aa bc dd
    }
    list.insert(2, "mm");         // 在位置2插入项目
    list.swap(1,3);               // 交换项目1和项目3
    qDebug() << "the list is: ";
    for(int i=0; i<list.size(); ++i)
    {
        qDebug() << list.at(i);   // 现在列表为mm bc mm aa dd
    }
    qDebug() << "contains 'mm' ?" << list.contains("mm"); // 列表中是否包含“mm”
    qDebug() << "the 'mm' count: " << list.count("mm"); // 包含“mm”的个数
    // 第一个“mm”的位置,默认从位置0开始往前查找,返回第一个匹配的项目的位置
    qDebug() << "the first 'mm' index: " << list.indexOf("mm");
    // 第二个“mm”的位置,我们指定从位置1开始往前查找
    qDebug() << "the second 'mm' index: " << list.indexOf("mm", 1);
    return a.exec();
}

QList是一个模板类,它提供了一个列表。
QList< T>实际上是一个T类型项目的指针数组,所以它支持基于索引的访问,而且当项目的数目小于1000时,可以实现在列表中间进行快速地插入操作。
QList,提供了很多方便的接口函数来操作列表中的项目,例如,插入操作insert()、替换操作replace(),移除操作removeAt(),移动操作move()、交换操作swap() ,在表尾添加项目append()、在表头添加项目 prepend(),移除第一个项目removeFirst(),移除最后一个项目removeLast(),从列表中移除一项并获取这个项目takeAt()及相应的takeFirst()和 takeLast(),获取一个项目的索引in-dexOf(),判断是否含有相应的项目contains()以及获取一个项目出现的次数count()等。
QList可以使用“<<”操作符向列表中插入项目,也可以使用“[]”操作符通过索引来访问一个项目,其中项目是从0开始编号的。不过,对于只读的访问,另一种方法是使用at()函数,它比“[]”操作符要快很多。

程序中使用了一些常用的函数,读者不用一下子把整个程序都写出来再运行,而是写一部分就运行一次并查看结果。

QMap类是一个容器类,它提供了一个基于跳跃列表的字典(a skip-list-based dic-tionary)。QMap<Key,T>是Qt的通用容器类之一,它存储(键,值)对并提供了与键相关的值的快速查找。QMap中提供了很多方便的接口函数,例如,插入操作insert(),获取值 value() ,是否包含一个键contains()、删除一个键remove()、删除一个键并获取该键对应的值take(),清空操作 clear()、插入一键多值insertMulti()等。可以使用“[]”操作符插入一个键值对或者获取一个键的值,不过当使用该操作符获取一个不存在的键的值时,会默认向map中插入该键;为了避免这个情况,可以使用value()函数来获取键的值。当使用value()函数时,如果指定的键不存在,那么默认会返回0,可以在使用该函数时提供参数来更改这个默认返回的值。
QMap默认是一个键对应一个值的,但是也可以使用insertMulti()进行一键多值的插入;对于一键多值的情况,更方便的是使用QMap的子类QMultiMap。
容器也可以嵌套使用,如QMap<QString,QList≤int>>,这里键的类型是QString,而值的类型是QList< int>。注意,后面的“>>”符号之间要有一个空格,不然编译器会将它当作“>>”操作符对待。各种容器存储的值的类型可以是任何的可赋值的数据类型,该类型需要有一个默认的构造函数、一个复制构造函数和一个赋值操作运算符,像基本的类型(如 int和 double)、指针类型、Qt的数据类型(如 QString 和QDate等),但不包括QObject以及QObject的子类(QWidget、QDialog、QTimer等),不过可以存储这些类的指针,如QList<QWidget *>。也可以自定义数据类型,具体方法可以参考Container Classes文档中的相关内容。

遍历容器

遍历一个容器可以使用迭代器(iterators)来完成,迭代器提供了一个统一的方法来访问容器中的项目。Qt的容器类提供了两种类型的迭代器:Java风格迭代器和STL风格迭代器。如果只是想按顺序遍历一个容器中的项目,那么还可以使用Qt的foreach关键字。

Java风格迭代器

Java风格迭代器从Qt 4时被引入,使用上比STL风格迭代器要方便很多,但是在性能上稍微弱于后者。每一个容器类都有两个Java风格迭代器数据类型:一个提供只读访问,一个提供读/写访问,如表所列。

下面将以QList和QMap为例来进行讲解,而QLinkedList、QVector和QSet与QList的迭代器拥有极其相似的接口;类似的,QHash与QMap的迭代器拥有相同的接口。

新建Qt控制台应用,项目名称为myiterators。然后将main. cpp文件更改如下:

#include <QCoreApplication>
#include <QList>
#include <QListIterator>
#include <QMutableListIterator>
#include <QDebug>
int main(int argc, char *argv[])
{
    QCoreApplication a(argc, argv);
    QList<QString> list;
    list << "A" << "B" << "C" << "D";
    QListIterator<QString> i(list); // 创建列表的只读迭代器,将list作为参数
    qDebug() << "the forward is :";
    while (i.hasNext())             // 正向遍历列表,结果为A,B,C,D
        qDebug() << i.next();
    qDebug() << "the backward is :";
    while (i.hasPrevious())         // 反向遍历列表,结果为D,C,B,A
        qDebug() << i.previous();

    return a.exec();
}

这里先创建了一个QList列表list, 然后使用list作为参数创建了列表的只读迭代器。这时,迭代器指向列表的第一个项目的前面(这里是指向项目“A"的前面)。然后使用hasNext()函数来检查该迭代器后面是否还有项目,如果还有项目,那么使用next()来跳过这个项目,next()函数会返回它所跳过的项目。当正向遍历结束后,迭代器会指向列表最后一个项目的后面,这时可以使用hasPrevious( )和previous()来进行反向遍历。

可以看到,Java风格迭代器是指向项目之间的,而不是直接指向项目。所以,迭代器或者指向容器的最前面,或者指向两个项目之间,或者指向容器的最后面。

QListIterator没有提供向列表中插入或者删除项目的函数,要完成这些功能,就必须使QMutableListlterator。这个类增加了insert()函数来完成插入操作,remove( )
函数完成删除操作,setValue()函数完成设置值操作。在前面程序中的“returna.exec();"前添加如下代码:

QMutableListIterator<QString> j(list);
    j.toBack();                                // 返回列表尾部
    while (j.hasPrevious()) {
        QString str = j.previous();
        if(str == "B") j.remove();             // 删除项目“B”
    }
    j.insert("Q");                             // 在列表最前面添加项目“Q”
    j.toBack();
    if(j.hasPrevious()) j.previous() = "N";    // 直接赋值
    j.previous();
    j.setValue("M");                           // 使用setValue()进行赋值
    j.toFront();
    qDebug()<< "the forward is :";
    while (j.hasNext())                        // 正向遍历列表,结果为Q,A,M,N
        qDebug() << j.next();

    return a.exec();

可以使用remove()函数来删除上一-次跳过的项目,使用insert()函数在迭代器指向的位置插入一个项目,这时迭代器会位于添加的项目之后,比如这里添加“Q”后,迭代器指向“Q”和“A”之间。使用QMutableListIterator类的next() 和previous()等函数时会返回列表中项目的一个非const引用,所以可以直接对其赋值;当然也可以使用setValue()函数进行赋值,这个函数是对上一-次跳过的项目进行赋值的。除了这里讲到的这些函数外,还有findNext( )和findPrevious()函数可以用来实现项目的查找。

现在可以运行程序,运行结果已经在上面的程序中注释出来了。

与QListIlterator 类似, QMapIterator提供了toFront( ). toBack( ). hasNext( )、next()、peekNext( )、hasPrevious( )、previous( )和peekPrevious()等函数。可以在next()、peekNext()、previous( )和peekPrevious()等函数返回的对象上分别使用key()和value()函数来获取键和值。

还是那样项目,main.cpp

#include <QCoreApplication>
#include <QMapIterator>
#include <QMutableMapIterator>
#include <QDebug>

int main(int argc, char *argv[])
{
    QCoreApplication a(argc, argv);
    QMap<QString, QString> map;
    map.insert("Paris", "France");
    map.insert("Guatemala City", "Guatemala");
    map.insert("Mexico City", "Mexico");
    map.insert("Moscow", "Russia");
    QMapIterator<QString,QString> i(map);
    while(i.hasNext()) {
        i.next();
        qDebug() << i.key() << " : " << i.value();
    }
    if(i.findPrevious("Mexico")) qDebug() << "find 'Mexico'";  // 向前查找键的值
    QMutableMapIterator<QString, QString> j(map);
    while (j.hasNext()) {
        if (j.next().key().endsWith("City")) // endsWith()是QString类的函数
            j.remove();                      // 删除含有“City”结尾的键的项目
    }
    while(j.hasPrevious()) {
        j.previous();          // 现在的键值对为(paris,France),(Moscow,Russia)
        qDebug() << j.key() << " : " << j.value();
    }
    return a.exec();
}

其中,QMap中存储了一些(首都,国家)键值对,然后删除了包含以“City”字符串结尾的键的项目。对于QMap的遍历,可以先使用next()函数,然后再使用key()和value()来获取键和值的信息。因为这里很多函数与前面例子中的用法相似,这里就不再过多讲解。现在运行一下程序,从遍历结果可以看到,QMap是按照键的顺序来存储数据的,比如这里是按照键的字母顺序排列的。

STL风格迭代器

STL风格迭代器兼容Qt和STL的通用算法(generic algorithms),而且在速度上.进行了优化。每-一个容器类都有两个STL风格迭代器类型:一个提供了只读访问,另一个提供了读/写访问,如表所列。因为只读迭代器比读/写迭代器要快很多,所以应尽可能使用只读迭代器。

下面仍然以QList和QMap为例进行相关内容的讲解。新建Qt控制台应用,项目名称为myiterators3。然后将main. cpp文件更改如下:

#include <QCoreApplication>
#include <QList>
#include <QDebug>
#include <QMap>

int main(int argc, char *argv[])
{
    QCoreApplication a(argc, argv);
    QList<QString> list;
    list << "A" << "B" << "C" << "D";
    QList<QString>::iterator i;      // 使用读写迭代器
    qDebug() << "the forward is :";
    for (i = list.begin(); i != list.end(); ++i) {
        *i = (*i).toLower();         // 使用QString的toLower()函数转换为小写
        qDebug() << *i;              // 结果为a,b,c,d
    }
    qDebug() << "the backward is :";
    while (i != list.begin()) {
        --i;
        qDebug() << *i;               // 结果为d,c,b,a
    }
    QList<QString>::const_iterator j; // 使用只读迭代器
    qDebug() << "the forward is :";
    for (j = list.constBegin(); j != list.constEnd(); ++j)
        qDebug() << *j;               // 结果为a,b,c,d

    

    return a.exec();
}

STL风格迭代器的API模仿了数组的指针,例如,使用“十十”操作符来向后移动迭代器使其指向下一个项目、使用“*”操作符返回迭代器指向的项目等。需要说明的是,不同于Java风格迭代器,STL风格迭代器是直接指向项目的。其中,一个容器的begin()函数返回了一个指向该容器中第一个项目的迭代器,end()函数也返回一个迭代器,但是这个迭代器指向该容器最后一个项目的下一个假想的虚项目,end()标志着一个无效的位置,当列表为空时,begin()函数等价于end()函数。STL迭代器的有效位置如图所示。上面的程序中分别使用了读/写迭代器和只读迭代器对列表进行了遍历,可以运行程序查看效果。

在STL风格迭代器中“++”和“- -”操作符既可以作为前缀(i,–i)操作符,也可以作为后缀(i,i-- )操作符。当作为前缀时会先修改迭代器,然后返回修改后的迭代器的一个引用;当作为后缀时,在修改迭代器以前会对其进行复制,然后返回这个复制。如果在表达式中不会对返回值进行处理,那么最好使用前缀操作符(++i,–i),这样会更快一些。对于非const迭代器类型,使用一元操作符“*”获得
的返回值可以用在赋值运算符的左侧。STL风格迭代器的常用API如表所列。

QMap可以使用“*”操作符来返回一个项目,然后使用key()和value()来分别获取键和值。在前面的程序中先添加头文件井include< QMap>,然后在main()函数的
return a. exec();”一行代码前添加如下代码:

QMap<QString, int> map;
    map.insert("one",1);
    map.insert("two",2);
    map.insert("three",3);
    QMap<QString, int>::const_iterator p;
    qDebug() << "the forward is :";
    for (p = map.constBegin(); p != map.constEnd(); ++p)
        qDebug() << p.key() << ":" << p.value();// 结果为(one,1),(three,3),(two,2)

这里创建了一个QMap,然后使用STL风格的只读迭代器对其进行遍历,输出了其中所有项目的键和值,可以运行程序查看一下输出结果。

foreach关键字

foreach是Qt向C+ +语言中添加的一个用来进行容器顺序遍历的关键字,它使用预处理器来进行实施。下面来看一下具体应用的例子。

新建Qt控制台应用,项目名称为myforeach。然后将main. cpp文件更改如下:

#include <QCoreApplication>
#include <QList>
#include <QMap>
#include <QMultiMap>
#include <QDebug>

int main(int argc, char *argv[])
{
    QCoreApplication a(argc, argv);
    QList<QString> list;
    list.insert(0, "A");
    list.insert(1, "B");
    list.insert(2, "C");
    qDebug() <<"the list is :";
    foreach (QString str, list) {   // 从list中获取每一项
        qDebug() << str;            // 结果为A,B,C
    }

    QMap<QString,int> map;
    map.insert("first", 1);
    map.insert("second", 2);
    map.insert("third", 3);
    qDebug() << endl << "the map is :";
    foreach (QString str, map.keys())   // 从map中获取每一个键
        // 输出键和对应的值,结果为(first,1),(second,2),(third,3)
        qDebug() << str << " : " << map.value(str);

    QMultiMap<QString,int> map2;
    map2.insert("first", 1);
    map2.insert("first", 2);
    map2.insert("first", 3);
    map2.insert("second", 2);
    qDebug() << endl << "the map2 is :";
    QList<QString> keys = map2.uniqueKeys(); // 返回所有键的列表
    foreach (QString str, keys) {            // 遍历所有的键
        foreach (int i, map2.values(str))    // 遍历键中所有的值
            qDebug() << str << " : " << i;
    }// 结果为(first,3),(first,2),(first,1),(second,2)
    return a.exec();
}

上面的程序中使用了foreach关键字分别遍历了QList、QMap、QMultiMap。
需要说明的是,在foreach循环中也可以使用break和continue语句。可以看到,使用foreach关键字进行容器的遍历非常便捷;当不愿意使用迭代器时,就可以使用foreach来代替。现在运行程序查看一下输出结果。

关于容器的相关内容就讲到这里,如果还希望了解各个容器类的时间复杂度、增长策略等内容,则可以参考Qt帮助中Container Classes关键字的相关内容。

遍历算法

在< QtAlgorithms>头文件中,Qt提供了一些全局的模板函数,这些函数是可以使用在容器上的十分常用的算法。我们可以在任何提供了STL风格迭代器的容器类上使用这些算法,包括QList .QLinkedList、QVector、QMap和QHash。如果在目标平台上可以使用STL,那么可以使用STL的算法来代替Qt的这些算法,因为STL提供了更多的算法,而Qt只提供了其中最重要的一些算法。下面对其中几个常用的算法进行演示,读者可以在帮助索引中通过GenericAlgorithms关键字来了解其他的算法。

新建Qt控制台应用,项目名称为myalgorithms。然后main.cpp文件更改如下:

#include <QCoreApplication>
#include <QVector>
#include <QStringList>
#include <QDebug>
#include <algorithm>

int main(int argc, char *argv[])
{
    QCoreApplication a(argc, argv);

    QStringList list;
    list << "one" << "two" << "three";

    qDebug() << QObject::tr("std::copy算法:");
    QVector<QString> vect(3);
    // 将list中所有项目复制到vect中
    std::copy(list.begin(), list.end(), vect.begin());
    qDebug() << vect; //结果为one,two,three

    qDebug() << endl << QObject::tr("std::equal算法:");
    // 从list的开始到结束的所有项目与vect的开始及其后面的等数量的项目进行比较,
    // 全部相同则返回true
    bool ret1 = std::equal(list.begin(), list.end(), vect.begin());
    qDebug() << "euqal: " << ret1; //结果为true

    qDebug() << endl << QObject::tr("std::find算法:");
    // 从list中查找"two",返回第一个对应的值的迭代器,如果没有找到则返回end()
    QList<QString>::iterator i = std::find(list.begin(), list.end(), "two");
    qDebug() << *i;     // 结果为"two"

    qDebug() << endl << QObject::tr("std::fill算法:");
    // 将list中的所有项目填充为"eleven"
    std::fill(list.begin(), list.end(), "eleven");
    qDebug() << list;  // 结果eleven,eleven,eleven

    QList<int> list1;
    list1 << 3 << 3 << 6 << 6 << 6 << 8;

    qDebug() << endl << QObject::tr("std::count算法:");
    int countOf6 = std::count(list1.begin(), list1.end(), 6); // 查找6的个数
    qDebug() << "countOf6: " << countOf6; // 结果为3

    qDebug() << endl << QObject::tr("std::lower_bound算法:");
    // 返回第一个出现5的位置,如果没有5,则返回5应该在的位置,
    // list1被查找的范围中的项目必须是升序
    QList<int>::iterator j = std::lower_bound(list1.begin(), list1.end(), 5);
    list1.insert(j, 5);
    qDebug() << list1; // 结果3,3,5,6,6,6,8

    QList<int> list2;
    list2 << 33 << 12 << 68 << 6 << 12;

    qDebug() << endl << QObject::tr("std::sort算法:");
    // 使用快速排序算法对list2进行升序排序,排序后两个12的位置不确定
    std::sort(list2.begin(), list2.end());
    qDebug() << list2; // 结果6,12,12,33,68

    qDebug() << endl << QObject::tr("std::stable_sort算法:");
    // 使用一种稳定排序算法对list2进行升序排序,
    // 排序前在前面的12,排序后依然在前面
    std::stable_sort(list2.begin(), list2.end());
    qDebug() << list2; // 结果6,12,12,33,68

    qDebug() << endl << QObject::tr("std::greater算法:");
    // 可以在qSort()算法中使其反向排序
    qSort(list2.begin(), list2.end(), std::greater<int>());
    qDebug() << list2; // 结果68,33,12,12,6

    qDebug() << endl << QObject::tr("std::swap算法:");
    double pi = 3.14;
    double e = 2.71;
    std::swap(pi, e);                         // 交换pi和e的值
    qDebug() << "pi:" << pi << "e:" << e; // 结果pi=2.71,e=3.14

    return a.exec();
}

这些算法在一些数据处理操作中非常有用,应该对它们有一定的了解,以后遇到相关问题时要能够想到使用它们。现在可以运行程序,运行结果已经注释在代码中了。

< QtGlobal>头文件中也提供了一些函数来实现一些经常使用的功能,比如qAbs()函数用来获取绝对值、qBound()丽数用来获取数值边界、qMax()函数用来返回两个数中的最大值、qMin()函数用来返回两个数中的最小值、qRound()丽数用来返回一个浮点数接近的整数值、还有以前讲到的与随机数相关的qrand()函数和qsrand()函数等。

QString

QString类提供了一个Unicode(Unicode是一种支持大部分文字系统的国际字符编码标准)字符串。其实在第一个Hello World程序就用到了它,而几乎所有的程序中
都会使用到它,所以有必要对QString类进行更多的了解。QString 存储了一QChar,而QChar提供了一个16位的Unicode4.0字符。在后台,QString使用隐式共
享(implicit sharing)来减少内存使用和避免不必要的数据复制,这也有助于减少存储16位字符的固有开销。这一小节的内容可以参考QString类的帮助文档。

隐式共享

隐式共享( Implicit Sharing)又称为写时复制(copy-on-write)。Qt 中很多C++类使用隐式数据共享来尽可能地提高资源使用率和减少复制操作。使用隐式共享类作为
参数传递是既安全又有效的,因为只有一个指向该数据的指针被传递了,只有当函数向它写人时才会复制该数据。这里根据下面的几行代码进行讲解:

QPixmap p1,p2;
p1.load("image.bmp");
p2=p1;  //p1与p2共享数据
QPainter paint;
paint.begin(&p2); //p2被修改
paint.drawText(0,50,"Hi")
paint.end();

一个共享类由指向一个共享数据块的指针和数据组成,共享数据块中包含了一个引用计数。

当一个共享对象被建立时,会设置引用计数为1,例如,这里QPixmap类是一个隐式共享类,开始时pl和p2的引用计数都为1。每当有新的对象引用了共享数据时引用计数都会递增,而有对象不再引用这个共享数据时引用计数就会递减;当引用计数为0时,这个共享数据就会被销毁掉。

例如,这里执行了“p2 = p1;"语句后,p2 便与pl共享同一个数据,这时pl的引用计数为2,而p2的引用计数为0,所以p2以前指向的数据结构将会被销毁掉。

当处理共享对象时,有两种复制对象的方法:深复制(deep copy)和浅复制(shallow copy)。深复制意味着复制一个对象,而浅复制则是复制一个引用(仅仅是一个指向共享数据块的指针)。
一个深复制是非常耗能的,需要消耗很多的内存和CPU资源;而浅复制则非常快速,因为它只需要设置一个指针和增加引用计数的值。当隐式共享类使用“=”操作符时就是使用浅复制,如上面的“p2 =p1;"语句。
但是当一个对象被修改时,就必须进行一次深复制,比如上面程序中“paint. begin(&p2);"语句要对p2进行修改,这时就要对数据进行深复制,使p2和p1指向不同的数据结构,然后将pl的引用计数设为1,p2的引用计数也设为1。

共享的好处是程序不需要进行不必要的数据复制,这样可以减少数据复制、使用更少的内存,对象也可以很容易地被分配,或者作为参数被传递,或者从函数被返回。隐式共享在后台进行,在实际编程中不必去关注它。Qt中主要的隐式共享类有QByteArray、QCursor、QFont 、QPixmap、QString、QUrl .QVariant、所有的容器类等,所有的隐式共享类及其他内容可以在帮助索引中通过ImplicitSharing关键字查看。

编辑操作

#include <QCoreApplication>
#include <QDebug>
#include <QStringList>
int main(int argc, char *argv[])
{
    QCoreApplication a(argc, argv);
    qDebug() << QObject::tr("以下是编辑字符串操作:") << endl;

    QString str = "hello";
    qDebug() << QObject::tr("字符串大小:") << str.size(); // 大小为5
    str[0] = QChar('H');      // 将第一个字符换为‘H'
    qDebug() << QObject::tr("第一个字符:") << str[0]; // 结果为‘H'
    str.append(" Qt");        // 向字符串后添加"Qt"
    str.replace(1,4,"i");     // 将第1个字符开始的后面4个字符替换为字符串"i"
    str.insert(2," my");      // 在第2个字符后插入" my"
    qDebug() << QObject::tr("str为:") << str; // 结果为Hi my Qt
    str = str + "!!!";        // 将两个字符串组合
    qDebug() << QObject::tr("str为:") << str; // 结果为Hi my Qt!!!

    str = " hi\r\n Qt!\n  ";
    qDebug() << QObject::tr("str为:") << str;
    QString str1 = str.trimmed();    // 除去字符串两端的空白字符
    qDebug() << QObject::tr("str1为:") << str1;
    QString str2 = str.simplified(); // 除去字符串两端和中间多余的空白字符
    qDebug() << QObject::tr("str2为:") << str2; //结果为hi Qt!

    str = "hi,my,,Qt";
    // 从字符串中有","的地方将其分为多个子字符串,
    // QString::SkipEmptyParts表示跳过空的条目
    QStringList list = str.split(",",QString::SkipEmptyParts);
    qDebug() << QObject::tr("str拆分后为:") << list;  // 结果为hi,my,Qt
    str = list.join(" "); // 将各个子字符串组合为一个字符串,中间用" "隔开
    qDebug() << QObject::tr("list组合后为:") << str;  // 结果为hi my Qt

    qDebug() << QString().isNull();     // 结果为true
    qDebug() << QString().isEmpty();    // 结果为true
    qDebug() << QString("").isNull();   // 结果为false
    qDebug() << QString("").isEmpty();  // 结果为true
    return a.exec();
}

QString中提供了多个方便的函数来操作字符串,例如,append()和prepend()分别实现了在字符串后面和前面添加字符串或者字符;replace()替换指定位置的多个字符;insert()在指定位置添加字符串或者字符;remove()在指定位置移除多个字符;trimmed()除去字符串两端的空白字符,包括“"、“\n”、“\v”、“\f"、“\r”和“”;simplified()不仅除去字符串两端的空白字符,还将字符串中间的空白字符序列替换为一个空格;split()可以将一个字符串分割为多个子字符串的列表等。对于-一个字符串,也可以使用“[ ]”操作符来获取或者修改其中的一个字符,还可以使用“十”操作符来组合两个字符串。QString 类中一个null字符串和一个空字符串并不是完全一样的。一个null字符串是使用QString的默认构造丽数或者在构造函数中传递了0来初始化的字符串;一个空字符串是指大小为0的字符串。一般null字符串都是空字符串,但一个空字符串不一定是一个null 字符串,实际编程中一般使用isEmpty()来判断一个字符串是否为空。

查询操作

继续在下面写:

qDebug() << endl << QObject::tr("以下是在字符串中进行查询的操作:") <<endl;
    str = "yafeilinux";
    qDebug() << QObject::tr("字符串为:") << str;
    // 执行下面一行代码后,结果为linux
    qDebug() << QObject::tr("包含右侧5个字符的子字符串:") << str.right(5);
    // 执行下面一行代码后,结果为yafei
    qDebug() << QObject::tr("包含左侧5个字符的子字符串:") << str.left(5);
    // 执行下面一行代码后,结果为fei
    qDebug() << QObject::tr("包含第2个字符以后3个字符的子字符串:") << str.mid(2, 3);
    qDebug() << QObject::tr("'fei'的位置:") << str.indexOf("fei"); //结果为2
    qDebug() << QObject::tr("str的第0个字符:") << str.at(0); //结果为y
    qDebug() << QObject::tr("str中'i'字符的个数:") << str.count('i'); //结果为2
    // 执行下面一行代码后,结果为true
    qDebug() << QObject::tr("str是否以”ya“开始?") << str.startsWith("ya");
    // 执行下面一行代码后,结果为true
    qDebug() << QObject::tr("str是否以”linux“结尾?") << str.endsWith("linux");
    // 执行下面一行代码后,结果为true
    qDebug() << QObject::tr("str是否包含”lin“字符串?") << str.contains("lin");
    QString temp = "hello";
    if(temp > str) qDebug() << temp; // 两字符串进行比较,结果为yafeilinux
    else qDebug() << str;

QString中还提供了right()、left()和 mid()函数分别来提取一个字符串的最右面.最左面和中间的含有多个字符的子字符串;也可以使用indexOf()函数来获取一个字符或者子字符串在该字符串中的位置;使用at()函数可以获取一个指定位置的字符,它比“[]”操作符要快很多,因为它不会引起深复制;可以使用contains()函数来判断该字符串是否包含一个指定的字符或者字符串;可以使用count()来获得字符串中一个字符或者子字符串出现的次数;使用startsWith()和endsWidth()函数可以判断该字符串是以一个字符或者字符串开始或者结束的;对于两个字符串的比较,可以使用“>”和“<=”等操作符,也可以使用compare()函数。

转换操作

qDebug() << endl << QObject::tr("以下是字符串的转换操作:") << endl;
    str = "100";
    qDebug() << QObject::tr("字符串转换为整数:") << str.toInt(); // 结果为100
    int num = 45;
    qDebug() << QObject::tr("整数转换为字符串:") << QString::number(num);//结果为"45"
    str = "FF";
    bool ok;
    int hex = str.toInt(&ok, 16);
    // 结果为ok:true 255
    qDebug() << "ok: "<< ok << QObject::tr("转换为十六进制:") << hex;
    num = 26;
    qDebug() << QObject::tr("使用十六进制将整数转换为字符串:")
                  << QString::number(num, 16);//结果为1a
    str = "123.456";
    qDebug() << QObject::tr("字符串转换为浮点型:") << str.toFloat();//结果为123.456
    str = "abc";
    qDebug() << QObject::tr("转换为大写:") << str.toUpper();// 结果为ABC
    str = "ABC";
    qDebug() << QObject::tr("转换为小写:") <<str.toLower();// 结果为abc
    int age = 25;
    QString name = "yafei";
    // name代替%1,age代替%2
    str = QString("name is %1, age is %2").arg(name).arg(age);
    // 结果为name is yafei, age is 25
    qDebug() << QObject::tr("更改后的str为:") << str;
    str = "%1 %2";
    qDebug() << str.arg("%1f","hello");      // 结果为%1f hello
    qDebug() << str.arg("%1f").arg("hello"); // 结果为hellof %2
    str = QString("ni%1").arg("hi",5,'*');
    qDebug() << QObject::tr("设置字段宽度为5,使用'*'填充:") << str;//结果为ni***hi
    qreal value = 123.456;
    str = QString("number: %1").arg(value,0,'f',2);
    qDebug() << QObject::tr("设置小数点位数为两位:") << str;  //结果为"number:123.45
    // 执行下面一行代码,结果为number:123.45不会显示引号
    qDebug() << QObject::tr("将str转换为const char* :") << qPrintable(str);

对于“e”、“E"和“f”格式,精度precision 表示小数点后面的位数;而对于“g"和“G”格式,精度表示有效数字的最大位数。arg()是 一个非常有用的丽数,前面的章节中已经多次用到了它,要在一个字符串中使用变量,那么使用arg()是种很好的解决办法。还有一个qPrintable()函数,它不是QString中的函数,但是可以将字符串转换为const char*类型,当在输出一个字符串的时候,两边总会有引号,为了显示更清晰,可以使用这个函数将引号去掉。QString 中还提供了toAscii()、toLatin1(). toUtf8()和toLocal8Bit()等函数,可以使用一种编码将字符串转换为QByteArray类型。

QByteArray和QVariant

QByteArray类提供了一个字节数组,它可以用来存储原始字节(包括“\0’)和传统的以“\0’结尾的8位字符串。

使用QByteArray比使用const char*要方便很多,在后台,它总是保证数据以一个“\0’结尾,而且使用隐式共享来减少内存的使用和避免不必要的数据复制。
但是除了当需要存储原始二进制数据或者对内存保护要求很高(如在嵌人式Linux上)时,一般都推荐使用QString,因为QString存储16位的Unicode字符,使得在应用程序中更容易存储非ASCII和非Latin-1 字符,而且QString全部使用的是Qt的API。

QByteArray类拥有和QString类相似的接口函数,比如前一节讲到的QString的那些函数,除了arg()以外,在QByteArray中都有相同的用法。

QVariant类像是常见的Qt数据类型的一个共用体(union),一个QVariant对象,在一个时间只保存一个单一类型的单一的值(有些类型可能是多值的,比如字符串列表)。可以使用toT()(T代表一种数据类型)函数将QVariant对象转换为T类型,并且获取它的值。这里toT()函数会复制以前的QVariant 对象,然后对其进行转换,所以以前的QVariant对象并不会改变。QVariant 是Qt中一个很重要的类,比如前面讲解属性系统时提到的QObject::property()返回的就是QVariant类型的对象。

新建Qt Widgets 应用,项目名称为myvariant,基类选择QWidget ,类名保持Widget 不变。建好项目后,在wid-
get.cpp文件中添加头文件井include< QDebug>,然后在构造函数中添加如下代码:

QVariant v1(15);
    qDebug() << v1.toInt();                     // 结果为15
    QVariant v2(12.3);
    qDebug() << v2.toFloat();                   // 结果为12.3
    QVariant v3("nihao");
    qDebug() << v3.toString();                  // 结果为"nihao"
    QColor color = QColor(Qt::red);
    QVariant v4 = color;
    qDebug() << v4.type();                      // 结果为QVariant::QColor
    qDebug() << v4.value<QColor>();             // 结果为QColor(ARGB 1,1,0,0)
    QString str = "hello";
    QVariant v5 = str;
    qDebug() << v5.canConvert(QVariant::Int);   // 结果为true
    qDebug() << v5.toString();                  // 结果为"hello"
    qDebug() << v5.convert(QVariant::Int);      // 结果为false
    qDebug() << v5.toString();                  // 转换失败,v5被清空,结果为"0"

QVariant类的toInt()函数返回int类型的值,toFloat()函数返回float类型的值。但是,因为QVariant是Qt Core库的一.部分,所以它没有提供对Qt GUI模块中定义的数据类型(如QColor、Qlmage.QPixmap等)进行转换的函数,也就是说,这里没有toColor()这样的函数。不过,可以使用QVariant: :value( )函数或者qvariant_ cast()模板函数来完成这样的转换,如上面程序中对QColor类型的转换。要了解QVariant可以包含的所有类型以及这些类型在QVariant类中对应的toT()函数,可以查看QVariant类的参考文档。对于一个类型是否可以转换为一个特殊的类型,可以使用canConvert()函数来判断,如果可以转换,则该函数返回true。也可以使用convert()函数来将一个类型转换为不同的类型,如果转换成功则返回true;如果无法进行转换,variant对象将会被清空,并且返回false。需要说明,对于同一种转换,canConvert()和convert()函数并不一定返回同样的结果,这通常是因为canConvert()只报告QVariant进行两个类型之间转换的能力。也就是说,如果在提供了合适的数据时,这两个类型间可以进行转换;但是,如果提供的数据不合适,那么转换就会失败,这样convert()的返回值就与canConvert()不同了。例如,前面代码中的QString类型的字符串str.当str中只有数字字符时,它可以转换为int类型,比如str =“123",因为它有这个能力,所以canConvert()返回为true。但是,现在str中包含了非数字字符,真正进行转换时会失败,所以canconvert()返回为false。使用canConvert() 函数返回为true 的数据类型组合如表所列。

一言: 不管明天会发生什么,请牢记住今天。

相关文章