Qt知识回顾(六)——Qt对象模型与容器类

对象模型

  标准C++对象模型可以在运行时非常有效地支持对象范式(object paradigm),但是它的静态特性在一些问题领域中不够灵活。图形用户界面编程不仅需要运行时的高效性,还需要高度的灵活性。为此,Qt在标准C++对象模型的基础上添加了一些特性,形成了自己的对象模型。这些特性有:
  > 一个强大的无缝对象通信机制——信号与槽(signals and slots);
  > 可查询、可设计的对象属性系统(object properties);
  > 强大的事件和事件过滤器(events and evnet filters);
  > 基于上下文的国际化字符串翻译机制(string translation for internationalization);
  > 完善的定时器(timers)驱动,可以在一个事件驱动的GUI中处理多个任务;
  > 分层结果的、可查询的对象树(object trees),它使用一种很自然的方式来组织对象拥有权(object ownership);
  > 守卫指针即QPointer,它在引用对象被销毁时将自动将其设置为0;
  > 动态的对象转换机制(dynamic cast);
  > 支持创建自定义类型(custom type);
  Qt的这些特性都是在遵循标准C++规范内实现的,使用这些特性都必须要继承自QObject类。其中,对象通信机制和动态属性系统还需要元对象系统(Meta-Objetc System)的支持,关于对象模型的介绍,可以在帮助中通过Object Model关键字查看。

信号与槽

  在前面已经简单介绍过信号与槽,在这里进行含义详细介绍。信号和槽用于两个对象之间的通信。信号和槽机制是Qt的核心特征,也是Qt不同于其他开发框架的最突出特征。在GUI编程中,当改变了一个部件时马总希望其他部件也能了解到该变化。更一般来说,我们希望任何对象都可以和其他对象进行通信。例如,用户单击了关闭按钮,则希望可以执行窗口的close()函数来关闭窗口。为了实现对象间的通信,一些工具包中使用了回调(callback)机制,而在Qt中使用了信号和槽来进行对象间的通信。当一个特殊的事情发生时便可以发射一个信号,比如按钮被单击就发射clicked()信号;而槽就是一个函数,它在信号发射后被调用来相应这个信号。Qt的部件中已经定义了一些信号和槽,但是更常用的做法是子类化部件,然后添加自定义的信号和槽来实现想要的功能。注意,一个信号可以关联到多个槽上,多个信号也可以关联到同一个槽上,甚至,一个信号还可以关联到另一个信号上,如果存在多个槽与某个信号相关联,那么,当这个信号被发射时,这个槽将会一个接一个地执行,执行顺序与关联顺序相同。
  (ps: 回调就是指向函数的指针,把这个指针传递给一个要被处理的函数,那么就可以在这个函数被处理时在适当的地方调用这个回调函数。回调机制主要有两个缺陷:第一,不是类型安全的(type-safe),不能保证在回调函数时可以使用正确的参数;第二,是强耦合地。处理函数必须直到调用哪个回调函数。)

信号
  声明一个信号要使用signals关键字,在signals前面不能用public、private和protected等限定符,因为信号默认是public函数,可以从任何地方进行发射,但是建议只在定义该信号的类及其子类中发射该信号。信号只用声明,不需要也不能对它进行定义实现。还要注意,信号没有返回值,只能是void类型的。因为只有QObject类 及其派生的类才能使用信号和槽机制。发射一个信号要用到emit关键字。


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

信号和槽的使用
  使用信号与槽应注意以下几点:①需要继承自QObject或其子类;②在生命的最开始处添加Q_OBJECT类;③槽中参数的类型要和信号参数的类型相对应,且不能比信号的参数多;④信号只用声明,没有定义,且返回值为void类型。

信号和槽的关联
  信号和槽的关联使用的是QObject类的connect()函数,该函数原型如下:

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

  第一个参数为发射信号的对象;第二个参数是要发射的信号;第三个参数是接收信号的对象,当这个参数为this时,也可以将这个参数省略点,因为connect()函数还有另外一个重载形式,该参数默认为this;第四个参数是要执行的槽,其实该参数也可以指定一个信号,实现信号与信号的关联。对于信号和槽,必须使用SIGNAL()和SLOT()宏,它们可以将其参数转化为const char*类型,另外,第四个参数指定的槽在声明时必须使用slots关键字。connect()函数的返回值为QMetaObject::Connection类型,该类型可以用于QObject::disconnect(const QMetaObject::connection &connection)函数来断开该关联。注意,在调用该connect()函数时信号和槽的参数只能有类型,不能有变量名。connect()函数的最后一个参数type表明了关联的方式,由Qt::ConnectionType枚举类型指定,其默认值是Qt::AutoConnection,这里还有其他几个选择,具体如下表,编程中一般使用默认值

