第七章 Qt对象模型与容器类——Qt

一、对象模型

Qt使用 moc,为标准 C++ 增加了一些特性:

●一个强大的无缝对象通信机制————信号和槽(signals and slots);
●可查询和可设计的对象属性系统(object properties);
●强大的事件和事件过滤器(events and event filters);
●通过上下文进行国际化的字符串翻译机制(string translation for internationalization);
●完善的定时器(timers)驱动,使得可以在一个事件驱动的GUI中处理多个任务;
●分层结构的、可查询的对象树(object trees),它使用一种很自然的方式来组织对象拥有权(object ownership);
●守卫指针即QPointer,它在引用对象被销毁时自动将其设置为0;
●动态的对象转换机制(dynamic cast);

Qt的这些特性都是在遵循标准C++规范内实现的,使用这些特性都必须要继承自QObject类。其中对象通信机制和动态属性系统,还需要元对象系统(Meta-Object System)的支持。

1.信号与槽

信号和槽用于两个对象之间的通信,当一个特殊的事情发生时便可以发射一个信号,比如按钮被单击;而槽就是一个函数,它在信号发射后被调用,来响应这个信号。
一个信号可以关联到多个槽上,多个信号也可以关联到同一个槽上,甚至,一个信号还可以关联到另一个信号上。
如果存在多个槽与某个信号相关联,那么,当这个信号被发射时,这些槽将会一个接一个地执行,执行顺序与关联顺序相同。

(1)信号

声明一个信号,例如:

signals:
	void dlgReturn(int);                  // 自定义的信号		
●声明一个信号要使用signals关键字。			
●在signals前面不能使用public、private和protected等限定符,因为只有定义该信号的类及其子类才可以发射该信号。				
●信号只用声明,不需要也不能对它进行定义实现。				
●信号没有返回值,只能是void类型的。				
●只有QObject类及其子类派生的类才能使用信号和槽机制,使用信号和槽,还必须在类声明的最开始处添加Q_OBJECT宏。
(2)发射信号

使用emit关键字将自定义的信号发射出去。

(3)槽

自定义槽的声明:

private slots:
	void showValue(int value);

声明一个槽需要使用slots关键字。一个槽可以是private、public或者protected类型的,槽也可以被声明为虚函数,这与普通的成员函数是一样的,也可以像调用一个普通函数一样来调用槽。槽的最大特点就是可以和信号关联。

(4)信号与槽关联

①手动关联

connect()函数原型如下:

bool QObject::connect ( const QObject * sender, const char * signal, const QObject * receiver, const char * method, Qt::ConnectionType type = Qt::AutoConnection )		
  • 第一个参数为发送信号的对象

  • 第二个参数是要发送的信号

  • 第三个参数是接收信号的对象

  • 第四个参数是要执行的槽,这里是SLOT(showValue(int))。

  • 对于信号和槽,必须使用SIGNAL()和SLOT()宏,它们可以将其参数转化为const char* 类型。connect()函数的返回值为bool类型,当关联成功时返回true。

  • 信号和槽的参数只能有类型,不能有变量。

  • 最后一个参数,它表明了关联的方式,其默认值是Qt::AutoConnection,这里还有其他几个选择:

      常量							描述
      Qt::AutoConnection			自动关联,默认值。如果信号和槽在不同的线程中,同Qt::QueuedConnection;如果信号与槽在同一个线程中,同Qt::DirectConnection;
      Qt::DirectConnection			直接关联,发射完信号后立即调用槽,只有槽执行完成返回后,发射信号处后面的代码才可以执行
      Qt::QueuedConnection			队列关联,当控制返回接受者所在线程的事件循环后再执行槽,发射信号处后面的代码会立即执行
      Qt::BlockingQueuedConnection	阻塞队列关联,类似上面那个,不过信号线程会一直阻塞,直到槽返回,当接受者存在信号线程不能使用,不然会死锁
      Qt::UniqueConnection			唯一关联,标志,可以结合其他几种连接类型,使用按位或操作组合,主要是为了防止重复关联
      Qt::AutoCompatConnection 		类似Qt::AutoConnection,它是Qt3中的默认类型
    

connect()函数另一种常用的基于函数指针的重载形式 :(Qt5新增)

[static] QMetaObject::Connection QObject::connect(
		const QObject *sender, PointerToMemberFunction signal, 
		const QObject *receiver,PointerToMemberFunction method, 
		Qt::ConnectionType type = Qt::AutoConnection)	

指定信号和槽两个参数时不用再使用SIGNAL()和SLOT()宏,并且槽函数不再必须是使用slots关键字声明的函数,而可以是任意能和信号关联的成员函数。要使一个成员函数可以和信号关联,那么这个函数的参数数目不能超过信号的参数数目,但是并不要求该函数拥有的参数类型与信号中对应的参数类型完全一致,只需要可以进行隐式转换即可。

②自动关联

为了实现槽函数自动进行关联,对于Qt窗口部件已经提供的信号,可按照以下规范命名:
void on_<窗口部件名称><信号名称>(<信号参数>);
若未能自动关联,需要显示调用connectSlotByName(),调用方法如上注释处,详细请参照官方帮助文档:

Searches recursively for all child objects of the given object, and connects matching signals from them to slots of object that follow the following form:
	void on_<object name>_<signal name>(<signal parameters>);
Let's assume our object has a child object of type QPushButton with the object name button1. 
The slot to catch the button's clicked() signal would be:
	void on_button1_clicked();
