Qt 学习之路 2笔记3

来源:互联网 发布:网络翻墙违法吗 编辑:程序博客网 时间:2024/04/20 11:18

Graphics View Framework

Graphics View 提供了一种接口,用于管理大量自定义的 2D 图形元素,并与之进行交互;还提供了用于将这些元素进行可视化显示的观察组件,并支持缩放和旋转。

Graphics View 框架包含了一套完整的事件体系,可以用于与场景中的元素进行双精度的交互。这些元素同样支持键盘事件、鼠标事件等。Graphics View 使用了 BSP 树(Binary Space Partitioning tree,这是一种被广泛应用于图形学方面的数据结构)来提供非常快速的元素发现,也正因为如此,才能够实现一种上百万数量级元素的实时显示机制。

Graphics View 是一个基于元素(item)的 MV 架构的框架。它可以分成三个部分:元素 item、场景 scene 和视图 view

基于元素的意思是,它的每一个组件都是一个独立的元素。这是与QPainter状态机机制不同。使用QPainter绘图,大多是采用一种面向过程的描述方式:首先使用drawLine()画一条直线,然后使用drawPolygon()画一个多边形。对于 Graphics View,相同的过程可以是,首先创建一个场景(scene),然后创建一个直线对象和一个多边形对象,再使用场景的add()函数,将直线和多边形添加到场景中,最后通过视图进行观察。

MV 架构的意思是,Graphics View 提供一个 model 和一个 view,所谓模型(model)就是我们添加的种种对象;所谓视图(view)就是我们观察这些对象的视口。同一个模型可以由很多视图从不同的角度进行观察。

Graphics View 提供了QGraphicsScene作为场景,即是允许我们添加图形的空间,相当于整个世界;QGraphicsView作为视口,也就是我们的观察窗口,相当于照相机的取景框,这个取景框可以覆盖整个场景,也可以是场景的一部分;QGraphicsItem作为图形元件,以便添加到场景中去,Qt 内置了很多图形,比如直线、多边形等,它们都是继承自QGraphicsItem。

int main(int argc, char *argv[]){    QApplication app(argc, argv);    QGraphicsScene scene;    scene.addLine(0, 0, 150, 150);    QGraphicsView view(&scene);    view.setWindowTitle("Graphics View");    view.resize(500, 500);    view.show();    return app.exec();}

这段代码很简单:首先创建一个场景,也就是QGraphicsScene对象。然后我们使用addLine()函数向场景中添加了一个直线,起始点和终点坐标分别是 (0, 0) 和 (150, 150)。通过这两步,我们已经有了场景和元素。之后,我们创建一个GraphicsView对象,绑定到一个场景上(也就是我们前面创建的 scene 对象)。

注意,QGraphicsScene不是QWidget的子类,因此该构造函数并不是调用的QGraphicsView(QWidget *parent)。
这里写图片描述
我们看到,这个直线自动在视图居中显示。这并不需要我们进行任何额外的代码。如果不想这么做,我们可以给 scene 设置一下sceneRect()属性:

    QGraphicsScene scene;    scene.setSceneRect(0, 0, 300, 300);    scene.addLine(0, 0, 150, 150);    QGraphicsView view(&scene);    view.setWindowTitle("Graphics View");    // view.resize(500, 500);    view.show();

不仅如此,我们还去掉了view.resize()一行。QGraphicsScene的sceneRect属性供QGraphicsView确定视图默认的滚动条区域,并且协助QGraphicsScene管理元素索引。之所以去掉view.resize()一行,是因为我们让系统去决定视图的最小尺寸(否则的话,我们需要手动将窗口标题栏等的大小同时考虑设置)。

文件

Qt5

  • QIODevice:所有 I/O 设备类的父类,提供了字节块读写的通用操作以及基本接口;
  • QFlie:访问本地文件或者嵌入资源;
  • QTemporaryFile:创建和访问本地文件系统的临时文件;
  • QBuffer:读写QByteArray;
  • QProcess:运行外部程序,处理进程间通讯;
  • QAbstractSocket:所有套接字类的父类;
  • QTcpSocket:TCP协议网络数据传输;
  • QUdpSocket:传输 UDP 报文;
  • QSslSocket:使用 SSL/TLS 传输数据;
  • QFileDevice:Qt5新增加的类,提供了有关文件操作的通用实现。