常量描述
Qt::AutoConnection自动关联,默认值。如果receiver存在于(lives in)发射信号的线程,则使用Qt::DirectConnection;否则,使用Qt::QueiedConnection,在信号被发射时决定使用哪种关联类型
Qt::DirectConnection直接关联。发射完信号后立即调用槽,只有槽执行完成返回后,发射信号出后面的代码才可以执行
Qt::QueiedConnection队列关联。当控制返回receiver所在线程的事件循环后再执行槽,无论槽执行与否,发射信号出后面的代码都会立即执行
Qt::BlockingQueuedConnection阻塞队列关联。类似Qt::QueuedConnection,不过,信号线程会一直阻塞,直到槽返回。当receiver存在于信号线程时不能使用该类型,不然程序会死锁
Qt::UniqueConnection唯一关联。这是一个标志,可以结合其他几种连接类型,使用按位或操作。这时两个对象间的相同的信号和槽只能由唯一的关联。使用这个标志主要为了防止重复关联。

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

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

  这是Qt5中加入的一种重载形式,与前者最大的不同就是,指定信号和槽两个参数时不用再使用SIGNAL()和SLOT()宏,并且槽函数不在必须是使用slots关键字声明的函数,而可以是任意能和信号关联的成员函数。要使一个成员函数可以和信号关联,那么这个函数的参数数目不能超过信号的参数个数,但是并不要求该函数拥有的参数类型与信号中对应的参数类型完全一致,只需要可以进行隐式转换即可。使用这种方式与前一种相比,还有一个好处就是可以在编译时进行检查,信号或槽的拼写错误、槽函数参数数目多于信号的参数数目等错误在编译时就能够被发现。另外这种形式还支持C++ 11中的Lamba表达式,可以在关联时直接编写信号发射后要执行的代码。
  当然了,信号与槽的连接不止可以通过connect()函数来实现,还有一种自动关联方式,即通过在设计模式里来实现自动关联。这个比较简单,在前面已经有过介绍。
断开关联
  可以通过disconnect()函数来断开信号和槽的关联,其原型如下:

[static] bool QObject::disconnect(const QObject *sender,const char *signal,const QObject *receiver,const char *method);

  该函数由如下几种用法

disconnect(myObject,0,0,0); //等价于myObject->disconnect();断开与一个对象所有信号的所有关联

disconnect(myObject,SIGNAL(mySignal()),0,0);//等价于myObject->disconnect(SIGNAL(mySignal());断开与一个指定信号的所有关联

disconnect(myObject,0,myReceiver,0);//等价于myObject->disconnect(myReceiver);断开与一个指定Receiver的所有关联