If object itself has a properly set object name, its own signals are also connected to its respective slots.
	See also QObject::setObjectName().
(5)信号与槽高级应用

获得信号发送者的信息,在Qt中提供了QObject::sender()函数来返回发送该信号的对象的指针。
如果有多个信号关联到了同一个槽上,而在该槽中需要对每一个信号进行不同的处理,使用QSignalMapper类。
QSignalMapper可以被叫做信号映射器,它可以实现对多个相同部件的相同信号进行映射,为其添加字符串或者数值参数,然后再发射出去。

(6)信号和槽机制的特色和优越性
  • 信号和槽机制是类型安全的,相关联的信号和槽的参数必须匹配;
  • 信号和槽是松耦合的,信号发送者不知道也不需要知道接受者的信息;
  • 信号和槽可以使用任意类型的任意数量的参数。
2.属性系统

要声明一个属性,那么该类必须继承自QObject类,而且还要在声明前使用Q_PROPERTY()宏:

Q_PROPERTY(type name
		(READ getFunction [WRITE setFunction] |
		MEMBER memberName [(READ getFunction | WRITE setFunction)])
			[RESET resetFunction]
			[NOTIFY notifySignal]
			[REVISION int]
			[DESIGNABLE bool]
			[SCRIPTABLE bool]
			[STORED bool]
			[USER bool]
			[CONSTANT]
			[FINAL])

其中type表示属性的类型,它可以是QVariant所支持的类型或者是用户自定义的类型。
而如果是枚举类型,还需要使用Q_ENUMS()宏在元对象系统中进行注册,这样以后才可以使用QObject::setProperty()函数来使用该属性。
name就是属性的名称。READ后面是读取该属性的函数,这个函数是必须有的,而后面带有“[ ]”号的选项表示这些函数是可选的。
一个属性类似于一个数据成员,不过添加了一些可以通过元对象系统访问的附加功能:

  • 一个读(READ)操作函数。如果MEMBER变量没有指定,那么该函数是必须有的,它用来读取属性的值。这个函数一般是const类型的,
    它的返回值类型必须是该属性的类型,或者是该属性类型的指针或者引用。
  • 一个可选的写(WRITE)操作函数。它用来设置属性的值。这个函数必须只有一个参数,而且它的返回值必须为空void。
  • 如果没有指定READ操作函数,那么必须指定一个MEMBER变量关联,这样会使给定的成员变量变为可读写的而不用创建READ和WRITE操作函数。
  • 一个可选的重置(RESET)函数。它用来将属性恢复到一个默认的值。这个函数不能有参数,而且返回值必须为空void。
  • 一个可选的通知(NOTIFY)信号。如果使用该选项,那么需要指定类中一个已经存在的信号,每当该属性的值改变时都会发射该信号。
    如果使用MEMBER变量时指定NOTIFY信号,那么信号最多只能有一个参数,并且参数的类型必须与属性的类型相同。
  • 一个可选的版本(REVISION)号。如果包含了该版本号,它会定义属性及其通知信号只用于特定版本的API(通常暴露给QML),如果不包含,则默认为0。
  • 可选的DESIGNABLE表明这个属性在GUI设计器(例如Qt Designer)的属性编辑器中是否可见。大多数属性的该值为true,即可见。
  • 可选的SCRIPTABLE表明这个属性是否可以被脚本引擎(scripting engine)访问,默认值为true。
  • 可选的STORED表明是否在当对象的状态被存储时也必须存储这个属性的值,大部分属性的该值为true。
  • 可选的USER表明这个属性是否被设计为该类的面向用户或者用户可编辑的属性。一般,每一个类中只有一个USER属性,它的默认值为false。
  • 可选的CONSTANT表明这个属性的值是一个常量。对于给定的一个对象实例,每一次使用常量属性的READ方法都必须返回相同的值,
    但对于类的不同的实例,这个常量可以不同。一个常量属性不可以有WRITE方法和NOTIFY信号。
  • 可选的FINAL表明这个属性不能被派生类重写。

其中的READ,WRITE和RESET函数可以被继承,也可以是虚的(virtual),当在多继承时,它们必须继承自第一个父类。

3.对象树和拥有权

Qt中使用对象树(object tree)来组织和管理所有的QObject类及其子类的对象。
当创建一个QObject时,如果使用了其他的对象作为其父对象(parent),那么这个QObject就会被添加到父对象的children()列表中,这样当父对象被销毁时,这个QObject也会被销毁。

4.元对象系统

Qt中的元对象系统(Meta-Object System)提供了对象间通信的信号和槽机制、运行时类型信息和动态属性系统。元对象系统是基于以下三个条件的:

  • 该类必须继承自QObject类;
  • 必须在类的私有声明区声明Q_OBJECT宏(在类定义时,如果没有指定public或者private,则默认为private);
  • 元对象编译器Meta-Object Compiler(moc),为QObject的子类实现元对象特性提供必要的代码。

其中moc工具读取一个C++源文件,如果它发现一个或者多个类的声明中包含有Q_OBJECT宏,便会另外创建一个C++源文件(就是在项目目录中的debug目录下看到的以moc开头的C++源文件),其中包含了为每一个类生成的元对象代码。
这些产生的源文件或者被包含进类的源文件中,或者和类的实现同时进行编译和链接。
元对象系统主要是为了实现信号和槽机制才被引入的,不过除了信号和槽机制以外,元对象系统还提供了其他一些特性:

  • QObject::metaObject()函数可以返回一个类的元对象,它是QMetaObject类的对象;
  • QMetaObject::className()可以在运行时以字符串形式返回类名,而不需要C++编辑器原生的运行时类型信息(RTTI)的支持;
  • QObject::inherits()函数返回一个对象是否是QObject继承树上一个类的实例的信息;
  • QObject::tr()和QObject::trUtf8()进行字符串翻译来实现国际化;
  • QObject::setProperty()和QObject::property()通过名字来动态设置或者获取对象属性;
  • QMetaObject::newInstance()构造该类的一个新实例。