这其中,QProcess、QTcpSocket、QUdpSoctet和QSslSocket是顺序访问设备。所谓“顺序访问”,是指它们的数据只能访问一遍:从头走到尾,从第一个字节开始访问,直到最后一个字节,中途不能返回去读取上一个字节;QFile、QTemporaryFile和QBuffer是随机访问设备,可以访问任意位置任意次数,还可以使用QIODevice::seek()函数来重新定位文件访问位置指针。
在所有的 I/O 设备中,文件 I/O 是最重要的部分之一。QFile提供了从文件中读取和写入数据的能力。Qt5 新加入的QFileDevice类,则将这部分公共操作放到了这个单独的类中。
我们通常会将文件路径作为参数传给QFile的构造函数。不过也可以在创建好对象最后,使用setFileName()来修改。QFile需要使用 / 作为文件分隔符,不过,它会自动将其转换成操作系统所需要的形式。
QFile主要提供了有关文件的各种操作,比如打开文件、关闭文件、刷新文件等。我们可以使用QDataStream或QTextStream类来读写文件,也可以使用QIODevice类提供的read()、readLine()、readAll()以及write()这样的函数。值得注意的是,有关文件本身的信息,比如文件名、文件所在目录的名字等,则是通过QFileInfo获取,而不是自己分析文件路径字符串。

int main(int argc, char *argv[]){    QApplication app(argc, argv);    QFile file("in.txt");    if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) {        qDebug() << "Open file failed.";        return -1;    } else {        while (!file.atEnd()) {            qDebug() << file.readLine();        }    }    QFileInfo info(file);    qDebug() << info.isDir();    qDebug() << info.isExecutable();    qDebug() << info.baseName();    qDebug() << info.completeBaseName();    qDebug() << info.suffix();    qDebug() << info.completeSuffix();    return app.exec();}

首先使用QFile创建了一个文件对象。这个文件名字是 in.txt。然后,我们使用open()函数打开这个文件,打开形式是只读方式,文本格式。
程序的第二部分,我们使用QFileInfo获取有关该文件的信息。QFileInfo有很多类型的函数,我们只举出一些例子。比如这里,isDir()检查该文件是否是目录;isExecutable()检查该文件是否是可执行文件等。baseName()可以直接获得文件名;suffix()则直接获取文件后缀名。我们可以由下面的示例看到,baseName()和completeBaseName(),以及suffix()和completeSuffix()的区别:

QFileInfo fi("/tmp/archive.tar.gz");QString base  = fi.baseName();  // base = "archive"QString cbase = fi.completeBaseName();  // base = "archive.tar"QString ext   = fi.suffix();  // ext = "gz"QString ext   = fi.completeSuffix();  // ext = "tar.gz"

二进制文件

Qt还提供了更高一级的操作:用于二进制的流QDataStream和用于文本流的QTextStream。
QDataStream提供了基于QIODevice的二进制数据的序列化。数据流是一种二进制流,这种流完全不依赖于底层操作系统、CPU 或者字节顺序(大端或小端)。
QDataStream既能够存取 C++ 基本类型,如 int、char、short 等,也可以存取复杂的数据类型,例如自定义的类。实际上,QDataStream对于类的存储,是将复杂的类分割为很多基本单元实现的。
结合QIODevice,QDataStream可以很方便地对文件、网络套接字等进行读写操作。

QFile file("file.dat");file.open(QIODevice::WriteOnly);QDataStream out(&file);out << QString("the answer is");out << (qint32)42;

为性能起见,数据只有在文件关闭时才会真正写入。因此,我们必须在最后添加一行代码:

file.close(); // 如果不想关闭文件,可以使用 file.flush();

文件读取:

QFile file("file.dat");file.open(QIODevice::ReadOnly);QDataStream in(&file);QString str;qint32 a;in >> str >> a;

需要注意的是,必须按照写入的顺序,将数据读取出来。也就是说,程序数据写入的顺序必须预先定义好。在这个例子中,我们首先写入字符串,然后写入数字,那么就首先读出来的就是字符串,然后才是数字。顺序颠倒的话,程序行为是不确定的,严重时会直接造成程序崩溃。

文本文件读写

文本文件是一种人可读的文件。为了操作这种文件,我们需要使用QTextStream类。
QTextStream会自动将 Unicode 编码同操作系统的编码进行转换,这一操作对开发人员是透明的。它也会将换行符进行转换,同样不需要自己处理。QTextStream使用 16 位的QChar作为基础的数据存储单位,同样,它也支持 C++ 标准类型,如 int 等。实际上,这是将这种标准类型与字符串进行了相互转换。
QTextStream同QDataStream的使用基本一致,例如下面的代码将把“The answer is 42”写入到 file.txt 文件中:

QFile data("file.txt");if (data.open(QFile::WriteOnly | QIODevice::Truncate)) {    QTextStream out(&data);    out << "The answer is " << 42;}

open()函数中增加了QIODevice::Truncate打开方式。我们可以从下表中看到这些打开方式的区别:

枚举值 描述 QIODevice::NotOpen 未打开 QIODevice::ReadOnly 以只读方式打开 QIODevice::WriteOnly 以只写方式打开 QIODevice::ReadWrite 以读写方式打开 QIODevice::Append 以追加的方式打开,新增加的内容将被追加到文件末尾 QIODevice::Truncate 以重写的方式打开,在写入新的数据时会将原有数据全部清除,游标设置在文件开头。 QIODevice::Text 在读取时,将行结束符转换成 \n;在写入时,将行结束符转换成本地格式,例如 Win32 平台上是 \r\n QIODevice::Unbuffered 忽略缓存

默认情况下,QTextStream的编码格式是 Unicode,如果我们需要使用另外的编码,可以使用:

stream.setCodec("UTF-8");

为方便起见,QTextStream同std::cout一样提供了很多描述符,被称为 stream manipulators。因为文本文件是供人去读的,自然需要良好的格式(相比而言,二进制文件就没有这些问题,只要数据准确就可以了)。这些描述符是一些函数的简写,我们可以从文档中找到:

描述符 等价于 bin setIntegerBase(2) oct setIntegerBase(8) dec setIntegerBase(10) hex setIntegerBase(16) showbase setNumberFlags(numberFlags() | ShowBase) forcesign setNumberFlags(numberFlags() | ForceSign) forcepoint setNumberFlags(numberFlags() | ForcePoint) noshowbase setNumberFlags(numberFlags() & ~ShowBase) noforcesign setNumberFlags(numberFlags() & ~ForceSign) noforcepoint setNumberFlags(numberFlags() & ~ForcePoint) uppercasebase setNumberFlags(numberFlags() | UppercaseBase) uppercasedigits setNumberFlags(numberFlags() | UppercaseDigits) lowercasebase setNumberFlags(numberFlags() & ~UppercaseBase) lowercasedigits setNumberFlags(numberFlags() & ~UppercaseDigits) fixed setRealNumberNotation(FixedNotation) scientific setRealNumberNotation(ScientificNotation) left setFieldAlignment(AlignLeft) right setFieldAlignment(AlignRight) center setFieldAlignment(AlignCenter) endl operator<<(‘\n’)和flush() flush flush() reset reset() ws skipWhiteSpace() bom setGenerateByteOrderMark(true)

这些描述符只是一些函数的简写。例如,我们想要输出 12345678 的二进制形式,那么可以直接使用

out << bin << 12345678;

就可以了。这等价于

out.setIntegerBase(2);out << 12345678;

更复杂的,如果我们想要舒服 1234567890 的带有前缀、全部字母大写的十六进制格式(0xBC614E),那么只要使用