disconnect(myObject,SIGNAL(mySignal()),myReceiver,SLOT(mySlot());
//等价于myObject->disconnect(SIGNAL(mySignal(),myReceiver,SLOT(mySlot());
//也等价于disconnect(myConnection);其中myConnection是进行关联时connect()的返回值
//断开一个指定信号和槽的关联

  与connect()函数一样,disconnect()函数也有基于函数指针的重载形式,其用法类似,这个函数并不能断开信号与一般函数或者lambda表达式之间的关联。

[static] bool QObject::disconnect(const QObject *sender,PointerToMemberFunction signal,const QObject *receiver,PointerToMemberFunction method);

信号和槽的高级应用
  有时希望获得信号发射者的消息,Qt提供了QObject::sender()函数来返回发送该信号的对象的指针。但是如果有多个信号关联到了同一个槽上,而在该槽中需要堆每一个信号进行不同的处理,则使用这种方法就很麻烦了。这种情况可以使用QSignalMapper类。QSignalMapper类被叫做信号映射器,可以实现对多个相同部件的相同信号进行映射,为其添加字符串或者数值参数,然后再发射出去。
  > 信号和槽机制的特色和优越性:
  > 信号和槽机制时类型安全的,相关联的信号和槽的参数必须匹配;
  > 信号和槽时松耦合的,信号发送者不知道也不需要直到接收者的信息;
  > 信号和槽可以使用任意类型的任意数量的参数;

属性系统

   Qt提供了强大基于元对象系统的属性系统,可以再运行Qt的平台上支持C++编译器。要在类中声明属性,该类必须继承自QObject,而且还在再声明前使用Q_PROPERTY()宏:

QPROPERTY(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后面是读取该属性的函数,这个函数是必须有的,而后面带有"[ ]"号的选项表示这些函数是可选的,一个属性类似一个数据成员,不过它添加了一些可以通过元素对象系统访问的附加功能:
  > 一个读操作函数。如果MEMBER变量没有指定,那么该函数是必须有的,它用来读取属性的值。这个函数一般是const类型的,它的返回值类型必须是该属性的类型,或者是该属性类型的指针或引用。例如QWidget::focus是一个只读属性,其READ函数是QWidget::hasFocus()。
  > 一个可选的写操作函数。它用来设置属性的值。这个函数必须只有一个参数,而且它的返回值必须为空void。例如,QWidget::enabled的WRITE函数是QWidget::setEnable();
  > 如果没有指定READ操作函数,那么必须指定一个MEMBER变量关联,这样会使给定的成员变量变为可读/写的而不用创建READ和WRITE操作函数。
  > 一个可选的重置(RESET)函数,它用来将属性恢复到一个默认的值。这个函数不能有参数,而且返回值必须为空void。例如,QWidget::cursor的RESET函数是QWidget::unsetCursor();
  > 一个可选的通知(NOTIFY)信号。如果使用该选项,那么需要指定类中一个已经存在的信号,每当该属性的值改变时都会发射该信号。如果使用MEMBER变量指定NOTIFY信号,那么信号最多只能有一个参数,并且参数的类型必须与属性的类型相同。
  > 一个可选的版本号。如果包含了该版本号,那么它会定义属性及其通知信号只用于特定版本的API(通常暴漏给QML),如果不包含,则默认为0.
  > 可选的DESIGNABLE表明这个属性在GUI设计器(例如Qt Designer)的属性编辑器中是否可见。大多数属性的该值为truem即可见。
  > 可选的SCRIPTABLE表明这个属性是否可以被脚本引擎访问,默认值为true。
  > 可选的STORED表明是否在对象的状态被存储时也必须存储这个属性的值,大部分属性的该值为true。
  > 可选的USER表明是否被设计为该类的面向用户或者用户可编辑的属性。一般,每一个类只有一个USER属性,它的默认值是false。例如QAbstractionButton::checked是按钮的用户可编辑属性。
  > 可选的CONSTANT表明这个属性的值是一个常量。对于给定的一个实例,每一次使用常量属性的READ方法都必须返回相同的值,但对于给定的一个对象实例,这个常量可以不同,一个常量属性不可以有WRITE方法和NOTIFY信号
  > 可选的FINAL表明这个属性不能被派生类重写。

对象树与拥有权

  Qt中使用对象树来组织和管理所有的QObject类及其子类的对象。当创建一个QObject时,如果使用了其他的对象作为其父对象,那么这个QObject就会被添加到父对象的children()列表中;当父对象被销毁时,这个QObject也会被销毁。实践表明,这个机制非常适合于管理GUI对象。例如,一个QShortcut(键盘快捷键)对象是相应窗口的一个子对象,当用户关闭这个窗口时,快捷键对象被销毁。
  QWidget作为Qt Widgets模块的基础类,扩展了对象间的父子关系。一个子对象一般也就是一个子部件,因为它们要显示在父部件的坐标系统之中。例如,当关闭一个消息对话框后要销毁它时,消息对话框中的按钮和标签也会被销毁,这也是我们所希望的,因为按钮和标签时消息对话框的子部件。当然,也可以自己手动来销毁一个对象,这时会将它们从其父对象中移除。这一部分的内容可以在帮助索引中通过Qbject Trees & Ownership关键字查看。‘
  对于规范的Qt程序,我们要在main()函数上中将主窗口部件创建在栈上,比如"Widget w";而不要在堆上进行创建(使用new操作符)。对于其他窗口部件,可以使用new操作符在堆上进行创建,不过一定要指定其父部件,这样就不需要在使用delete操作符来销毁该对象了。还有一种重定义父部件的情况,例如,将一个包含其他部件的布局管理器应用到窗口上,那么该布局管理器和其中所有部件都会自动将它们的父部件转移为该窗口部件。

元对象系统

  Qt中的元对象系统提供了对象间通信的信号和槽机制,运行时类型信息和动态属性系统。元对象系统是基于以下3个条件的:
  > 该类必须继承自QObject类;
  > 必须在类的私有声明区别声明Q_OBJECT宏(在宏定义时,如果没有指定public或者private,则默认为private);
  >元对象编译器Meta-Object Compiler(moc)为QObject的子类实现元对象特征提供必要的代码。
  其中,moc工具读取一个C++源文件,如果他发现一个或者多个类的声明中包含Q_OBJECT宏,则会另外创建一个C++源文件(就是在项目目录中的debug目录下看到的以moc开头的C++源文件),其中包含了为每一个类生成的元对象代码。这些产生的源文件或者被包含进类的源文件中,或者和类的实现同时进行编译和链接。
  元对象系统主要是为了实现信号和槽机制才被引入的,不过除了信号和槽机制以外,元对象系统还提供了其他一些特性:
  >QObject::metaObject()函数可以返回一个类的元对象;
  >QMetaObject::className()可以在运行时以字符串形式返回类名,而不需要C++编译器原生的运行时类型信息(RTTI)的支持;
  >QObject::inherit()函数返回一个对象是否是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); 

容器类

  Qt库提供了一组通用的基于模板的容器类。这些容器类可以用来存储指定类型的项目(items),例如,如果需要一个QSting类型的大小可变的数组,那么可以使用QVector< QString >。容器类的内容可以在帮助中参考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容器用来支持一个键多个值的情况。

简介
QList< T >这是目前最常用的容器类。它存储了给定类型的值的一个列表,而这些值可以通过索引访问。在内部,QList使用数组来实现,以确保进行快速的基于索引的访问。可以使用QList::append()和QList::prepend()在列表的两端添加项目,也可以使用QList::insert()在列表的中间插入项目。常用的QStringList继承自QList< QString >
QLinkedList< T >除了使用迭代器而不是用整数索引进行项目访问,他基本与QList相同。当向一个很大的列表的中间插入项目时,QLinkedList比QList拥有更好的性能,而且它拥有更好的迭代器语义(当迭代器指向QLinkedList的一个项目后,只要这个项目还存在,那么迭代器就依然有效;而当迭代器指向QList中的一个i项目后,如果QList进行了插入或者删除操作,那么这个迭代器就无效了)
QVector< T >它在内存的相邻位置存储给定类型的值得一个数组。在vector的前面或者中间插入项目是非常缓慢的,因为这样可能导致大量的项目在内存中移动一个位置
QStack< T >它是QVector的一个便捷子类,提供了后进先出(LIFO)语义。它添加了push()、pop()、和top()等函数
QQueue< T >它是QList的一个便捷子类,提供了先进i西安出(FIFO)语义。它添加了enqueue()、dequeue()、和head()函数
QSet< T >它提供了一个可以快速查询单值的数学集
QMap< Key,T >它提供了一个字典(关联数组),将Key类型的键映射到T类型的值上。一般每一个键关联一个单一的值。QMap使用键顺序来存储它的数据;如果不关心存储顺序,那么可以使用QHash类代替它。因为QHash速度更快
QMultiMap< Key,T >它是QMap的一个便捷类,提供了实现多值映射的接口函数,例如一个键可以关联多个值
QHash< Key,T >它与QMap拥有基本相同的接口,但是它的查找速度更快。QHash的数据是以任意的顺序存储的。
QMultiHash< Key,T >它是QHash的一个便捷类,提供了实现多值散列的接口函数。

遍历容器

  遍历一个容器可以使用迭代器来完成,迭代器提供了一个统一的方法来访问容器中的项目。Qt的容器类提供了两种类型的迭代器:Java风格迭代器和STL风格迭代器。如果只是想按顺序遍历一个容器中的项目,那么还可以使用Qt的foreach关键字。
1.Java风格迭代器
  Java风格迭代器从Qt 4时被引入,使用上比STL风格迭代器要方便得多,但是在性能上稍微弱于后者。每一个容器类都有两个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 >QMutableQSetIterator< 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 >

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

容器只读迭代器读/写迭代器
QList< T >, QQueue< T >QList< T >::const_iteratorQList< T >::iterator
QLinkedList< T >QLinkedList< T >::const_iteratorQLinkedList< T >::iterator
QVector< T >,QStack< T >QVector< T >::const_iteratorQVector< T >::iterator
QSet< T >QSet< T >::const_iteratorQSet< T >::iterator
QMap< Key,T >,QMultiMap< Key,T >QMap< Key,T >::const_iteratorQMap< Key,T >::iterator
QHash< Key,T >,QMultiHash< Key,T >QHash< Key,T >::const_iteratorQHash< Key,T >:: ``iterator

3.foreache关键字
  foreach是Qt向C++语言中添加的一个用来进行容器顺序遍历的关键字,它使用预处理器来实施。

通用算法

  在< QtAlgorithms >头文件中,Qt提供了一些全局的模板函数,这些函数是可以使用在容器上的非常常用的算法。

std::copy();//复制
std::equal();//比较是否全部相同
std::find();//查找
std::count();//计数
std::lower_bound();//查找第一个出现的**的位置
std::sort();//排序
std::greater();//可以在qsort()中反向排序
std::swap();//交换
...etc...
#include<QtGlobal>这个头文件里也提供了一些函数来实现一些经常使用的功能
qAbs();//获取绝对值
qMin();//返回两个数中的最小值
qMax();//返回两个数中的最大值
qRound();//返回一个浮点数接近的整数值
qrand();qsrand();

QString

  QString类提供了一个Unicode(Unicode是一种支持大部分文字系统的国际字符编码标准)字符串。几乎所有的程序中都会使用到他,所以有必要对QString类进行更多的了解。QString存储了一串QChar,而QChar提供了一个16位的Unicode 4.0字符。在后台,QString使用隐式共享来减少内存使用和避免不必要的数据复制,这也有助于减少存储16位字符的固有开销。
  隐式共享又称为写时复制。Qt中很多C++类使用隐式数据共享来尽可能提高资源使用率和减少复制操作。使用隐式共享类作为参数传递是既安全又有效的,因为只有一个指向该数据的指针被传递了,只有当函数向它写入时才会复制该数据。这里根据下面几行代码进行讲解:

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

  一个共享类由指向一个共享数据块的指针和数据组成,共享数据块中包含了一个引用计数。当一个共享对象被建立时,会设置引用计数为1,例如,这里的QPixmap类是一个隐式共享类,开始时p1和p2的引用计数都为1.每当有新的对象引用了共享数据时引用计数都会递增,而有对象不再引用这个共享数据时引用计数就会递减;当引用计数为0时,这个共享数据就会被销毁掉。例如,这里执行了“p2 = p1;”语句后,p2便与p1共享同一个数据,这时p1的引用计数为2,而p2的引用计数为0,所以p2以前指向的数据结构将会被销毁掉。当处理共享对象时,有两种复制对象的方法:深复制和浅复制。深复制意味着复制一个对象,而浅复制则是复制一个引用(仅仅是一个指向共享数据块的指针)。一个深复制是非常耗时的,需要消耗很多的内存和GPU资源;浅复制则非常快速,因为它只需要设置一个指针和增加引用计数的值。当隐式共享类使用"=“操作符时就是使用浅复制,如上面的"p2=p1;“语句。但是当一个对象被修改时,就必须进行一次深复制,比如上面程序中“paint.begin(&p2);”语句要对p2进行修改,这时就要对数据进行深复制,使p2和p1指向不同的数据结构,然后将p1的引用计数设置为1,p2的引用计数设置为1。
  共享的好处是程序不需要进行不必要的数据复制,这样可以减少数据复制,使用更少的内存,对象也可以很容易被分配,或者作为参数被传递,或者从函数返回。隐式共享在后台进行,在实际编程中不必去关注它。Qt主要的隐式共享类有QByteArray、QCursor、QFont、QPixmap、QString、QUrl、QVariant、所有的容器类,所有的隐式共享类及其他内容可以在帮助索引中通过Implicit Sharing关键字查看。
  QString中提供了多个方便的函数来操作字符串,例如,qppend()和prepend()分别实现了在字符串后面和前面添加字符串或者字符;replace()替换指定位置的多个字符;insert()在指定位置添加字符串或者字符;remove()在指定位置移除多个字符;trimmed()出去字符串两端的空白字符,包括”\t“、”\n”、"\v"、"\f"、”\r“和" “;simplified()不仅除去字符串两端的字符串,还将字符串中间的空白字符序列替换为一个空格;split()可以将一个字符串分割为多个在字符串的列表等;对于一个字符串,也可以使用”[ ]“操作符来获取或者修改其中一个字符,还可以使用” + “操作符来组合两个字符串。QString类中一个null字符串和一个空字符串并不是完全一样的。一个Null字符串是使用QString的默认构造函数或者在构造函数中传递了0来初始化的字符串;一个空字符串是指大小为0的空符串。一般一个null字符串都是空字符串,但一个空字符串不一定是null字符串,实际变成中一般使用isEmpty()来判断一个字符串是否为空。
  QString中还提供了right()、left()、和mid()函数分别来提取一个字符串的最右面、最左面和中间的含有多个字符的字符串;也可以使用IndexOf()函数来获取一个字符或者子字符串在该字符串中的位置;使用at()函数可以获取一个指定位置的字符,它比”[ ]“操作符要快的多,因为它不会引起深复制;可以使用contains()函数来判断该字符串是否包含一个指定的字符或者字符串;可以使用count()来获得字符串中一个字符或者字符串出现的次数;使用startsWith()和endsWidth()函数可以判断该字符串是以一个字符或者字符串开始或者结束的;对于两个字符串的比较,可以使用”>“”<=“等操作符,也可以使用compare()函数。
  QString中toInt()、toDouble()等函数可以很方便的将字符串转换为整型或者double型数据,转换成功后,它们的第一个bool型参数会为true;使用静态函数number()可以将数值转换为字符串,这里还可以指定要转换为哪种进制;使用toLower()和toUpper()函数可以分别返回字符串小写和大写形式的副本;arg()函数中的参数可以取代字符串中相应”%1“等标记,字符串中可以使用的标记在1~99之间,arg()函数会从最小的数字开始对应,比如QString(%5,%2,%7).arg(“a”).arg(“b”),那么"a"会代替“%2”,“b"会代替”%5”,而"%7"会直接显示。arg()的一种重载形式是arg(const QString &a1,const QString &a2),它与使用str.arg(a1).arg(a2)是相同的,不过当参数a1中函数”%1“等标记时,两者的效果是不同的,该函数的另一种重载形式arg(const QString &a,int fieldWidth = 0,const QChar &fillChar = QLatin1Char(’ ‘),这里可以设置字段宽度,如果第一个参数a的宽度小于fieldWidth的值,那么就可以使用第三个参数设置的字符来进行补充。这里的fieldWidth如果是正值,那么文本是右对齐的,而如果为负值,那么文本是左对齐的。arg()还有一种重载形式arg(double a,int fieldWidth = 0,char format =‘g’,int precision = -1,const QChar &fillChar = QLatin1Char(’ '),它的第一个参数参数是double类型的,后面的format和precision可以分别指定其类型和参数。
  还有一个qPrintable()函数,他不是QString中的函数,但是可以将字符串转换为const char* 类型,当在输出一个字符串的时候,两边总会有引号,为了显示更加清晰,可以使用这个函数将引号去掉。QString中还提供了toAscii()、toLatin1()、toUtf8()和toLocal8Bit()等函数,可以使用一种编码将字符串转换为QByteArray类型。

QByteArray和QVariant

  QByteArray类提供了一个字节数组,它可以用来存储原始字节(包含’\0’)和传统的以‘\0’结尾的8为字符串。使用QByteArray比使用const char*要方便得多,在后台,他总是保证数据以一个’\0’结尾,而且使用隐式共享来减少内存的使用和避免不必要的数据复制。但是除了当需要存储原始二进制数据或者对内存保护要求很高时,一般都推荐使用QString,因为QString存储16位的Unicode字符,使得在应用程序中更容易存储非ASCII和非Latin -1字符,而且QString全部使用的是Qt的API。
  QVariant类像是常见的Qt数据类型的一个共用体,一个QVariant对象在一个时间只保存一个单一类型的,可以使用toT()函数将QVariant对象转换为T类型,并且获取它的值。这里toT()函数恢复至以前的QVariant对象,然后对其进行胡暂缓,所以以前的QVariant对象并不会改变。QVariant是Qt中一个很重要的类,比如QObject::property()返回的就是QVariant类型的对象。
  QVariant类的toInt()函数返回int类型的值,toFloat()函数返回float类型的值。但是因为QVariant是Qt Core库的一部分,所以它没有提供对 Qt GUI模块中定义的数据类型(QColor、QImage、QPixmap等)进行转换的函数,也就是说,这里没有toColor()这样的函数。不过,可以使用QVariant::value()函数或者qvariant_cast()模板函数来完成这样的转换。对于一个类型是否可以转换为一个特殊的类型,可以使用canCovert()函数来判断,如果可以转换,则该函数返回true。也可以使用convert()函数来将一个类型转换为不同的类型,如果成功则返回true,如果无法进行转换,variant对象将会被清空,并且返回false。

类型自动转换到
BoolChar、Double、Int、LongLong、String、UInt、ULongLong
ByteArrayDouble、Int、LongLong、String、UInt、ULongLong
CharBool、Int、UInt、LongLong、ULongLong
ColorString
DateDateTime、String
DateTimeDate、String、Time
DoubleBool、Int、LongLong、String、UInt、ULongLong
FontString
IntBool、Char、Double、LongLong、String、UInt、ULongLong
KeySequenceInt、String
ListStringList
LongLongBool、ByteArray、Char、Double、Int、String、UInt、ULongLong
PointPointF
RectRecrF
StringBool、ByteArray、Char、Color、Date、DateTime、Double、Font、Int、KeySequence、LongLong、StringList、Time、UInt、ULongLong
StringListList、String
TimeString
UIntBool、Char、Double、Int、LongLong、String、ULongLong
ULongLongBool、Char、Double、Int、LongLong、String、UInt

正则表达式

  正则化就是在一个文本中匹配子字符串的一种模式,它可以简写为regexp。一个regexp主要应用在以下几个方面:
   >验证。regexp可以测试一个子字符串是否符合一些规范。例如,是否是一个整数或者不包含任何空格等。
   >搜索。regexp提供了比简单的子字符串匹配更强大的模式匹配。例如,匹配单词mail或者letter,而不匹配单词email、mailman或者letterbox。
   >查找和替换。regexp可以使用一个不同的字符串替换所有匹配的子字符串。例如,使用Mail来替换一个字符串中所有的M字符,但是M字符后面有ail时则不进行替换。
   >字符串分割。regexp可以识别在哪里进行字符串分割。例如,分割制表符隔离的字符串。
  Qt中的QRegExp类实现了使用正则表达式进行模式匹配。QRegExp是以Perl的正则表达式语言为蓝本的,它完全支持Unicode。QRegExp中的语法规则可以使用setPatternSyntax()函数来更改。

正则化表达式介绍

  Regexps由表达式、量词和断言组成。最简单的一个表达式就是一个字符,比如x和5。而一组字符可以使用方括号括起来,例如,[ABC]将会匹配一个A或者一个B或者一个C,这个也可以简写为[A-C],这样若要匹配所有的英文大写字母,就可以使用[A-Z]。
  一个量词制定了必须要匹配的表达式出现的次数。例如,x{1,1}意味着必须匹配且只能匹配一个字符x,而x{1,5}意味着匹配一列字符x,其中至少要包含一个字符x,但是最多包含5个字符x。
  现在假设要使用一个regexp来匹配0~99之间的整数。因为至少要有一个数字,所以使用表达式[0-9]{1,1}开始,它会匹配一个单一的数字一次。要匹配0 ~99,则可以想到将表达式最多出现的次数设置为2,即[0-9]{1,2}。现在这个regexp已经可以满足我们假设的需要了,不过,它也会匹配出现在字符串中间的整数。如果想匹配的整数是整个字符串,那么就需要使用断言“^”和“ $ ”,当“ ^ ”在regexp中作为第一个字符时,意味着这个regexp必须从字符串的开始进行匹配;当“ $ ”在regexp中作为最后一个字符时,意味着regexp必须匹配到字符串的结尾。所以,最终的regexp为“ ^ [0-99] {1,2}$”
  一般可以使用一些特殊的符号来表示一些常见的字符组和量词。例如,[0-9]可以使用“\d”来替代。而对于只出现一次的量词{1,1},则可以使用表达式本身代替,例如1,x{1,1}等价于x。所以要匹配0 ~ 99,就可以写成"^ \d{1,2} $“或者“^ \d\d{0,1} $ ”。而{0,1}表示字符是可选的,就是只出现一次或者不出现,它可以使用“?”替代,这样regexp就可以写成”^ \d\d? $"它意味着从字符串的开始,匹配一个数字,紧接着是0个或一个数字,再后面就是字符串的结尾。
  现在写一个regexp来匹配单词mail或者letter其中的一个,但是不要匹配那些包含这些的单词,比如email和letterbox。要匹配mail,regexp可以写成m{1,1},a{1,1},i{1,1},l{1,1},因为{1,1}可以省略,所以又可以简写成mail。下面就可以使用竖线“|”来包含另一个单词,这里“|”表示“或”的意思。为了避免regexp匹配多余的单词,必须让它从单词的边界进行匹配。首先,将regexp用括号括起来,即(mail|letter)。括号将表达式组合在一起,可以在一个更复杂的regexp中作为一个组件来使用,这样也可以方便我们检查到底是哪一个单词被匹配了。为了强制匹配的开始和结束都在单词的边界上,就要将regexp包含在“\b”单词边界断言中,即“\b(mail|letter)\b”。这些“\b”断言在regexp中匹配一个位置,而不是一个字符,一个单词的边界是任何的非单词字符,如一个空格、新行、或者一个字符串的开始或者结束。
  如果想使用一个单词,如Mail,替换一个字符串的字符M,但是当字符M的后面是ail的化就不用再替换。这样可以使用(?!E)断言。例如,这里regexp应该写成M(?!Mail)。
  如果想统计Eric和Eirik在字符串中出现的次数,则可以使用\b(Eric|Eirik)\b或者\bEi?ri[ck]\b。这里需要使用单词边界断言’\b’来避免匹配那些包含了这些名字的单词。
  这里需要说明一下,因为C++中“\”也是转义字符,所以要在regexp使用它时,需要再转义一次,比如使用“\d”就应该写成“\d”;如果要使用’'本身,那么就要写成“\\”。

正则表达式组成元素

正则表达式中的字符和字符集缩写如下:

元素含义
c一个字符代表它本身,除非这个字符有特殊的regexp含义。例如,c匹配字符c
\c跟在反斜杠后面的字符匹配字符本身,但是本表中下面指定的这些字符除外。例如,要匹配一个字符串的开头,使用“^”
\a匹配ASCII的振铃
\f匹配ASCII的换页
\n匹配ASCII的换行
\r匹配ASCII的回车
\t匹配ASCII的水平制表符
\v匹配ASCII的垂直制表符
\xhhhh匹配Unicode字符对应的十六进制hhhh(在0x0000 ~ 0xFFFF之间)
\0ooo匹配八进制的ASCII/Latin1字符ooo(在0 ~ 377之间)
.(点)匹配任意字符(包括新行)
\d匹配一个数字
\D匹配一个非数字
\s匹配一个空白字符,包括“\t”、“\n”、“\v”、“\f”、“\r”、和“”
\S匹配一个非空白字符
\w匹配一个单词字符,包括任意一个字母或数字或下划线,即A ~ Z ,a ~ z,0 ~ 9,_中任意一个
\W匹配一个非单词字符
\n第n个反向引用。例如,\1,\2等。

  字符集还有两个特殊的符号“^”和“-”,其中“ ^ ”在方括号的开始可以表示相反的意思,例如,[ ^ abc]表示匹配任何字符,但是不匹配a或者b或c。而“-”可以表示一个范围的字符,例如,[W-Z]表示匹配W或者X或者Z。
  正则表达式的量词使用如下

量词含义
E?匹配0次或者1次,表明E是可选的,E?等价于E{0,1}
E+匹配1次或者多次,E+等价于E{1,},例如,0+匹配0、00、000等
E*匹配0次或者多次,等级与E{0,}
E{n}匹配n次,等于E{n,n},例如,x{5}等价于x{5,5},也等价于xxxxx
E{n,}匹配至少n次
E{,m}匹配至多m次,等价于E{0,M}
E{n,m}匹配至少n次,至多m次

  在使用量词时要注意,tag+表示匹配一个t要跟着一个a,然后跟着至少一个g,而(tag)+表示匹配tag至少一次。还要说明的是,量词一般是“贪婪的”,它会尽可能多的去匹配可以匹配的文本,例如,0+匹配表示他发现的第一个0及其随后所有连续的0,当应用到字符串20005时,它会匹配其中的3个0。要使量词变得“非贪婪”,则可以使用setMinimal(true),这样在上面的例子中就会只匹配一个0。
  断言在regexp中做出一些有关文本的声明,它们不匹配任何字符,正则表达式中的断言如下:

断言含义
^标志着字符串的开始,如果要匹配“^”就要使用“\\”
$标志着字符串的结尾。如果要匹配“$”就要使用“\\$”
\b一个单词的边界
\B一个非单词的边界,当“\b”为false时,它为true
(?=E)表达式后面紧跟着E才匹配.例如,const(?=\s+char)匹配const且其后必须有char
(?!E)表达式后面没有紧跟着E才匹配,例如,const(?!\s+char)匹配const但其后不能有char

  QRegxExp类还支持通配符匹配。很多命令shell(例如bash和cmd.exe)都支持文件通配符,可以使用通配符来识别一组文件。QRegExp的setPatternSyntax()函数就是用来在regexp和通配符之间进行切换的。通配符匹配要比regexp简单得多,它只有4个功能,如下表:

字符含义
c任意一个字符,表示字符本身
匹配任意一个字符,类似于regexp中的“.”
*匹配0个或者多个任意的字符,类似于regexp中的“.*“
[…]在方括号中的字符串,与regexp中的类似

  除了通配符之外,QRegExp还支持其他一些语法,这些语法都可以使用setPatternSyntax()函数来切换。

常量描述
QRegExp::RegExp类似于Perl的模式匹配语法,这个是默认语法
QRegExp::RegExp类似于RegExp,不过是一种贪婪匹配语法
QRegExp::Wildcard一种简单的模式匹配1语法
QRegExp::WildcardUnix与Widcard类似,但是使用Unix shell的行为
QRegExp::FixedString使用字符原意,不使用任何的转义字符
QRegExp::W3CXmlSchemall在W3C XML Schema 1.1规范中定义的一种正则表达式

文本捕捉

  在regexp中使用括号可以使一些元素组合在一起,这样既可以对它们进行量化,也可以捕获它们。例如,使用表达式mail|letter来匹配一个字符串,知道有一个单词被匹配了,但是却不能知道具体是哪一个,使用括号就可以捕获被匹配的那个单词。比如使用(mail|letter)来匹配字符串”I Sent you som email“,这样就可以使用cap()或者capturedTexts()函数来提取匹配的字符。
  还可以在regexp中使用捕获到的文本,为了表示捕获到的文本,使用反向引用”\n“,其中n从1开始编号,比如”\1“表示前面第一个捕获到的文本。例如,使用”\b(\w+)\W+\1\b“在一个字符串中查询重复出现的单词,这意味着先匹配一个单词边界,随后是一个或者多个单词字符,随后是一个或者多个非单词字符,随后是一个或者多个非单词字符,随后是与前面第一个括号中相同的文本,随后是单词边界。
  如果使用括号仅仅是为了组合元素而不是为了捕获文本,那么可以使用非捕获语法,如”(?:green|blue)“。非捕获括号由”(?:“开始,由”)“结束,使用非捕获括号比使用捕获括号更高效,因为regexp引擎只需做较少的操作。

新的QRegularExpression类

  Qt5中引入了新的QRegularExpression类,实现了与Perl兼容的正则表达式,并在QRegExp基础上进行了很大的改进。建议编写Qt 5程序时使用QRegularExpression来代替QRegExp。
  在QRegularExpression中,一个正则表达式由两部分构成:一个模式字符串和一组模式选项,模式选项用来更改模式字符串的含义。可以在构造函数中直接设置模式字符串:

QRegularExpression re("a pattern");

  也可以使用setPattern()为已有的QRegularExpression对象设置模式字符串:

QRegularExpression re;
re.setPattern("another pattern");

  可以通过pattern()来获取已设置的模式字符串。QRegularExpression中通过模块选项来改变模式字符串的含义。例如,可以通过设置QRegularExpression::CaseInsensitiveOption使匹配不区分字母大小写:

QRegularExpression re("Qt rocks",QRegularExpression::CaseInsensitiveOption);

  这时除了匹配 Qt rocks,还会匹配QT rocks、QT ROCKS、QT rOcKs等字符串。也可以通过setPatternOption()来设置模式选项,通过patternOptions()获取设置的模式选项:

QRegularExpression re("^\\d + $");
re.setPatternOptions(QRegularEpression::MultilineOption);
QRegularExpression::PatternOption options = re.patternOption();

  模式选项由QRegularExpression::PatternOption枚举类型进行定义,其取值如下:

常量描述
QRegularExpression::NoPatternOption()没有设置模式选项
QRegularExpression::CaseInsensitiveOption匹配目标字符串时不区分大小写
QRegularExpression::DotMatchesEverythingOption”.“匹配任意字符,包括换行符
QRegularExpression::MultilineOption”^“匹配字符串的开始和新行的开始,"$"匹配任意行的结尾
QRegularExpression::ExtendedPatternSyntaxOption忽略所有空白,"#"后面的内容作为注释,用于提高可读性
QRegularExpression::InvertedGreedinessOption返转量词的贪婪
QRegularExpression::DontCaptureOption未命名捕获组不捕获子字符串,命名捕获组正常执行
QRegularExpression::UseUnicodePropertiesOption\w、\d等字符类不再只匹配ASCII字符,而是匹配相应Unicode属性的任意字符
QRegularExpression::正则表达式在第一次使用时被优化,而不再是用几次之后才优化1
QRegularExpression::正则表达式默认在使用一定次数后会被自动优化,使用该选项可以防止此类优化,这样就可以避免CPU和内存使用率出现不可预测的峰值
  • 1
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值