除了这些特性外,还可以使用qobject_cast()函数来对QObject类进行动态类型转换,这个函数的功能类似于标准C++中的dynamic_cast()函数,但它不再需要RTTI的支持。这个函数尝试将它的参数转换为尖括号中的类型的指针,如果是正确的类型则返回一个非零的指针,如果类型不兼容则返回0。
例如:

QObject *obj = new MyWidget;
QWidget *widget = qobject_cast<QWidget *>(obj);	

二、容器类

1.Qt的容器类简介

Qt提供了几个有序容器:QList、QLinkedList、QVector、QStack和QQueue。大多数时候,QList是最好的选择,虽然是用数组实现的,但在它的首尾添加元素都非常快。如果你需要一个链表(linked-list)就用QLinkedList;想要你的项在内存中连续存储,就使用QVector。QStack和QQueue(栈和队列)分别提供了后进先出(LIFO)和先进先出(FIFO)的机制。
Qt还有一些关联容器:QMap、QMultiMap、QHash、QMultiHash、QSet。“Multi”容器支持一个键对应多个值。
“Hash”容器在有序集合上使用hash函数进行快速的查找,而没有用二叉搜索。
作为特殊的情况,QCache和QContiguousCache类在有限的缓存中提供对对象高效的哈希查找。

类					|		概述
————————————————————|————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————
QList<T>			|	这是目前使用最频繁的容器类,它存储了指定类型(T)的一串值,可以通过索引来获得。本质上QList是用数组实现的,
					|	从而保证基于索引的访问非常快。可以通过QList::append()和QList::prepend在两端添加项,
					|	或者通过QList::insert()在中间插入项。QStringList是从QList<QString>得到的。
————————————————————|————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————	
QLinkedList<T>	 	|	类似于QList,但它使用迭代器而不是整数索引来获得项。当在一个很大的list中间插入项时,它提供了更好的性能,
					|	并且它有更好的迭代器机制。(只要那一项存在,指向那一项的迭代器依然保持有效。但插入或移除之后,QList中的迭代器可能会失效)
————————————————————|————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————	
QVector<T>	 		|	在内存中相邻的位置存储一组值,在开头或中间插入会非常慢,因为它会导致内存中很多项移动一个位置。
————————————————————|————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————
QStack<T>	 		|	QVector的一个子类,提供后进先出的机制。在当前的QVector中增加了几个方法:push()、pos()、top()。
————————————————————|————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————
QQueue<T>	 		|	QList的一个子类,提供了先进先出的机制,在当前的QList中增加了几个方法:enqueue()、dequeue()、head()。
————————————————————|————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————
QSet<T>	 			|	单值的数学集合,能够快速查找。
————————————————————|————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————
QMap<Key, T>	 	|	提供了字典(关联数组)将类型Key的键对应类型T的值。通常一个键对应一个值,QMap以Key的顺序存储数据,
					|	如果顺序不重要,QHash是一个更快的选择。
————————————————————|————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————
QMultiMap<Key, T>	|	QMap的子类,提供了多值的接口,一个键对应多个值。
————————————————————|————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————
QHash<Key, T>	 	|	和QMap几乎有着相同的接口,但查找起来更快。QHash存储数据没有什么顺序。
————————————————————|————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————
QMultiHash<Key, T>	|	QHash的子类,提供了多值的接口。
—————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————
(1)QList

QList是一个模板类,它提供了一个列表。QList实际上是一个T类型项目的指针数组,所以它支持基于索引的访问,而且当项目的数目小于1000时,可以实现在列表中间进行快速的插入操作。QList提供了很多方便的接口函数来操作列表中的项目,
例如:

●插入操作insert();
●替换操作replace();
●移除操作removeAt();
●移动操作move();
●交换操作swap();
●在表尾添加项目append();
●在表头添加项目prepend();
●移除第一个项目removeFirst();
●移除最后一个项目removeLast();
●从列表中移除一项并获取这个项目takeAt(),还有相应的takeFirst()和takeLast();
●获取一个项目的索引indexOf();
●判断是否含有相应的项目contains();
●获取一个项目出现的次数count()。

对于QList,可以使用“<<”操作符来向列表中插入项目,也可以使用“[ ]”操作符通过索引来访问一个项目,其中项目是从0开始编号的。
不过,对于只读的访问,另一种方法是使用at()函数,它比“[ ]”操作符要快很多。

(2)QMap

QMap类是一个容器类,它提供了一个基于跳跃列表的字典(a skip-list-based dictionary)。QMap<Key,T>是Qt的通用容器类之一,
它存储(键,值)对并提供了与键相关的值的快速查找。QMap中提供了很多方便的接口函数,
例如:

●插入操作insert();
●获取值value();
●是否包含一个键contains();
●删除一个键remove();
●删除一个键并获取该键对应的值take();
●清空操作clear();
●插入一键多值insertMulti()。