out << showbase << uppercasedigits << hex << 12345678;

即可。

不仅是QIODevice,QTextStream也可以直接把内容输出到QString。例如

QString str;  QTextStream(&str) << oct << 31 << " " << dec << 25 << endl;

这提供了一种简单的处理字符串内容的方法。

存储容器

存储容器(containers)有时候也被称为集合(collections),是能够在内存中存储其它特定类型的对象,通常是一些常用的数据结构,一般是通用模板类的形式。C++ 提供了一套完整的解决方案,作为标准模板库(Standard Template Library)的组成部分,也就是常说的 STL。

Qt 提供了另外一套基于模板的容器类。相比 STL,这些容器类通常更轻量、更安全、更容易使用。如果你对 STL 不大熟悉,或者更喜欢 Qt 风格的 API,那么你就应该选择使用这些类。当然,你也可以在 Qt 中使用 STL 容器,没有任何问题。

Qt 的容器类都继承QObject,都提供了隐式数据共享不可变的特性,并且为速度做了优化,具有较低的内存占用量等。另外一点比较重要的,它们是线程安全的。这些容器类是平台无关的,即不因编译器的不同而具有不同的实现;隐式数据共享,有时也被称作“写时复制(copy on write)”,这种技术允许在容器类中使用传值参数,但却不会出现额外的性能损失。

Qt 提供了顺序存储容器:QList,QLinkedList,QVector,QStack和QQueue。对于绝大多数应用程序,QList是最好的选择。虽然它是基于数组实现的列表,但它提供了快速的向前添加和向后追加的操作。如果你需要链表,可以使用QLinkedList。如果你希望所有元素占用连续地址空间,可以选择QVectorQStackQQueue则是 LIFO 和 FIFO 的。

Qt 还提供了关联容器:QMap,QMultiMap,QHash,QMultiHash和QSet。带有“Multi”字样的容器支持在一个键上面关联多个值。“Hash”容器提供了基于散列函数的更快的查找,而非 Hash 容器则是基于二分搜索的有序集合。

另外两个特例:QCache和QContiguousCache提供了在有限缓存空间中的高效 hash 查找。

  • QList< T>:这是至今为止提供的最通用的容器类。它将给定的类型 T 的对象以列表的形式进行存储,与一个整型的索引关联。QList在内部使用数组实现,同时提供基于索引的快速访问。我们可以使用 QList::append()和QList::prepend()在列表尾部或头部添加元素,也可以使用QList::insert()在中间插入。相比其它容器类,QList专门为这种修改操作作了优化。QStringList继承自QList。
  • QLinkedList< T>:类似于 QList,除了它是使用遍历器进行遍历,而不是基于整数索引的随机访问。对于在中部插入大量数据,它的性能要优于QList。同时具有更好的遍历器语义(只要数据元素存在,QLinkedList的遍历器就会指向一个合法元素,相比而言,当插入或删除数据时,QList的遍历器就会指向一个非法值)。
  • QVector< T>:用于在内存的连续区存储一系列给定类型的值。在头部或中间插入数据可能会非常慢,因为这会引起大量数据在内存中的移动。
  • QStack< T>:这是QVector的子类,提供了后进先出(LIFO)语义。相比QVector,它提供了额外的函数:push(),pop()和top()。
  • QQueue< T>:这是QList的子类,提供了先进先出(FIFO)语义。相比QList,它提供了额外的函数:enqueue(),dequeue()和head()。
  • QSet< T>:提供单值的数学上面的集合,具有快速的查找性能。
  • QMap< Key, T>:提供了字典数据结构(关联数组),将类型 T 的值同类型 Key 的键关联起来。通常,每个键与一个值关联。QMap以键的顺序存储数据;如果顺序无关,QHash提供了更好的性能。
  • QMultiMap< Key, T>:这是QMap的子类,提供了多值映射:一个键可以与多个值关联。
  • QHash< Key, T>:该类同QMap的接口几乎相同,但是提供了更快的查找。QHash以字母顺序存储数据。
  • QMultiHash< Key, T>:这是QHash的子类,提供了多值散列。