可以使用“[ ]”操作符插入一个键值对或者获取一个键的值,不过当使用该操作符获取一个不存在的键的值时,会默认向map中插入该键,为了避免这个情况,可以使用value()函数来获取键的值。当使用value()函数时,如果指定的键不存在,那么默认会返回0,可以在使用该函数时提供参数来更改这个默认返回的值。QMap默认是一个键对应一个值的,但是也可以使用insertMulti()进行一键多值的插入,对于一键多值的情况,更方便的是使用QMap的子类QMultiMap。

(3)嵌套和赋值
  • 容器也可以嵌套使用,例如QMap<QString,QList >,这里键的类型是QString, 而值的类型是QList,需要注意,在后面的“> >”符号之间要有一个空格,不然编译器会将它当做“>>”操作符对待。
  • 在各种容器中所存储的值的类型可以是任何的可赋值的数据类型,该类型需要有一个默认的构造函数, 一个拷贝构造函数和一个赋值操作运算符,像基本的类型double,指针类型,Qt的数据类型如QString、QDate、QTime等。

但是QObject以及QObject的子类都不能存储在容器中,不过,可以存储这些类的指针,例如QList<QWidget*>。

2.遍历容器

遍历一个容器可以使用迭代器(iterators)来完成,Qt容器类有两种类型的迭代器:Java风格的以及STL风格的。
当调用非const的成员函数将容器中的数据从隐式共享的拷贝中修改或分离时,两种迭代器都会失效。

(1)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>

Java风格的迭代器指向项之间的位置,而不是直接指向项。由于这个原因,它们指向第一项之前,或者最后一项之后,或者两项之间。
下面的图展示了包含4项的list的有效的迭代器位置,用红色箭头表示:
在这里插入图片描述

在一个迭代器上调用next()和previous()函数的效果:
在这里插入图片描述

QListIterator的API:
函数			用途
toFront()		将迭代器移到list的最前面(在第一个项之前)
toBack()		将迭代器移到list的最后面 (最后一项之后)
hasNext()		如果迭代器没有到list的最后则返回true
next()			返回下一项,并将迭代器向前移动一个位置
peekNext()		返回下一项,不会移动迭代器
hasPrevious()	如果迭代器没有到list的最前面则返回true
previous()		返回上一项,并将迭代器移到上一个位置
peekPrevious()	返回上一项,不会移动迭代器

QListIterator没有提供从list中插入或移除项的函数,想要实现插入和移除,必须使用QMutableListIterator。

(2)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		Map<Key, T>::iterator
QHash<Key, T>, QMultiHash<Key, T>	QHash<Key, T>::const_iterator		QHash<Key, T>::iterator

STL迭代器的API是以数组中的指针为模型的,比如++运算符将迭代器前移到下一项,*运算符返回迭代器所指的那一项。事实上,对于QVector和QStack,它们的项在内存中存储在相邻的位置,迭代器类型正是T *,const迭代器类型正是const T *。
STL风格的迭代器直接指向每一项。下图展示了一个包含4个元素的vector的所有有效迭代器位置,用红色箭头标出:
在这里插入图片描述

STL风格迭代器的API:
表达式		用途
*i			返回当前项
++i			将迭代器指向下一项
i += n		迭代器向前移动n项
--i			将迭代器指向上一项
i -= n		将迭代器你向后移动n项
i - j		返回迭代器i和j之间项的数目

说明:

  • ++和–运算符可以使用前缀(++i, --i)和后缀(i++, i–)的形式,前缀的版本修改迭代器并返回修改后迭代器的引用,后缀版本在修改之前先复制迭代器,然后返回它的拷贝。在不需要考虑返回值的情况下,我们推荐使用前缀运算符(++i, --i),因为它们稍微快一点。
  • 对于非const的迭代器类型,一元运算符*可以被用在赋值运算符的左边。

补充:

  • foreach关键字
    如果要按顺序遍历容器中的所有项,可以使用Qt的foreach关键字。
    它的语法是:foreach (variable, container) statement

  • 其它类似容器的类
    Qt提供了三个模板类(valarray、vector、array),在一些方面与容器有点像。这些类不提供迭代器,而且不能使用foreach关键字。
    QVarLengthArray<T, Prealloc>提供一个低级的可变长度的数组,当速度特别重要的时候,它可以被用来替换QVector。
    QCache<Key, T>提供缓存,用来存储和Key类型键相关联的T类型的对象。
    QContiguousCache提供了一种缓存可连续获得的数据的有效方式。
    QPair<T1, T2>存储一对元素。
    其它类似于模板容器的非模板类型有QBitArray、QByteArray、QString和QStringList。

3.通用算法
(1)算法:
qFind() :查找容器中特定的值。(实际使用线性搜索)
qBinayyFind(): 查找容器中特定的值,但它认为容器是升序排序的。(实际上使用的快速二分法搜索)
qFill():采用特定的值组装一个容器。
qCopy():将一个容器的值拷贝到另外一个容器。
qSort():升序排序一个容器。
qDeleteAll():对存放在容器中的每一个指针调用delete。			
QString:支持16位的Unicode。
QByteArray:存储原始的二进制数据以及8位编码的文本字符串。
(2)算法复杂度

算法复杂度关注当容器中项的数目增长时,函数有多快。
为了描述算法复杂度,使用了下面的术语,基于“大O”标记法:

●常量时间:O(1)。
●指数时间:O(log n)。
●线性时间:O(n)。
●线性指数时间:O(nlog n)。
●平方时间:O(n2)。

下面的表概括了Qt顺序容器的算法复杂度:

按索引查找		插入	在前面增加		在后面增加
QLinkedList<T>	O(n)			O(1)	O(1)			O(1)
QList<T>		O(1)			O(n)	Amort. O(1)		Amort. O(1)
QVector<T>		O(1)			O(n)	O(n)			Amort. O(1)

说明:在表中,“Amort”指的是“平摊行为”。比如,“Amort.O(1)”指的是如果你只调用函数1次,你可能得到O(n),但如果你多次调用,平均下来将是O(1)。
下面的表概括了Qt关联容器的算法复杂度:

				  |		关键字查找			  |			插入
				  |—————————————————————————— |———————————————————————————
				  |	平均		|	最坏情况  | 平均		    |	最坏情况
————————————————————————————————|—————————————|—————————————|—————————————				  
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>	  |	Amort. O(1)	|	O(n)	  |	Amort. O(1)	|	O(n)
QSet<Key>		  |	Amort. O(1)	|	O(n)	  |	Amort. O(1)	|	O(n)
——————————————————————————————————————————————————————————————————————————

补充:

增长策略
QVector、QString和 QByteArray在内存中连续存储它们的项;QList维护一个指向每一项指针的数组,从而提供快速的基于索引的获得方法;
QHash<Key, T>维护一个哈希表,它的大小与其中项的个数成比例。为了避免每次在容器末尾增加一项就分配一次内存,这些容器比实际需要的分配了更多的内存。
QByteArray和QList使用了与QString差不多的算法。
QVector对一些数据类型也使用同样的算法,这些数据类型可以使用memcpy()在内存中移动(包括基本的C++类型,指针类型以及Qt的共享类)。 但是QVector对只能调用构造和析构函数来移动的数据类型使用了不同的算法,这些情况下重新分配内存的代价更高,当空间不够时,QVector通过内存加一倍来减少再分配的次数。
QHash<Key, T>是一个完全不同的情况。QHash的内部哈希表以2的次方增长,每次增长时,项被定为到新的存储块中, 通过qHash(key) % QHash::capacity()(存储快的数目)计算。这个机制同样适用于QSet和QCache<Key, T>。
QVector、QHash<Key, T>、QSet、QString和QByteArray提供了一些函数,能够检测和确定存储这些项用了多少内存:

  • capacity()返回内存分配的项的数目(对QHash和QSet来说,是hash表中存储块的数目)。
  • reserve(size)显式地为size个项预分配内存。
  • squeeze()释放不需要用来存储项的内存。

如果知道在容器中大约要存储多少项,可以调用reserve()开始,当在容器中存储结束,可以调用squeeze()来释放额外的预分配的内存。

4.QString

QString 存储字符串釆用的是 Unicode 码,每一个字符是一个 16 位的 QChar,而不是 8 位的 char,所以 QString 处理中文字符没有问题,而且一个汉字算作是一个字符。
QString使用隐式共享(implicit sharing)来减少内存使用和避免不必要的数据拷贝,这也有助于减少存储16位字符的固有开销。

(1)隐式共享

隐式共享(Implicit Sharing)又称为写时复制(copy-on-write)。使用隐式共享类作为参数传递是既安全又有效的,因为只有一个指向该数据的指针被传递了,只有当函数向它写入时才会复制该数据。
Qt中主要的隐式共享类有:QByteArray、QCursor、QFont、QPixmap、QString、QUrl、QVariant和所有的容器类等等。

(2)编辑操作

在QString中提供了多个方便的函数来操作字符串,如:

  • append()和prepend()分别实现了在字符串后面和前面添加字符串或者字符;replace()替换指定位置的多个字符;
  • insert()在指定位置添加字符串或者字符;
  • remove()在指定位置移除多个字符;
  • trimmed()除去字符串两端的空白字符,这包括‘\t’、‘\n’、‘\v’、‘\f’、‘\r’和‘ ’;
  • simplified()不仅除去字符串两端的空白字符,还将字符串中间的空白字符序列替换为一个空格;
  • split()可以将一个字符串分割为多个子字符串的列表等等。

对于一个字符串,也可以使用“[ ]”操作符来获取或者修改其中的一个字符,还可以使用“+”操作符来组合两个字符串。
在QString类中一个null字符串和一个空字符串并不是完全一样的。一个null字符串是使用QString的默认构造函数或者在构造函数中传递了0来初始化的字符串;而一个空字符串是指大小为0的字符串。一般null字符串都是空字符串,但一个空字符串不一定是一个null字符串,在实际编程中一般使用isEmpty()来判断一个字符串是否为空。

(3)查询操作
  • right()、left()和mid()函数分别来提取一个字符串的最右面,最左面和中间的含有多个字符的子字符串;
  • indexOf()函数来获取一个字符或者子字符串在该字符串中的位置;
  • at()函数可以获取一个指定位置的字符,它比“[ ]”操作符要快很多,因为它不会引起深拷贝;
  • contains()函数用来判断该字符串是否包含一个指定的字符或者字符串;
  • count()用来获得字符串中一个字符或者子字符串出现的次数;
  • startsWith()和endsWidth()函数可以用来判断该字符串是否是以一个字符或者字符串开始或者结束的;
  • 对于两个字符串的比较,可以使用“>”和“<=”等操作符,也可以使用compare()函数。
(4)转换操作
  • QString中的toInt()、toDouble()等函数可以很方便的将字符串转换为整型或者double型数据,当转换成功后,它们的第一个bool型参数会为true;
  • 静态函数number()可以将数值转换为字符串,这里还可以指定要转换为哪种进制;
  • toLower()和toUpper()函数可以分别返回字符串小写和大写形式的副本。