所有的容器都可以嵌套。例如,QMap< QString, QList< int> >是一个映射,其键是QString类型,值是QList< int>类型,也就是说,每个值都可以存储多个 int。这里需要注意的是,C++ 编译器会将连续的两个 > 当做输入重定向运算符,因此,这里的两个 > 中间必须有一个空格。

能够存储在容器中的数据必须是可赋值数据类型。所谓可赋值数据类型,是指具有默认构造函数、拷贝构造函数和赋值运算符的类型。绝大多数数据类型,包括基本类型,比如 int 和 double,指针,Qt 数据类型,例如QString、QDate和QTime,都是可赋值数据类型。但是,QObject及其子类(QWidget、QTimer等)都不是。也就是说,你不能使用QList< QWidget>这种容器,因为QWidget的拷贝构造函数和赋值运算符不可用。如果你需要这种类型的容器,只能存储其指针,也就是QList< QWidget *>。

根据数据结构的相关内容,我们有必要对这些容器类的算法复杂性进行定量分析。算法复杂度关心的是在数据量增长时,容器的每一个函数究竟有多快(或者多慢)。
常量时间:O(1)。如果一个函数的运行时间与容器中数据量无关,我们说这个函数是常量时间的。QLinkedList::insert()就是常量时间的。

  • 对数时间:O(log n)。如果一个函数的运行时间是容器数据量的对数关系,我们说这个函数是对数时间的。qBinaryFind()就是对数时间的。
  • 线性时间:O(n)。如果一个函数的运行时间是容器数据量的线性关系,也就是说直接与数量相关,我们说这个函数是限行时间的。QVector::insert()就是线性时间的。
  • 线性对数时间:O(n log n)。线性对数时间要比线性时间慢,但是要比平方时间快。
  • 平方时间:O(n²)。平方时间与容器数据量的平方关系。
查找 插入 前方添加 后方追加 QLinkedList< T> O(n) O(1) O(1) O(1) QList< T> O(1) O(n) 统计 O(1) 统计 O(1) QVector< T> O(1) O(n) O(n) 统计 O(1)

上表中,所谓“统计”,意思是统计意义上的数据。例如“统计 O(1)”是说,如果只调用一次,其运行时间是 O(n),但是如果调用多次(例如 n 次),则平均时间是 O(1)。

查找键 插入 平均 最坏 平均 最坏 QMap< Key, T> O(log n) O(log n) O(log n) O(log n) QMultiMap< Key, T> O(log n) O(log n) O(log n) O(log n) QHash< Key, T> 统计 O(1) O(n) O(1) 统计 O(n) QSet< Key, T> 统计 O(1) O(n) O(1) 统计 O(n)

STL 风格的遍历器:
STL 风格的遍历器从 Qt 2.0 就开始提供。这种遍历器能够兼容 Qt 和 STL 的通用算法,并且为速度进行了优化。Qt 提供了两种 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
QList<QString> list;list << "A" << "B" << "C" << "D";QList<QString>::iterator i;for (i = list.begin(); i != list.end(); ++i) {    *i = (*i).toLower();}

容器的begin()函数返回指向该容器第一个元素的遍历器;end()函数返回指向该容器最后一个元素之后的元素的遍历器。end()实际是一个非法位置,永远不可达。这是为跳出循环做的一个虚元素。
这里写图片描述

QList<QString>::const_iterator i;for (i = list.constBegin(); i != list.constEnd(); ++i) {    qDebug() << *i;}
QMap<int, int> map;QMap<int, int>::const_iterator i;for (i = map.constBegin(); i != map.constEnd(); ++i) {    qDebug() << i.key() << ":" << i.value();}
QLinkedList<QString> list;...QString str;foreach (str, list) {    qDebug() << str;}
QLinkedList<QString> list;...QLinkedListIterator<QString> i(list);while (i.hasNext()) {    qDebug() << i.next();}
0 0
原创粉丝点击