(5)arg()函数

arg接口列表

QString arg(const QString &a, int fieldWidth = 0, QChar fillChar = QLatin1Char(' ')) const
QString arg(qlonglong a, int fieldWidth = 0, int base = 10, QChar fillChar = QLatin1Char(' ')) const
QString arg(qulonglong a, int fieldWidth = 0, int base = 10, QChar fillChar = QLatin1Char(' ')) const
QString arg(long a, int fieldWidth = 0, int base = 10, QChar fillChar = QLatin1Char(' ')) const
QString arg(ulong a, int fieldWidth = 0, int base = 10, QChar fillChar = QLatin1Char(' ')) const
QString arg(int a, int fieldWidth = 0, int base = 10, QChar fillChar = QLatin1Char(' ')) const
QString arg(uint a, int fieldWidth = 0, int base = 10, QChar fillChar = QLatin1Char(' ')) const
QString arg(short a, int fieldWidth = 0, int base = 10, QChar fillChar = QLatin1Char(' ')) const
QString arg(ushort a, int fieldWidth = 0, int base = 10, QChar fillChar = QLatin1Char(' ')) const
QString arg(double a, int fieldWidth = 0, char format = 'g', int precision = -1, QChar fillChar = QLatin1Char(' ')) const
QString arg(char a, int fieldWidth = 0, QChar fillChar = QLatin1Char(' ')) const
QString arg(QChar a, int fieldWidth = 0, QChar fillChar = QLatin1Char(' ')) const
QString arg(QStringView a, int fieldWidth = 0, QChar fillChar = QLatin1Char(' ')) const
QString arg(QLatin1String a, int fieldWidth = 0, QChar fillChar = QLatin1Char(' ')) const
QString arg(const QString &a1, const QString &a2) const
QString arg(const QString &a1, const QString &a2, const QString &a3) const
QString arg(const QString &a1, const QString &a2, const QString &a3, const QString &a4) const
QString arg(const QString &a1, const QString &a2, const QString &a3, const QString &a4, const QString &a5) const
QString arg(const QString &a1, const QString &a2, const QString &a3, const QString &a4, const QString &a5, const QString &a6) const
QString arg(const QString &a1, const QString &a2, const QString &a3, const QString &a4, const QString &a5, const QString &a6, const QString &a7) const
QString arg(const QString &a1, const QString &a2, const QString &a3, const QString &a4, const QString &a5, const QString &a6, const QString &a7, const QString &a8) const
QString arg(const QString &a1, const QString &a2, const QString &a3, const QString &a4, const QString &a5, const QString &a6, const QString &a7, const QString &a8, const QString &a9) const

基本使用

使用%1-%99占位符来组合数据,每一个%?对应一个arg(...),最多可以添加到%99,如:
	QString str = QString("%1+%2=?").arg(1.1).arg(10.1);
	qDebug() << str;
	输出:1.1+10.1=?
可以不按顺序填充数据,如:
	QString str = QString("%2+%1=?").arg(1.1).arg(10.1);
	qDebug() << str;
	输出:10.1+1.1=?
当然你还可以重复使用%1-%99,如:
	QString str = QString("%2+%1=%1+%2").arg(1.1).arg(10.1);
	qDebug() << str;
	输出:"10.1+1.1=1.1+10.1"
如果填充的是都是QString类型数据,还可以合并在一个arg写但最多9个参数,如:
	QString str = QString("%1 %2 %3").arg("Hello", "world", "!!!"); /* 这里的字符串隐式转换为QString了 */
	qDebug() << str;
	输出:"Hello world !!!"

进阶

附加指定的填充数据:

QString arg(const QString &a, int fieldWidth = 0, QChar fillChar = QLatin1Char(' ')) const
a:需要填充的数据
fieldWidth:如果是正值,则fillChar字符依附在a的前面fieldWidth次;如果为负值,则fillChar字符依附在a的后面fieldWidth次。
fillChar:依附在a前/后的字符。
例:						
	qDebug() << QString("%1").arg("+", 5, '='); /* '='依附在'+'前5次 */
	qDebug() << QString("%1").arg("+", -5, '=');/* '='依附在'+'后5次 */
	输出:
		====+
		+====

转换为对应进制的QString:

QString QString::arg(int a, int fieldWidth = 0, int base = 10, QChar fillChar = QLatin1Char(' ')) const
a:需要填充的整型数据。
base:转换目标为236进制之间。
例:
	qDebug() << QString("%1").arg(100, 0, 2); /* 转换为2进制 */
	qDebug() << QString("%1").arg(100, 0, 16);/* 转换为16进制 */
	输出:
		1100100
		64
5.QByteArray和QVariant
(1)QByteArray

QByteArray类提供了一个字节数组,它可以用来存储原始字节(包括’\0’)和传统的以’\0’结尾的8位字符串。
使用QByteArray比使用const char*要方便很多,在后台,它总是保证数据以’\0’结尾,而且使用隐式共享来减少内存的使用和避免不必要的数据复制。

(2)QVariant

QVariant类像是最常见的Qt的数据类型的一个共用体(union),一个QVariant对象在一个时间只保存一个单一类型的一个单一的值。
可以使用toT()(T代表一种数据类型)函数来将QVariant对象转换为T类型,并且获取它的值。
这里toT()函数会复制以前的QVariant对象,然后对其进行转换,所以以前的QVariant对象并不会改变。

三、正则表达式

正则表达式(regular expression),就是在一个文本中匹配子字符串的一种模式(pattern),它可以简写为“regexp”。
一个regexp主要应用在以下几个方面:

  • 验证:一个regexp可以测试一个子字符串是否符合一些标准。
  • 搜索:一个regexp提供了比简单的子字符串匹配更强大的模式匹配。
  • 查找和替换:一个regexp可以使用一个不同的字符串替换一个字符串中所有要替换的子字符串。
  • 字符串分割:一个regexp可以识别在哪里进行字符串分割。

Qt中的QRegExp类实现了使用正则表达式进行模式匹配。
下表列出了所有的元字符和对它们的一个简短的描述:

元字符		|	描述
————————————|—————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————
\ 			|	将下一个字符标记为一个特殊字符、或一个原义字符、或一个向后引用、或一个八进制转义符。
			|	例如,“\\n”匹配\n。“\n”匹配换行符。序列“\\”匹配“\”而“\(”则匹配“(”。即相当于多种编程语言中都有的“转义字符”的概念。
————————————|—————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————
^			|	匹配输入字符串的开始位置。如果设置了RegExp对象的Multiline属性,^也匹配“\n”或“\r”之后的位置。
————————————|—————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————
$			|	匹配输入字符串的结束位置。如果设置了RegExp对象的Multiline属性,$也匹配“\n”或“\r”之前的位置。
————————————|—————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————
*			|	匹配前面的子表达式任意次。例如,zo*能匹配“z”,“zo”以及“zoo”。*等价于{0,}。
————————————|—————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————
+			|	匹配前面的子表达式一次或多次(大于等于1次)。例如,“zo+”能匹配“zo”以及“zoo”,但不能匹配“z”。+等价于{1,}。
————————————|—————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————
?			|	匹配前面的子表达式零次或一次。例如,“do(es)?”可以匹配“do”或“does”中的“do”。?等价于{0,1}。
————————————|—————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————
{n}			|	n是一个非负整数。匹配确定的n次。例如,“o{2}”不能匹配“Bob”中的“o”,但是能匹配“food”中的两个o。
————————————|—————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————
{n,}		|	n是一个非负整数。至少匹配n次。例如,“o{2,}”不能匹配“Bob”中的“o”,但能匹配“foooood”中的所有o。“o{1,}”等价于“o+”。“o{0,}”则等价于“o*”。
————————————|—————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————
{n,m}		|	m和n均为非负整数,其中n<=m。最少匹配n次且最多匹配m次。例如,“o{1,3}”将匹配“fooooood”中的前三个o。“o{0,1}”等价于“o?”。
			|	请注意在逗号和两个数之间不能有空格。
————————————|—————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————
?			|	当该字符紧跟在任何一个其他限制符(*,+,?,{n},{n,},{n,m})后面时,匹配模式是非贪婪的。
			|	非贪婪模式尽可能少的匹配所搜索的字符串,而默认的贪婪模式则尽可能多的匹配所搜索的字符串。
			|	例如,对于字符串“oooo”,“o+?”将匹配单个“o”,而“o+”将匹配所有“o”。
————————————|—————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————
.点			|	匹配除“\r\n”之外的任何单个字符。要匹配包括“\r\n”在内的任何字符,请使用像“[\s\S]”的模式。
————————————|—————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————
(pattern)	|	匹配pattern并获取这一匹配。所获取的匹配可以从产生的Matches集合得到,在VBScript中使用SubMatches集合,
			|	在JScript中则使用$0…$9属性。要匹配圆括号字符,请使用“\(”或“\)”。
————————————|—————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————
(?:pattern)	|	匹配pattern但不获取匹配结果,也就是说这是一个非获取匹配,不进行存储供以后使用。这在使用或字符“(|)”来组合一个模式的各个部分是很有用。
			|	例如“industr(?:y|ies)”就是一个比“industry|industries”更简略的表达式。
————————————|—————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————
(?=pattern)	|	正向肯定预查,在任何匹配pattern的字符串开始处匹配查找字符串。这是一个非获取匹配,也就是说,该匹配不需要获取供以后使用。
			|	例如,“Windows(?=95|98|NT|2000)”能匹配“Windows2000”中的“Windows”,但不能匹配“Windows3.1”中的“Windows”。
			|	预查不消耗字符,也就是说,在一个匹配发生后,在最后一次匹配之后立即开始下一次匹配的搜索,而不是从包含预查的字符之后开始。
————————————|—————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————
(?!pattern)	|	正向否定预查,在任何不匹配pattern的字符串开始处匹配查找字符串。这是一个非获取匹配,也就是说,该匹配不需要获取供以后使用。
			|	例如“Windows(?!95|98|NT|2000)”能匹配“Windows3.1”中的“Windows”,但不能匹配“Windows2000”中的“Windows”。
————————————|—————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————
(?<=pattern)|	反向肯定预查,与正向肯定预查类似,只是方向相反。例如,“(?<=95|98|NT|2000)Windows”能匹配“2000Windows”中的“Windows”,
			|	但不能匹配“3.1Windows”中的“Windows”。
————————————|—————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————
(?			|	反向否定预查,与正向否定预查类似,只是方向相反。例如“(?
————————————|—————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————
x|y			|	匹配x或y。例如,“z|food”能匹配“z”或“food”或"zood"(此处请谨慎)。“(z|f)ood”则匹配“zood”或“food”。
————————————|—————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————
[xyz]		|	字符集合。匹配所包含的任意一个字符。例如,“[abc]”可以匹配“plain”中的“a”。
————————————|—————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————
[^xyz]		|	负值字符集合。匹配未包含的任意字符。例如,“[^abc]”可以匹配“plain”中的“plin”。
————————————|—————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————
[a-z]		|	字符范围。匹配指定范围内的任意字符。例如,“[a-z]”可以匹配“a”到“z”范围内的任意小写字母字符。
			|	注意:只有连字符在字符组内部时,并且出现在两个字符之间时,才能表示字符的范围; 如果出字符组的开头,则只能表示连字符本身.
————————————|—————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————
[^a-z]		|	负值字符范围。匹配任何不在指定范围内的任意字符。例如,“[^a-z]”可以匹配任何不在“a”到“z”范围内的任意字符。
————————————|—————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————
\b			|	匹配一个单词边界,也就是指单词和空格间的位置(即正则表达式的“匹配”有两种概念,一种是匹配字符,一种是匹配位置,
			|	这里的\b就是匹配位置的)。例如,“er\b”可以匹配“never”中的“er”,但不能匹配“verb”中的“er”。
————————————|—————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————
\B			|	匹配非单词边界。“er\B”能匹配“verb”中的“er”,但不能匹配“never”中的“er”。
————————————|—————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————
\cx			|	匹配由x指明的控制字符。例如,\cM匹配一个Control-M或回车符。x的值必须为A-Z或a-z之一。否则,将c视为一个原义的“c”字符。
————————————|—————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————
\d			|	匹配一个数字字符。等价于[0-9]。
————————————|—————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————
\D			|	匹配一个非数字字符。等价于[^0-9]。
————————————|—————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————
\f			|	匹配一个换页符。等价于\x0c和\cL。
————————————|—————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————
\n			|	匹配一个换行符。等价于\x0a和\cJ。
————————————|—————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————
\r			|	匹配一个回车符。等价于\x0d和\cM。
————————————|—————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————
\s			|	匹配任何不可见字符,包括空格、制表符、换页符等等。等价于[ \f\n\r\t\v]。
————————————|—————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————
\S			|	匹配任何可见字符。等价于[^ \f\n\r\t\v]。
————————————|—————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————
\t			|	匹配一个制表符。等价于\x09和\cI。
————————————|—————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————
\v			|	匹配一个垂直制表符。等价于\x0b和\cK。
————————————|—————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————	
\w			|	匹配包括下划线的任何单词字符。类似但不等价于“[A-Za-z0-9_]”,这里的"单词"字符使用Unicode字符集。
————————————|—————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————
\W			|	匹配任何非单词字符。等价于“[^A-Za-z0-9_]”。
————————————|—————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————
\xn			|	匹配n,其中n为十六进制转义值。十六进制转义值必须为确定的两个数字长。例如,“\x41”匹配“A”。“\x041”则等价于“\x04&1”。
			|	正则表达式中可以使用ASCII编码。
————————————|—————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————
\num		|	匹配num,其中num是一个正整数。对所获取的匹配的引用。例如,“(.)\1”匹配两个连续的相同字符。
————————————|—————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————
\n			|	标识一个八进制转义值或一个向后引用。如果\n之前至少n个获取的子表达式,则n为向后引用。
			|	否则,如果n为八进制数字(0-7),则n为一个八进制转义值。
————————————|—————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————
\nm			|	标识一个八进制转义值或一个向后引用。如果\nm之前至少有nm个获得子表达式,则nm为向后引用。如果\nm之前至少有n个获取,
			|	则n为一个后跟文字m的向后引用。如果前面的条件都不满足,若n和m均为八进制数字(0-7),则\nm将匹配八进制转义值nm。
————————————|—————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————
\nml		|	如果n为八进制数字(0-7),且m和l均为八进制数字(0-7),则匹配八进制转义值nml。
————————————|—————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————
\un			|	匹配n,其中n是一个用四个十六进制数字表示的Unicode字符。例如,\u00A9匹配版权符号(©)。
————————————|—————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————
\< \>		|	匹配词(word)的开始(\<)和结束(\>)。例如正则表达式\能够匹配字符串"for the wise"中的"the",
			|	但是不能匹配字符串"otherwise"中的"the"。注意:这个元字符不是所有的软件都支持的。
————————————|—————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————
\( \)		|	将 \( 和 \) 之间的表达式定义为“组”(group),并且将匹配这个表达式的字符保存到一个临时区域(一个正则表达式中最多可以保存9个),
			|	它们可以用 \1 到\9 的符号来引用。
————————————|—————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————
|			|	将两个匹配条件进行逻辑“或”(Or)运算。例如正则表达式(him|her) 匹配"it belongs to him"和"it belongs to her",
			|	但是不能匹配"it belongs to them."。注意:这个元字符不是所有的软件都支持的。
————————————|—————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————
+			|	匹配1或多个正好在它之前的那个字符。例如正则表达式9+匹配9、99、999等。注意:这个元字符不是所有的软件都支持的。
————————————|—————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————
?			|	匹配0或1个正好在它之前的那个字符。注意:这个元字符不是所有的软件都支持的。
————————————|—————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————
{i} {i,j}	|	匹配指定数目的字符,这些字符是在它之前的表达式定义的。例如正则表达式A[0-9]{3} 能够匹配字符"A"后面跟着正好3个数字字符的串,
			|	例如A123、A348等,但是不匹配A1234。而正则表达式[0-9]{4,6} 匹配连续的任意4个、5个或者6个数字
————————————|—————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————
  • 1
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值