目录
(一). Qt 6 框架中的模块
Qt 是一个跨平台开发框架,它包含很多功能模块。 Qt 框架中的模块主要分为以下两大类:
• Qt Essentials:Qt 框架的基础模块,这些模块提供了 Qt 在所有平台上的基本功能。在安装 Qt 时,这些基础模块是自动安装的,无须选择。
• Qt Addons:Qt 框架的附加模块,这些是实现一些特定功能的模块。安装时的可选组件都是附加模块。
一. Qt 基础模块
Qt 框架的基础模块提供了 Qt 在所有平台上的基本功能,它们在所有开发平台上都可用,在 Qt 6 所有版本上源代码和二进制代码是兼容的。Qt 6.2 框架的基础模块如下表所示:
模块 | 功能 |
Qt Core | 这个模块是 Qt 框架的核心,定义了元对象系统对标准 C++进行扩展 |
Qt GUI | 提供用于 GUI 设计的一些基础类,可用于窗口系统集成、事件处理、字体和文字处理等 |
Qt Network | 提供实现 TCP/IP 网络通信的一些类 |
Qt Widgets | 提供用于创建 GUI 的各种界面组件类 |
Qt D-Bus | D-Bus 是实现进程间通信(inter process communication,IPC)和远程过程调用(remote procedure call,RPC)的一种通信协议,这个模块提供实现 D-Bus 通信协议的一些类 |
Qt Test | 提供一些对应用程序和库进行单元测试的类 |
Qt QML | 提供用 QML 编程的框架,它定义了 QML 和基础引擎 |
Qt Quick | 这个模块是用于开发 QML 应用程序的标准库,提供创建 UI 的一些基本类型 |
Qt Quick Controls | 提供一套基于 Qt Quick 的控件,可用于创建复杂的 UI |
Qt Quick Dialogs | 提供通过 QML 使用系统对话框的功能 |
Qt Quick Layouts | 提供用于管理界面布局的 QML 类型 |
Qt Quick Test | 提供 QML 应用程序的单元测试框架 |
Qt Core 模块是 Qt 框架的核心,其他模块都依赖此模块。Qt GUI 模块提供用于开发 GUI 应用程序的必要的类。在创建 GUI 项目时,qmake 项目配置文件中会自动加入如下语句:
QT += core gui
二. Qt 附加模块
Qt 附加模块是一些能够实现特定功能的模块,用户安装 Qt 时可以选择性地安装这些附加模块。 下表所示为 Qt 6.2 框架的附加模块,表中未列出一些过时的模块以及只用于 QML 编程的模块。
模块 | 功能 |
Active Qt | 用于开发使用 ActiveX 和 COM 控件的 Windows 应用程序 |
Qt 3D | 支持二维和三维图形渲染,用于开发近实时的仿真系统 |
Qt 5 Core Compatibility | 提供一些 Qt 5 中有而 Qt 6 中没有的 API,这是为了兼容 Qt 5 |
Qt Bluetooth | 提供访问蓝牙硬件的功能 |
Qt Charts | 提供用于数据显示的一些二维图表组件 |
Qt Concurrent | 提供一些类,使我们无须使用底层的线程控制就可以编写多线程应用程序 |
Qt Data Visualization | 提供用于三维数据可视化显示的一些类 |
Qt Help | 提供一些在应用程序中集成帮助文档的类 |
Qt Image Formats | 支持附加图片格式的插件,格式包括 TIFF、MNG 和 TGA 等 |
Qt Multimedia | 提供处理多媒体内容的一些类,处理方式包括播放音频和视频,通过麦克风和摄像头录制音频和视频 |
Qt Network Authorization | 使 Qt 应用程序能访问在线账号或 HTTP 服务,而又不暴露用户密码 |
Qt NFC | 提供访问近场通信(near field communication,NFC)硬件的功能 |
Qt OpenGL | 提供一些便于在应用程序中使用 OpenGL 的类 |
Qt Positioning | 通过 GPS 或 WiFi 定位,为应用程序提供定位信息 |
Qt Print Support | 提供一些用于打印控制的类 |
Qt Remote Objects | 提供一种进程间通信技术,可以在进程间或计算机之间方便地交换信息 |
Qt SCXML | 用于通过 SCXML(有限状态机规范)文件创建状态机 |
Qt Sensors | 提供访问传感器硬件的功能,传感器包括加速度计、陀螺仪等 |
Qt Serial Bus | 提供访问串行工业总线(如 CAN 和 Modbus 总线)的功能 |
Qt Serial Port | 提供访问兼容 RS232 引脚的串行接口的功能 |
Qt Shader Tools | 提供用于三维图形着色的工具 |
Qt SQL | 提供一些使用 SQL 操作数据库的类 |
Qt SVG | 提供显示 SVG 图片文件的类 |
Qt UI Tools | 提供一些类,可以在程序运行时加载用 Qt Designer 设计的 UI 文件以动态创建 UI |
Qt Virtual Keyboard | 实现不同输入法的虚拟键盘 |
Qt Wayland Compositor | 实现了 Wayland 协议,能创建用户定制的显示服务 |
Qt WebChannel | 用于实现服务器端(QML 或 C++应用程序)与客户端(HTML/ JavaScript 或 QML 应用程 序)进行 P2P 通信 |
Qt WebEngine | 提供一些类和函数,通过 Chromium 浏览器项目实现在应用程序中嵌入显示动态网页 |
Qt WebSockets | 提供 WebSocket 通信功能。WebSocket 是一种 Web 通信协议,可实现客户端程序与远程主 机的双向通信 |
(二). Qt 全局定义
头文件<QtGlobal>包含 Qt 框架中的一些全局定义,包括基本数据类型、函数和宏。一般的 Qt 类的头文件都会包含这个头文件,所以用户程序中无须包含这个头文件就可以使用其中的定义。
一. 数据类型定义
为了确保在各个平台上各种基本数据类型都有统一确定的长度,Qt 为各种常见数据类型定义了类型符号,例如 qint8 就是 signed char 的类型定义:
typedef signed char qint8;
<QtGlobal>中定义的数据类型如下表所示:
Qt 数据类型 | POSIX 标准等效定义 | 字节数 |
qint8 | signed char | 1 |
qint16 | signed short | 2 |
qint32 | signed int | 4 |
qint64 | long long int | 8 |
qlonglong | long long int | 8 |
quint8 | unsigned char | 1 |
quint16 | unsigned short | 2 |
quint32 | unsigned int | 4 |
quint64 | unsigned long long int | 8 |
quint64 | unsigned long long int | 8 |
uchar | unsigned char | 1 |
ushort | unsigned short | 2 |
ushort | unsigned int | 4 |
ulong | unsigned long | 8 |
qreal | double | 8 |
qsizetype | ssize_t | 8 |
qfloat16 | — | 2 |
qreal 默认表示 8 字节 double 类型的浮点数,如果 Qt 使用-qreal float 选项进行配置,就表示 4 字节 float 类型的浮点数。
qfloat16 是 Qt 5.9 中增加的一种类型,用于表示16位的浮点数。qfloat16 不是在头文件中定义的,要使用 qfloat16,需要包含头文件。
qsizetype 是在 Qt 5.10 中增加的一种类型,等效于 POSIX 标准中的 ssize_t,表示有符号整数。
二. 函数
头文件<QtGlobal>中包含一些常用函数的定义,这些函数多以模板类型作为输入和输出参数类型,模板类型可以是上表中所示的各种整数类型。若以 double 或 float 类型作为参数类型,一般有两个 overload 型同名函数,例如 qFuzzyIsNull(double d)和 qFuzzyIsNull(float f)。
下表所示为中常用的全局函数定义,列出了函数的输入和输出参数。若存在 double 和 float 两种类型参数的 overload 型函数,只列出 double 类型参数的函数。
函数原型 | 功能 |
T qAbs(const T &value) | 返回变量 value 的绝对值 |
const T &qBound(const T &min, const T &value, const T &max) | 返回 value 限定在 min~max 的值 |
T qExchange(T &obj, U &&newValue) | 将 obj 的值用 newValue 替换,返回 obj 的旧值 |
int qFpClassify(double val) | 返回 val 的分类,包括 FP_NAN(非数)、FP_INFINITE(正或负的无穷大)、FP_ZERO(零)等几种类型 |
bool qFuzzyCompare(double p1, double p2) | 若 p1 和 p2 近似相等,返回 true |
bool qFuzzyIsNull(double d) | 若参数 d 约等于 0,返回 true |
double qInf() | 返回无穷大的数 |
bool qIsFinite(double d) | 若 d 是一个有限的数,返回 true |
bool qIsInf(double d) | 若 d 是一个无穷大的数,返回 true |
bool qIsNaN(double d) | 若 d 为非数,返回 true |
const T &qMax(const T &value1, const T &value2) | 返回 value1 和 value2 中的较大值 |
const T &qMin(const T &value1, const T &value2) | 返回 value1 和 value2 中的较小值 |
qint64 qRound64(double value) | 将 value 近似为最接近的 qint64 类型整数 |
int qRound(double value) | 将 value 近似为最接近的 int 类型整数 |
还有一些基础的数学运算函数在头文件中定义,这些函数,如三角运算函数、弧度与角度的转换函数等。
三. 宏定义
<QtGlobal>头文件中定义了很多宏,以下这些宏是比较常用的:
• QT_VERSION 表示 Qt 版本。这个宏展开为数值形式 0xMMNNPP。例如 Qt 版本为 Qt 6.2.3, 则 QT_VERSION 为 0x060203。
• Q_BYTE_ORDER 表示系统内存中数据的字节序。Q_BIG_ENDIAN 表示大端字节序, Q_LITTLE_ENDIAN 表示小端字节序。在需要判断系统字节序时可以用到这几个宏,例如:
#if Q_BYTE_ORDER == Q_LITTLE_ENDIAN
...
#endif
• Q_DECL_IMPORT和Q_DECL_EXPORT分别用于在使用或设计共享库时导入或导出库的内容。
• Q_UNUSED(name)用于声明函数中未被使用的参数。当函数的输入参数在函数对应的代码里未被使用时,需要用这个宏声明,否则会出现编译警告。
• foreach(variable, container)用于遍历容器的内容。
• qDebug(const char *message, ...)用于在 debugger 窗口显示信息。在 Qt Creator 中,debugger 窗口就是 Application Output 窗口。qDebug()输出数据的格式与 C 语言的 printf()函数的类似,可以格式化输出各种数据。qDebug()一般用于调试程序时显示一些中间信息。
(三). Qt 的元对象系统
Qt 中引入了元对象系统对标准 C++语言进行扩展,增加了信号与槽、属性系统、动态翻译等特性,为编写 GUI 应用程序提供了极大的方便。
一. 元对象系统概述
Qt 的元对象系统的功能建立在以下 3 个方面:
• QObject 类是所有使用元对象系统的类的基类。
• 必须在一个类的开头部分插入宏 Q_OBJECT,这样这个类才可以使用元对象系统的特性。
• MOC 为每个 QObject 的子类提供必要的代码来实现元对象系统的特性。
构建项目时,MOC 会读取 C++源文件,当它发现类的定义里有 Q_OBJECT 宏时,它就会为这个类生成另一个包含元对象支持代码的 C++源文件,这个生成的源文件连同类的实现文件一起被标准 C++编译器编译和连接。
1.QObject 类
QObject 类是所有使用元对象系统的类的基类,也就是说,如果一个类的父类或上层父类是 QObject,它就可以使用信号与槽、属性等特性。QObject 类与元对象系统特性相关的一些接口函数如下表所示,表中列出了函数的返回值类型等,并且省略了函数前后的 const 关键字。
特性 | 函数 | 功能 |
元对象 | QMetaObject *metaObject() | 返回这个对象的元对象 |
QMetaObject staticMetaObject | 这是类的静态变量,不是函数,存储了类的元对象 | |
类型信息 | bool inherits() | 判断这个对象是不是某个类的子类的实例 |
动态翻译 | QString tr() | 类的静态函数,返回一个字符串的翻译版本 |
对象树 | QObjectList &children() | 返回子对象列表 |
QObject *parent() | 返回父对象指针 | |
void setParent() | 设置父对象 | |
T findChild() | 按照对象名称,查找可被转换为类型 T 的子对象 | |
QList<T> findChildren() | 返回符合名称和类型条件的子对象列表 | |
信号与槽 | QMetaObject::Connection connect() | 设置信号与槽关联 |
bool disconnect() | 解除信号与槽的关联 | |
bool blockSignals() | 设置是否阻止对象发射任何信号 | |
bool signalsBlocked() | 若返回值为 true,表示对象被阻止发射信号 | |
属性系统 | QList<QByteArray> dynamicPropertyNames() | 返回所有动态属性名称 |
bool setProperty() | 设置属性值,或添加动态属性 | |
QVariant property() | 返回属性值 |
元对象系统的特性是通过 QObject 的一些函数来实现的。
(1)元对象(meta object)。每个 QObject 及其子类的实例都有一个元对象,这个元对象是自 动创建的。静态变量 staticMetaObject 就是这个元对象,函数 metaObject()返回这个元对象指针。 所以,获取一个对象的元对象有两种方式,示意代码如下:
QPushButton *btn= new QPushButton();
const QMetaObject *metaPtr= btn->metaObject(); //获取元对象指针
const QMetaObject metaObj= btn->staticMetaObject; //获取元对象
(2)类型信息。QObject 的inherits()函数可以判断对象是不是从某个类继承的类的实例。
(3)动态翻译。函数tr()用于返回一个字符串的翻译版本,在设计多语言界面的应用程序时需要用到 tr()函数。
(4)对象树(object tree)。对象树指的是表示对象间从属关系的树状结构。例如在一个窗口上,组件都有父容器,窗口是界面上所有组件的顶层容器。QObject 类的 parent()函数返回其父对象, children()函数返回其子对象,findChildren()函数可以返回某些子对象或所有子对象。窗口和窗口上 的组件就构成对象树,窗口可以访问任何一个界面组件。对象树中的某个对象被删除时,它的子 对象会被自动删除,所以,一个窗口被删除时,它上面的所有界面组件也会被自动删除。
(5)信号与槽。通过在一个类的定义中插入宏 Q_OBJECT,我们就可以使用 Qt 扩展的 C++ 语言特性编程,例如在一个类中定义属性、类信息、信号和槽函数。
(6)属性系统。在类的定义代码中可以用宏 Q_PROPERTY 定义属性,QObject 的 setProperty() 函数会设置属性的值或定义动态属性;property()函数会返回属性的值。
2.QMetaObject 类
每个 QObject 及其子类的实例都有一个自动创建的元对象,元对象是 QMetaObject 类型的实例。元对象存储了类的实例所属类的各种元数据,包括类信息元数据、方法元数据、属性元数据等,所以,元对象实质上是对类的描述。
QMetaObject 类的主要接口函数如下表所示,表中列出了函数原型,但是省略了输入参数以及函 数前后的 const 关键字。当函数的参数太多,在表格中不便于显示时,我们就用“****”替代表示。注意,表格中的“这个元对象”指的是一个 QObject 实例的元对象,“这个类”指的是元对象所描述的类。
分组 | 函数原型 | 功能 |
类的信息 | char *className() | 返回这个类的类名称 |
QMetaType metaType() | 返回这个元对象的元类型 | |
QMetaObject *superClass() | 返回这个类的上层父类的元对象 | |
bool inherits(QMetaObject *metaObject) | 返回 true 表示这个类继承自 metaObject 描述的类,否则 返回 false | |
QObject *newInstance(****) | 创建这个类的一个实例,可以给构造函数传递最多10个参数 | |
类信息元数据 | QMetaClassInfo classInfo(int index) | 返回序号为 index 的一条类信息的元数据,类信息是在类 中用宏 Q_CLASSINFO 定义的一条信息 |
int indexOfClassInfo(char *name) | 返回名称为name的类信息的序号,序号可用于classInfo()函数 | |
int classInfoCount() | 返回这个类的类信息条数 | |
int classInfoOffset() | 返回这个类的第一条类信息的序号 | |
构造函数元数据 | int constructorCount() | 返回这个类的构造函数的个数 |
QMetaMethod constructor(int index) | 返回这个类的序号为 index 的构造函数的元数据 | |
方法元数据 | int indexOfConstructor(char *constructor) | 返回一个构造函数的序号,constructor 包括正则化之后的 函数名和参数名 |
QMetaMethod method(int index) | 返回序号为 index 的方法的元数据 | |
int methodCount() | 返回这个类的方法的个数,包括基类中定义的方法,方 法包括一般的成员函数,还包括信号和槽 | |
int methodOffset() | 返回这个类的第一个方法的序号 | |
int indexOfMethod(char *method) | 返回名称为 method 的方法的序号 | |
枚举类型元数据 | QMetaEnum enumerator(int index) | 返回序号为 index 的枚举类型的元数据 |
int enumeratorCount() | 返回这个类的枚举类型个数 | |
int enumeratorOffset() | 返回这个类的第一个枚举类型的序号 | |
int indexOfEnumerator(char *name) | 返回名称为 name 的枚举类型的序号 | |
属性元数据 | QMetaProperty property(int index) | 返回序号为 index 的属性的元数据 |
int propertyCount() | 返回这个类的属性的个数 | |
int propertyOffset() | 返回这个类的第一个属性的序号 | |
int indexOfProperty(char *name) | 返回名称为 name 的属性的序号 | |
信号与槽 | int indexOfSignal(char *signal) | 返回名称为 signal 的信号的序号 |
int indexOfSlot(char *slot) | 返回名称为 slot 的槽函数的序号 | |
静态函数 | bool checkConnectArgs(****) | 检查信号与槽函数的参数是否兼容 |
void connectSlotsByName(QObject *object) | 迭代搜索 object 的所有子对象,将匹配的信号和槽连 接起来 | |
bool invokeMethod(****) | 运行 QObject 对象的某个方法,包括信号、槽或成员 函数 | |
QByteArray normalizedSignature(char *method) | 将方法 method 的名称和参数字符串正则化,去除多 余空格。函数返回的结果可用于 checkConnectArgs()、 indexOfConstructor()等函数 |
通过 QMetaObject 类的这些函数,我们可以在运行时获取一个 QObject 对象的类信息和各种元数据。例如,函数 className()可返回类的名称,函数 superClass()可返回其父类的元对象,函数 newInstance()可以创建元对象所描述类的一个新的实例。
类的元数据又分为多种类型,且有专门的类来描述。例如,函数 property()返回属性的元数据, 属性元数据用 QMetaProperty 类描述,它的接口函数描述了属性的各种特性,如函数 name()返回属性名称,函数 type()返回属性数据类型。
二. 运行时类型信息
通过使用 QObject 和 QMetaObject 提供的以下一些接口函数,我们可以在运行时获得一个对象的类名称以及其父类的名称, 判断其是否从某个类继承而来。要实现这些功能,我们并不需要C++编译器的运行时类型信息(run-time type information,RTTI)支持。
(1)函数 QMetaObject::className()。这个函数可在运行时返回类名称的字符串,例如:
QPushButton *btn= new QPushButton();
const QMetaObject *meta= btn->metaObject(); //获取对象的元对象指针
QString str= QString(meta->className()); //str= "QPushButton"
(2)函数 QObject::inherits()。这个函数可以判断一个对象是不是继承自某个类的实例,顶层的父类是 QObject。例如:
QPushButton *btn= new QPushButton();
bool result= btn->inherits("QPushButton"); //true
result= btn->inherits("QObject"); //true
result= btn->inherits("QWidget"); //true
result= btn->inherits("QCheckBox"); //false
如果一个类是多重继承的,其中一个父类是 QObject,那么 inherits("QObject")会返回 true。
(3)函数 QMetaObject::superClass()。这个函数返回该元对象所描述类的父类的元对象,通过 父类的元对象可以获取父类的一些元数据,例如:
QPushButton *btn= new QPushButton();
const QMetaObject *meta= btn->metaObject();
QString str1= QString(meta->className()); //str1="QPushButton"
const QMetaObject *metaSuper= btn->metaObject()->superClass();
QString str2= QString(metaSuper->className()); //str2="QAbstractButton"
QPushButton的父类是QAbstractButton,所以 str1 是“QPushButton”,str2是“QAbstractButton”。
4)函数 qobject_cast()。这个函数是头文件中定义的一个非成员函数,对于 QObject 及其子类的对象,可以使用函数 qobject_cast()进行动态类型转换。如果自定义的类要支持函数 qobject_cast(),那么自定义的类需要直接或间接继承自QObject,且在类定义中插入宏 Q_OBJECT。
例如,下面的代码段演示了函数 qobject_cast()的作用。
QObject *btn= new QPushButton(); //创建 QPushButton,但使用 QObject 类型指针
const QMetaObject *meta= btn->metaObject();
QString str1= QString(meta->className()); //str1= "QPushButton"
QPushButton *btnPush= qobject_cast<QPushButton*>(btn); //转换成功
const QMetaObject *meta2= btnPush->metaObject();
QString str2= QString(meta2->className()); //str2= "QPushButton"
QCheckBox *chkBox= qobject_cast<QCheckBox*>(btn); //转换失败,chkBox 是 nullptr
在第一行代码中,程序创建的是 QPushButton 类对象,用一个 QObject 类型指针 btn 指向这个对象。获取 btn 的元对象,元对象的 className()的返回值是“QPushButton”。 第二部分使用函数 qobject_cast()将 btn 转换为 QPushButton 类型指针:
QPushButton *btnPush= qobject_cast<QPushButton*>(btn); //转换成功
这个转换是成功的,因为 btn 就是 QPushButton 对象指针。但是,如果将 btn 转换为 QCheckBox 对象指针就会失败,因为 QCheckBox 不是 QPushButton 的父类。
标准 C++语言中有类似的强制类型转换函数 dynamic_cast(),使用 qobject_cast()的好处是不需要 C++编译器开启 RTTI 支持。
三. 属性系统
1.属性定义
属性是 Qt C++的一个扩展的特性,是基于元对象系统实现的,标准 C++语言中没有属性。在 QObject 的子类中,我们可以使用宏 Q_PROPERTY 定义属性,其格式如下:
Q_PROPERTY(type name
(READ getFunction [WRITE setFunction] |
MEMBER memberName [(READ getFunction | WRITE setFunction)])
[RESET resetFunction]
[NOTIFY notifySignal]
[REVISION int | REVISION(int[, int])]
[DESIGNABLE bool]
[SCRIPTABLE bool]
[STORED bool]
[USER bool]
[BINDABLE bindableProperty]
[CONSTANT]
[FINAL]
[REQUIRED])
宏 Q_PROPERTY 定义一个值类型为 type,名称为 name 的属性,用 READ、WRITE关键字分别定义属性的读取、写入函数,还有一些其他关键字用于定义属性的一些操作特性。属性值的类型可以是 QVariant 支持的任何类型,也可以是自定义类型。
宏 Q_PROPERTY 定义属性的一些主要关键字的含义如下:
• READ:指定一个读取属性值的函数,没有 MEMBER 关键字时必须设置 READ。
• WRITE:指定一个设置属性值的函数,只读属性没有 WRITE 配置。
• MEMBER:指定一个成员变量与属性关联,使之成为可读可写的属性,指定后无须再设置 READ 和 WRITE。
• RESET:是可选的,用于指定一个设置属性默认值的函数。
• NOTIFY:是可选的,用于设置一个信号,当属性值变化时发射此信号。
• DESIGNABLE:表示属性是否在 Qt Designer 的属性编辑器里可见,默认值为 true。
• USER:表示这个属性是不是用户可编辑的属性,默认值为 false。通常一个类只有一个 USER 设置为 true 的属性,例如 QAbstractButton 的 checked 属性。
• CONSTANT:表示属性值是一个常数,对于一个对象实例,READ 指定的函数返回值是常数,但是每个实例的返回值可以不一样。具有 CONSTANT 关键字的属性不能有 WRITE 和 NOTIFY 关键字。
• FINAL:表示所定义的属性不能被子类重载。
例如,下面是 QWidget 类定义属性的一些例子:
Q_PROPERTY(bool focus READ hasFocus)
Q_PROPERTY(bool enabled READ isEnabled WRITE setEnabled)
Q_PROPERTY(QCursor cursor READ cursor WRITE setCursor RESET unsetCursor)
2.属性的使用
在 Qt 类库中,很多基于 QObject 的类都定义了属性,特别是基于 QWidget 的界面组件类。 Qt Designer 的属性编辑器显示了一个界面组件的各种属性,我们可以在进行 UI 可视化设计时修改组件的属性值。可读可写的属性通常有一个用于读取属性值的函数,函数名一般与属性名相同; 还有一个用于设置属性值的函数,函数名一般是在属性名前面加“set”。例如 QLabel 有一个 text 属性,这个属性对应的读取和设置属性值的函数定义如下:
QString QLabel::text() //读取属性值的函数
void QLabel::setText(const QString &) //设置属性值的函数
在编程时,我们一般是使用属性的读取和设置函数来访问属性值.
QObject 类提供了两个函数直接通过属性名字符串来访问属性,其中 QObject::property()函数读取属性值,QObject::setProperty()函数设置属性值。例如下面一段代码:
bool isFlat= ui->btnProperty->property("flat").toBool(); //通过属性名读取属性值
ui->btnProperty->setProperty("flat", !isFlat); //通过属性名设置属性值
其中,ui->btnProperty 表示窗口上的一个 QPushButton 按钮。注意,QObject::property()函数的返回值是 QVariant 类型,需要转换为具体的类型。
QMetaObject 类的一些函数可以提供元对象所描述类的属性元数据. 属性元数据用 QMetaProperty类描述,它有各种函数可反映属性的一些特性,例如下面的一段代码:
const QMetaObject *meta= ui->spinBoy->metaObject(); //获取一个 SpinBox 的元对象
int index= meta->indexOfProperty("value"); //获取属性 value 的序号
QMetaProperty prop= meta->property(index); //获取属性 value 的元数据
bool res= prop.isWritable(); //属性是否可写,值为 true
res= prop.isDesignable(); //属性是否可设计,值为 true
res= prop.hasNotifySignal(); //属性是否有反映属性值变化的信号,值为 true
其中,ui->spinBoy 表示窗口上的一个 QSpinBox 组件。当一个 SpinBox 的 value 属性值发生变化时,它会发射 valueChanged()信号,所以函数 hasNotifySignal()的返回值为 true。QMetaProperty 类还有很多其他函数,这里就不详细列出来了。
3.动态属性
函数 QObject::setProperty()设置属性值时,如果属性名称不存在,就会为对象定义一个新的属性并设置属性值,这时定义的属性称为动态属性。动态属性是针对类的实例定义的,所以只能使用函数 QObject::property()读取动态属性的属性值。
可以根据需要灵活使用动态属性。例如,一个窗口上有多个组件与数据库表的字段关联,这些组件用于输入数据,如果某些字段是必填字段,我们就可以在初始化界面时为这些字段的关联显示组件定义一个新的 required 属性,并设置值为 true,如:
editName->setProperty("required", "true");
comboSex->setProperty("required", "true");
checkAgree->setProperty("required", "true");
然后,我们就可以使用样式表,将 required 属性值为 true 的组件的背景色设置为亮绿色。
*[required="true"] {background-color: lime}
4.附加的类信息
元对象系统还支持使用宏 Q_CLASSINFO()在类中定义一些类信息,类信息有名称和值,值只能用字符串表示,例如:
class QMyClass : public QObject
{
Q_OBJECT
Q_CLASSINFO("author", "Wang")
Q_CLASSINFO("company", "UPC")
Q_CLASSINFO("version ", "3.0.1")
public:
...
};
使用 QMetaObject 的一些函数可以获取类信息元数据。一条类信息用 QMetaClassInfo 类描述,这个类只有两个函数,函数原型定义如下:
char *QMetaClassInfo::name() //返回类信息的名称
char *QMetaClassInfo::value() //返回类信息的值
四. 信号与槽
信号与槽是 Qt 的核心特性,也是它区别于其他 C++开发框架的重要特性。信号与槽是对象间通信所采用的机制,也是由 Qt 的元对象系统支持而实现的。
Qt 使用信号与槽机制实现对象间通信,它隐藏了复杂的底层实现。完成信号与槽的关联后, 发射信号的对象并不需要知道 Qt 是如何找到槽函数的。
1.connect()函数的不同参数形式
函数 connect()有一种成员函数形式,还有多种静态函数形式。一般使用静态函数形式。
静态函数 QObject::connect()有多种参数形式,其中一种参数形式的函数原型是:
QMetaObject::Connection QObject::connect(const QObject *sender, const char *signal,
const QObject *receiver, const char *method, Qt::ConnectionType type = Qt::AutoConnection)
使用这种参数形式的 connect()函数进行信号与槽函数的连接时,一般用法如下:
connect(sender, SIGNAL(signal()), receiver, SLOT(slot()));
这里使用了宏 SIGNAL()和 SLOT()指定信号和槽函数,如果信号和槽函数带有参数,还需注明参数类型,如:
connect(spinNum, SIGNAL(valueChanged(int)), this, SLOT(updateStatus(int)));
另一种参数形式的静态函数 QObject::connect()的原型是:
QMetaObject::Connection QObject::connect(const QObject *sender, const QMetaMethod &signal,
const QObject *receiver, const QMetaMethod &method, Qt::ConnectionType type = Qt::AutoConnection)
对于具有默认参数的信号,即信号名称是唯一的,不存在参数不同的其他同名的信号,可以使用这种函数指针形式进行关联,如:
connect(lineEdit, &QLineEdit::textChanged, this, &Widget::do_textChanged);
QLineEdit 有一个信号 textChanged(QString),不存在参数不同的其他 textChanged()信号,自定 义窗口类 Widget 里有一个槽函数 do_textChanged(QString)。这样就可以用上面的语句将此信号与槽关联起来,无须出现函数参数。当信号的参数比较多时,这种写法简单一些。
某些信号的参数具有默认值,例如 QCheckBox 的 clicked()信号的定义如下:
void QCheckBox::clicked(bool checked = false)
在 QCheckBox 组件的 Go to slot 对话框中会出现两个同名的信号:clicked()和 clicked(bool)。
如果在一个窗口类 Widget 里设计了如下的两个自定义槽函数:
void do_checked(bool checked);
void do_checked_NoParam();
那么使用如下代码进行信号与槽的连接是没有问题的。
connect(ui->checkBox, &QCheckBox::clicked, this, &Widget::do_checked);
connect(ui->checkBox, &QCheckBox::clicked, this, &Widget::do_checked_NoParam);
第一行代码会自动使用 checkBox 的 clicked(bool)信号与 do_checked(bool)连接,第二行代码会 自动使用 checkBox 的 clicked()信号与 do_checked_NoParam()连接。
如果在窗口类 Widget 里设计了如下的两个自定义槽函数:
void do_click(bool checked);
void do_click( );
那么使用如下的代码进行信号与槽的连接时,编译会出现错误。
connect(ui->checkBox, &QCheckBox::clicked, this, &Widget::do_click);
这是因为 QCheckBox 的 clicked()信号是 overload 型信号,do_click()是 overload 型槽函数,信号与槽函数无法匹配。这时需要使用模板函数 qOverload()来明确参数类型,如果写成下面的语句在编译时就没有问题。
connect(ui->checkBox, &QCheckBox::clicked, this, qOverload<bool>(&Widget::do_click));
connect(ui->checkBox, &QCheckBox::clicked, this, qOverload<>(&Widget::do_click));
第一行语句是将信号 clicked(bool)与槽函数 do_click(bool)连接,第二行语句是将信号 clicked() 与槽函数 do_click()连接。模板函数 qOverload()的作用是明确 overload 型函数的参数类型。
因此,对于overload 型信号,只要槽函数不是 overload 型,就可以使用传递函数指针的connect() 来进行信号与槽的关联,Qt 会根据槽函数的参数自动确定使用哪个信号。我们在设计槽函数的时 候一般也不会设计成 overload 型的。
UI 文件经过 MOC 编译转换为 C++头文件时,在 Action 编辑器里设置的信号与槽的连接会在函数 setupUi()中自动生成 connect()函数的语句。例如setupUi()里为界面上的“确定”和“退出”两个按钮生成的 connect()的语句如下:
QObject::connect(btnOK, &QPushButton::clicked, Dialog, qOverload<>(&QDialog::accept));
QObject::connect(btnExit, &QPushButton::clicked, Dialog, qOverload<>(&QDialog::close));
这里 Qt 使用 qOverload()模板函数是为了保险。因为 QDialog::accept()和 QDialog::close()都不是 overload 型函数,所以不使用 qOverload()模板函数也是没有问题的。
不管是哪种参数形式的 connect()函数, 最后都有一个参数 type, 它是枚举类型 Qt::ConnectionType, 默认值为 Qt::AutoConnection。枚举类型 Qt::ConnectionType表示信号与槽的关联方式,有以下几种取值:
• Qt::AutoConnection(默认值):如果信号的接收者与发射者在同一个线程中,就使用 Qt::DirectConnection 方式,否则使用 Qt::QueuedConnection 方式,在信号发射时自动确定关联方式。
• Qt::DirectConnection:信号被发射时槽函数立即运行,槽函数与信号在同一个线程中。
• Qt::QueuedConnection:在事件循环回到接收者线程后运行槽函数,槽函数与信号在不同的线程中。
• Qt::BlockingQueuedConnection:与 Qt::QueuedConnection 相似,区别是信号线程会阻塞, 直到槽函数运行完毕。当信号与槽函数在同一个线程中时绝对不能使用这种方式,否则会造成死锁。
还有一个作为 QObject 成员函数的 connect(),其函数原型定义如下:
QMetaObject::Connection QObject::connect(const QObject *sender, const char *signal,
const char *method, Qt::ConnectionType type = Qt::AutoConnection)
这个函数里没有表示接收者的参数,接收者就是对象自身。例如,使用静态函数 connect()设置连接的一条语句如下:
connect(spinNum, SIGNAL(valueChanged(int)), this, SLOT(updateStatus(int)));
this 表示窗口对象,updateStatus()是窗口类里定义的一个槽函数。如果使用成员函数 connect(), 就可以写成如下的语句:
this->connect(spinNum, SIGNAL(valueChanged(int)), SLOT(updateStatus(int)));
2.disconnect()函数的使用
函数 disconnect()用于解除信号与槽的连接,它有 2 种成员函数形式和 4 种静态函数形式,具体的函数原型见 Qt 帮助文档。函数 disconnect()有以下几种使用方式,示意代码中 myObject 是发射信号的对象,myReceiver 是接收信号的对象。
(1)解除与一个发射者所有信号的连接,例如:
disconnect(myObject, nullptr, nullptr, nullptr); //静态函数形式
myObject->disconnect(); //成员函数形式
(2)解除与一个特定信号的所有连接,例如:
disconnect(myObject, SIGNAL(mySignal()), nullptr, nullptr); //静态函数形式
myObject->disconnect(SIGNAL(mySignal())); //成员函数形式
(3)解除与一个特定接收者的所有连接,例如:
disconnect(myObject, nullptr, myReceiver, nullptr); //静态函数形式
myObject->disconnect(myReceiver); //成员函数形式
(4)解除特定的一个信号与槽的连接,例如:
disconnect(lineEdit, &QLineEdit::textChanged, label, &QLabel::setText); //静态函数形式
3.使用函数 sender()获取信号发射者
sender()是 QObject 类的一个 protected 函数,在一个槽函数里调用函数 sender()可以获取信号发射者的 QObject 对象指针。如果知道信号发射者的类型,我们就可以将 QObject 指针转换为确定类型对象的指针,然后使用这个确定类的接口函数。例如,界面上一个 QPushButton 按钮 btnProperty 的 clicked()信号的槽函数代码如下:
void Widget::on_btnProperty_clicked()
{
QPushButton *btn= qobject_cast<QPushButton*>(sender()); //获取信号的发射者
bool isFlat= btn->property("flat").toBool();
btn->setProperty("flat", !isFlat);
}
在上述代码中,sender()是信号发射者,也就是界面上的 QPushButton 按钮 btnProperty。程序先通过 qobject_cast()函数将 sender()函数转换为 QPushButton 对象,然后获取其 flat 属性值,反转后再设置 flat 属性的值。常规编程时需要通过 ui->btnProperty 访问界面上的按钮 btnProperty,在这段代码里实现了访问按钮 btnProperty,但是没有出现其对象名称,这在某些场景下是非常有用的。
4.自定义信号及其使用
在自己设计的类里也可以自定义信号,信号就是在类定义里声明的一个函数。例如,在下面的自定义类 TPerson 的 signals 部分定义一个信号 ageChanged()。
class TPerson : public QObject
{
Q_OBJECT
private:
int m_age= 10;
public:
void incAge();
signals:
void ageChanged( int value);
}
信号函数必须是无返回值的函数,但是可以有输入参数。信号函数无须实现,而只需在某些条件下被发射。例如,函数 incAge()中会发射信号 ageChanged(),其代码如下:
void QPerson::incAge()
{
m_age++;
emit ageChanged(m_age); //发射信号
}
当私有变量 m_age 的值变化时,程序用 Qt C++中的关键字 emit 发射信号 ageChanged()。至于是否有与此信号关联的槽函数,信号发射者并不关注。如果在使用 TPerson 类对象的程序中为此 信号关联了槽函数,那么在信号 ageChanged()被发射后,关联的槽函数就会运行。
五. 对象树
使用 QObject 及其子类创建的对象(统称为 QObject 对象)是以对象树的形式来组织的。创建一个 QObject 对象时若设置一个父对象,它就会被添加到父对象的子对象列表里。一个父对象被删除时,其全部子对象就会被自动删除。
这种对象树的结构对于窗口上的对象管理特别有用。界面组件都有父对象,也就是组件的容器组件。对于不可见的 QObject 对象(如表示快捷方式的 QShortcut 对象)也可以设置窗口作为父对象。窗口是一个窗口对象树的最上层节点。当用户关闭并删除一个窗口时,窗口上的所有对象都会被自动删除。
QObject 类的构造函数里有一个参数 parent,用于设置对象的父对象。QObject 类有一些函数可以在运行时访问对象树中的对象.
(1)函数 children()。这个函数返回对象的子对象列表,其函数原型定义如下:
const QObjectList &QObject::children()
函数的返回值是 QObjectList 类型,就是 QObject 类型指针列表,定义如下:
typedef QList<QObject*> QObjectList;
对于界面上的容器类组件,容器内的所有组件(包括内部的布局组件)都是其子对象。
假设窗口上一个分组框 groupBox_Btns 里有 4 个水平布局的 QPushButton 按钮,可以通过下面的代码修改这些按钮的文字。
QObjectList objList=ui->groupBox_Btns->children(); //获取分组框的子对象列表
for(int i=0;i<objList.size(); i++) //列表中有 5 个元素
{
const QMetaObject *meta= objList.at(i)->metaObject();//获取元对象
QString className= QString(meta->className()); //子对象类名称
if ( className == "QPushButton") //子对象类名称是 QPushButton
{
QPushButton *btn= qobject_cast<QPushButton*>(objList.at(i));
QString str= btn->text();
btn->setText(str+"*");
}
}
注意,分组框 groupBox_Btns 里虽然有 4 个 QPushButton 按钮,但是 children()函数返回的列表 objList 中有 5 个元素,因为还有一个水平布局对象。所以在操作 QPushButton 按钮时,需要通过对象的元对象的 className()函数获取对象的类名称,判断对象类型后再操作。
(2)函数 findChild()。这个函数用于在对象的子对象中查找可以转换为类型 T 的子对象,其函数原型定义如下:
template <typename T> T QObject::findChild(const QString &name = QString(),
Qt::FindChildOptions options = Qt::FindChildrenRecursively)
参数 name 是子对象的对象名称; 参数 options 表示查找方式,默认值 Qt::FindChildrenRecursively表示在子对象中递归查找,也就是会查找子对象的子对象,若设置为 Qt::FindDirectChildrenOnly 表示 只查找直接子对象。例如,我们要查找窗口上对象名称为 btnOK 的 QPushButton 按钮,代码如下:
QPushButton *btn = this->findChild<QPushButton *>("btnOK");
默认是递归查找,只要窗口上有按钮 btnOK,就可以找到这个对象。
(3)函数 findChildren()。这个函数用于在对象的子对象中查找可以转换为类型 T 的子对象, 可以指定对象名称,还可以使用正则表达式(QRegularExpression)来匹配对象名称。如果不设置要查找的对象名称,就返回所有能转换为类型 T 的对象。两种参数形式的函数原型定义如下:
template <typename T> QList<T> QObject::findChildren(const QString &name = QString(),
Qt::FindChildOptions options = Qt::FindChildrenRecursively)
template <typename T> QList<T> QObject::findChildren(const QRegularExpression &re,
Qt::FindChildOptions options = Qt::FindChildrenRecursively)
例如,下面代码的功能可以替代前面介绍函数 children()时的示例代码的功能。
QList<QPushButton*> btnList= ui->groupBox_Btns->findChildren<QPushButton*>();
for(int i=0; i<btnList.size(); i++)
{
QPushButton *btn= btnList.at(i);
QString str= btn->text();
btn->setText(str+"*");
}
btnList 是分组框 groupBox_Btns 中所有 QPushButton 对象的列表,不包含水平布局对象,所以在访问 btnList 中的对象时无须再做类型判断。
六. 元对象系统功能测试示例
1.创建自定义类 TPerson
首先使 用向导创建一个 GUI 项目,选择窗口基类为 QWidget。然后用向导创建一个自定义 C++ 类 TPerson。打开 New File or Project 对话框, 选择 C/C++组的 C++ Class,会出现一个向导,如图所示。
在图 3-1 所示的向导中,设置要创建的 类名称为 TPerson,选择基类为 QObject,勾选 Include QObject 和Add Q_OBJECT复选框,因为要使用元对象系统,类的定义中需要插入宏 Q_OBJECT。结束向导设置后,Qt Creator 会创建文件 tperson.h 和 tperson.cpp,且会自动创建 TPerson 类的基本框架。
文件 tperson.h 中 TPerson 类的完整定义代码如下:
class TPerson : public QObject
{
Q_OBJECT
Q_CLASSINFO("author","Wang") //定义附加的类信息
Q_CLASSINFO("company","UPC")
Q_CLASSINFO("version","2.0.0")
Q_PROPERTY(int age READ age WRITE setAge NOTIFY ageChanged) //定义属性 age
Q_PROPERTY(QString name MEMBER m_name) //定义属性 name
Q_PROPERTY(int score MEMBER m_score) //定义属性 score
private:
int m_age =10;
int m_score =79;
QString m_name;
public:
explicit TPerson(QString aName, QObject *parent = nullptr);
~TPerson(); //析构函数
int age();
void setAge(int value);
void incAge();
signals:
void ageChanged( int value); //自定义信号
public slots:
};
TPerson 是 QObject 的子类,在类定义的开头需要插入宏 Q_OBJECT,这样 TPerson 就获得了 元对象系统支持,能使用信号与槽、属性等功能。
TPerson 使用宏 Q_CLASSINFO 定义了 3 种附加类信息,使用宏 Q_PROPERTY 定义了 3 个属 性,还定义了 1 个信号 ageChanged(),在 age 属性的值变化时会发射信号 ageChanged()。下面是 TPerson 类的实现代码:
TPerson::TPerson(QString aName,QObject *parent) : QObject(parent)
{//构造函数
m_name= aName;
}
TPerson::~TPerson()
{//析构函数里显示信息,可以看到对象是否被删除
qDebug("TPerson 对象被删除了");
}
int TPerson::age()
{//返回 age 属性的值
return m_age;
}
void TPerson::setAge(int value)
{//设置 age 属性的值
if ( m_age != value)
{
m_age= value;
emit ageChanged(m_age); //发射信号
}
}
void TPerson::incAge()
{
m_age++;
emit ageChanged(m_age); //发射信号
}
函数 setAge()用于设置 age 属性的值,属性值变化时发射信号 ageChanged()。函数 incAge()是 一个单独的接口函数,与属性无关,修改变量 m_age 的值时也会发射信号 ageChanged()。
TPerson 的析构函数里用 qDebug()输出一条信息,这样可以看到 TPerson 对象是否被删除,以及是什么时候被删除的。
2.元对象系统特性的使用
运行时界面如图所示:
我们在窗口类 Widget 中增加了一些定义。Widget 类的定义代码如下,这里省略了 Go to slot 对话框生成的槽函数的定义。
class Widget : public QWidget
{
Q_OBJECT
private:
TPerson *boy;
TPerson *girl;
public:
Widget(QWidget *parent = nullptr);
~Widget();
private slots:
void do_ageChanged(int value);
void do_spinChanged(int arg1);
private:
Ui::Widget *ui;
};
Widget 类定义了两个 TPerson 指针变量,并且定义了两个自定义槽函数。Widget 类的构造函数和析构函数代码如下:
Widget::Widget(QWidget *parent) :QWidget(parent), ui(new Ui::Widget)
{
ui->setupUi(this);
boy= new TPerson("王小明", this);
boy->setProperty("score",95); //设置属性值
boy->setProperty("age",10);
boy->setProperty("sex","Boy"); //sex 是动态属性
connect(boy,SIGNAL(ageChanged(int)),this,SLOT(do_ageChanged(int)));
girl= new TPerson("张小丽", this);
girl->setProperty("score",81); //设置属性值
girl->setProperty("age",20);
girl->setProperty("sex","Girl"); //sex 是动态属性
connect(girl,&TPerson::ageChanged,this,&Widget::do_ageChanged);
ui->spinBoy->setProperty("isBoy",true); //isBoy 是动态属性
ui->spinGirl->setProperty("isBoy",false); //isBoy 是动态属性
connect(ui->spinGirl,SIGNAL(valueChanged(int)), this,SLOT(do_spinChanged(int)));
connect(ui->spinBoy, &QSpinBox::valueChanged, this,&Widget::do_spinChanged);
}
Widget::~Widget()
{//析构函数
delete ui;
}
注意,在创建对象 boy 和 girl 时使用了 this 作为它们的父对象,即:
boy = new TPerson("王小明", this);
girl= new TPerson("张小丽", this);
this 就是窗口对象,这样 boy 和 girl 就被加入了窗口的对象树,虽然这两个对象不是可见的界面组件。在 Widget 类的析构函数里并没有用 delete 显式地删除 boy 和 girl,但是在关闭窗口时, 在 Qt Creator 下方的 Application Output 窗口里可看到两行“TPerson 对象被删除了”,这是 TPerson 的析构函数里用 qDebug()输出的信息,说明 boy 和 girl 被自动删除了。
如果在创建对象 boy 和 girl 时不传递 this 作为它们的父对象,关闭窗口时就看不到这两条信息,也就是 boy 和 girl 没有被自动删除。这种情况下,需要在 Widget 类的析构函数里用 delete 显式地删除 boy 和 girl。所以,在编程时要注意为 QObject 对象设置父对象,以便它们能被自动删除, 否则可能出现未被删除的对象,从而造成内存泄漏。
提示: 对于未加入 QObject 对象树的对象,为避免对象未被删除而出现内存泄漏,可以使用智能指针定义对象指针,例如可以使用 QScopedPointer 定义对象指针。
创建 TPerson 类型对象 boy 和 girl 后,使用函数 setProperty()设置了 score 和 age 属性的值, 这两个属性是在 TPerson 类里定义的。程序中还设置了 sex 属性的值。
boy->setProperty("sex","Boy");
girl->setProperty("sex","Girl");
属性 sex 在 TPerson 类里没有被定义过,所以这个属性是动态属性。
创建对象 boy 和 girl 后,将它们的 ageChanged()信号与槽函数 do_ageChanged()关联。程序中 使用了两种参数形式的 connect()函数,因为 TPerson 只有一个 ageChanged()信号,具有默认的函数参数,所以使用两种方式都是可以的。
为界面上的组件 spinBoy 和 spinGirl 各设置一个动态属性 isBoy,分别赋值为 true 和 false。
ui->spinBoy->setProperty("isBoy",true); //isBoy 是动态属性
ui->spinGirl->setProperty("isBoy",false); //isBoy 是动态属性
使用函数 connect()将这两个SpinBox的 valueChanged() 信号与槽函数do_spinChanged()相关联,这里也使用了两种参数形式的 connect()函数。
自定义槽函数 do_ageChanged()与 TPerson 对象 boy 和 girl 的 ageChanged()信号关联,其代码 如下:
void Widget::do_ageChanged( int value)
{
Q_UNUSED(value);
TPerson *person = qobject_cast<TPerson *>(sender()); //获取信号发射者
QString hisName= person->property("name").toString(); //姓名
QString hisSex= person->property("sex").toString(); //动态属性 sex 的值
int hisAge= person->age(); //通过接口函数获取年龄数据
// int hisAge= person->property("age").toInt(); //通过属性获取年龄数据
QString str= QString("%1, %2, 年龄=%3").arg(hisName).arg(hisSex).arg(hisAge);
ui->textEdit->appendPlainText(str);
}
这里使用了函数 QObject::sender()获取信号发射者。因为信号发射者是 TPerson 类对象 boy 或 girl,所以用函数 qobject_cast()将信号发射者转换为具体的 TPerson 类型:
TPerson *person = qobject_cast<TPerson *>(sender());
这样得到信号发射者 TPerson 类型的对象指针 person,它指向 boy 或 girl。
使用 TPerson 对象指针 person,程序里通过函数 property()获取 name 属性的值,也可以获取动态属性 sex 的值。因为在 TPerson 类中,name 属性只用 MEMBER 关键字定义了一个私有变量 来存储这个属性的值,所以只能用函数 property()读取此属性的值,也只能用函数 setProperty()设 置此属性的值。
获取年龄数据时直接用了接口函数:
int hisAge= person->age();
当然也可以使用函数 property()获取年龄数据:
int hisAge= person->property("age").toInt();
定义 age 属性时用 READ 和 WRITE 指定了公共的接口函数,所以既可以通过 property()和 setProperty()函数分别进行属性的读写,也可以通过接口函数进行读写。直接使用接口函数时执行 速度更快。
界面上两个 SpinBox 用于设置 boy 和 girl 的年龄,它们的 valueChanged(int)信号与自定义槽函数 do_spinChanged(int)关联,该槽函数代码如下:
void Widget::do_spinChanged(int arg1)
{
QSpinBox *spinBox = qobject_cast<QSpinBox *>(sender()); //获取信号发射者
if (spinBox->property("isBoy").toBool()) //根据动态属性判断是哪个 SpinBox
boy->setAge(arg1);
else
girl->setAge(arg1);
}
这里也使用了函数 sender()获取信号发射者,并且将其转换为 QSpinBox 类型对象 spinBox。 然后根据 spinBox 的动态属性 isBoy 的值判断是哪个 SpinBox,以确定调用 boy 或 girl 的 setAge() 函数。这种编写代码的方式一般用于为多个同类型组件的同一信号编写一个槽函数,在槽函数里 根据信号来源分别处理,这样可以避免为每个组件分别编写槽函数而形成代码冗余。
界面上“元对象信息”按钮的槽函数代码如下:
void Widget::on_btnClassInfo_clicked()
{//“元对象信息”按钮
QObject *obj= boy;
// QObject *obj= girl;
// QObject *obj= ui->spinBoy;
const QMetaObject *meta= obj->metaObject();
ui->textEdit->clear();
ui->textEdit->appendPlainText(QString("类名称:%1\n").arg(meta->className()));
ui->textEdit->appendPlainText("property");
for (int i= meta->propertyOffset(); i<meta->propertyCount(); i++)
{
const char* propName= meta->property(i).name();
QString propValue= obj->property(propName).toString();
QString str= QString("属性名称=%1,属性值=%2").arg(propName).arg(propValue);
ui->textEdit->appendPlainText(str);
}
//获取类信息
ui->textEdit->appendPlainText("");
ui->textEdit->appendPlainText("classInfo");
for (int i= meta->classInfoOffset(); i<meta->classInfoCount(); ++i)
{
QMetaClassInfo classInfo= meta->classInfo(i);
ui->textEdit->appendPlainText(
QString("Name=%1; Value=%2").arg(classInfo.name()).arg(classInfo.value()));
}
}
代码里定义了一个 QObject 对象指针 obj 指向 TPerson 对象 boy,然后获取其元对象 meta。可以修改本函数的第一行代码,使 obj 指向不同的 QObject 对象,例如 girl 或 ui->spinBoy。
QMetaObject 类可以获取元对象所描述类的属性定义。注意,QMetaObject::propertyCount()返回元对象描述的类中定义的属性个数,但是其中不包括对象的动态属性,所以界面中,文本框内并没有显示对象 boy 的动态属性 sex 的值。
在 TPerson 类中用宏 Q_CLASSINFO 定义的类信息可以通过元对象访问,一条类信息用一个 QMetaClassInfo 类对象表示,它只有 name()和 value()两个接口函数。
(四). 容器类
Qt 提供了多个基于模板的容器类,这些容器类可用于存储指定类型的数据项。例如常用的字符串列表类 QStringList 可用来操作一个 QList列表。
Qt 的容器类比标准模板库(standard template library,STL)中的容器类更轻巧、使用更安全且更易于使用。这些容器类是隐式共享和可重入的,而且它们进行了速度和存储上的优化,因而可以减小可执行文件大小。此外,它们是线程安全的,即它们作为只读容器时可被多个线程访问。
容器类是基于模板的类,例如常用的容器类 QList,T 是一种具体的类型,可以是 int、float等简单类型,也可以是 QString、QDate 等类。T 必须是一种可赋值的类型,即 T 必须提供一个默认的构造函数、一个可复制构造函数和一个赋值运算符。
Qt 的容器类分为顺序容器(sequential container)类和关联容器(associative container)类。
容器迭代器(iterator)用于遍历容器内的数据项,有 STL 类型的迭代器和 Java 类型的迭代器。 STL 类型的迭代器效率更高,Java 类型的迭代器是为了向后兼容。Qt 还提供了一个宏 foreach 用于遍历容器内的所有数据项。
一. 顺序容器类
Qt 6 的顺序容器类有 QList、QVector、QStack 和 QQueue。Qt 6 中对 QList 和 QVector 的改动 较大,Qt 6 中的 QVector 是 QList 的别名,即 QVector 就是 QList。另外,QList 的底层实现采用了 Qt 5 中 QVector 的机制。
1.QList 类
QList 是 Qt 中较常用的容器类,它用连续的存储空间存储一个列表的数据,可以通过序号访问列表的数据。在列表的始端或中间插入数据会比较慢,因为这需要移动大量的数据以腾出存储位置,在列表的末端添加数据会很快。
使用QList定义一个元素类型为T的列表,定义列表时还可以初始化列表数据或列表大小。
QList<float> list; //定义一个 float 类型的数据列表
QList<int> list= {1,2,3,4,5}; //初始化列表数据
QList<int> list(100); //初始化列表元素个数为 100,所有元素的默认值为 0
QList<QString> strList(10, "pass"); //初始化字符串列表有 10 个元素,每个元素初始化为"pass"
向列表末端添加数据操作比较快,可以使用流操作符“<<”或函数 append()向列表添加数据。QList 提供索引方式访问列表数据,如同访问数组一样,也可以使用函数 at()来访问,例如:
QList<QString> list;
list<<"Monday"<<"Tuesday"<<"Wednesday"<<"Thursday";
list.append("Friday");
QString str1= list[0]; // str1="Monday"
QString str2= list.at(1); // str2="Tuesday"
QList 用于数据操作的常用函数如下:
• append():在列表末端添加数据。
• prepend():在列表始端加入数据。
• insert():在某个位置插入数据。
• replace():替换某个位置的数据。
• at():返回某个索引对应的元素数据。
• clear():清除列表的所有元素,元素个数变为 0。
• size():返回列表的元素个数,即列表长度。
• count():统计某个数据在列表中出现的次数,不带任何参数的 count()等效于 size()。
• resize():重新设置列表的元素个数。
• reserve():给列表预先分配内存,但是不改变列表长度。
• isEmpty():如果列表元素个数为 0,该函数返回 true,否则返回 false。
• remove()、removeAt()、removeAll()、removeFirst()、removeLast():从列表中移除数据。
• takeAt()、takeFirst()、takeLast():从列表中移除数据,并返回被移除的数据。
QList 是Qt中常用的容器类,很多函数的输入参数或返回值是QList列表,例如 QObject::findChildren()函数,其返回值就是对象类型指针的列表。还有一些情况下是将某种 QList 列表用 typedef 定义为一种等效类型,例如QObject::children()函数,它的返回值类型是 QObjectList,而 QObjectList 的定义是:
typedef QList<QObject*> QObjectList;
所以,QObjectList 就是 QObject 类型指针的 QList 列表。
2.QStack 类
QStack 是 QList 的子类。QStack是提供类似于栈的后进先出(LIFO)操作的容器,push() 和 pop()是其主要的接口函数。例如:
QStack<int> stack;
stack.push(10);
stack.push(20);
stack.push(30);
while (!stack.isEmpty())
cout << stack.pop() << Qt::endl;
//程序运行后会依次输出 30、20、10。
3.QQueue 类
QQueue 是 QList 的子类。QQueue是提供类似于队列的先进先出(FIFO)操作的容器, enqueue()和 dequeue()是其主要操作函数。例如:
QQueue<int> queue;
queue.enqueue(10);
queue.enqueue(20);
queue.enqueue(30);
while (!queue.isEmpty())
cout << queue.dequeue() << Qt::endl;
//程序运行后会依次输出 10、20、30。
二. 关联容器类
Qt 还提供关联容器类 QSet、QMap、QMultiMap、QHash、QMultiHash。QMultiMap 和 QMultiHash 支持一个键关联多个值,QHash 类和 QMultiHash 类使用哈希(hash)函数进行查找,查找速度更快。
1.QSet 类
QSet 是基于哈希表的集合模板类,它存储数据的顺序是不确定的,查找值的速度非常快。 QSet内部就是用 QHash 类实现的。定义 QSet容器和输入数据的示例代码如下:
QSet<QString> set;
set << "dog" << "cat" << "tiger";
测试一个值是否包含于某个集合,可以用 contains()函数,例如:
if (!set.contains("cat"))
...
2.QMap 类
QMap定义字典(关联数组),一个键映射到一个值。QMap 是按照键的顺序存储数据的,如果不在意存储顺序,使用 QHash 会更快。
定义 QMap类型字典变量和赋值的示例代码如下:
QMap<QString, int> map;
map["one"] = 1;
map["two"] = 2;
map["three"] = 3;
也可以使用函数 insert()赋值,或使用 remove()移除键值对,例如:
map.insert("four", 4);
map.remove("two");
要查找某个值,可以使用运算符“[ ]”或函数 value(),例如:
int num1 = map["one"];
int num2 = map.value("two");
如果在映射表中没有找到指定的键,会返回一个默认的构造值。例如,如果值的类型是字符串,会返回一个空的字符串。
在使用函数 value()查找值时,还可以指定一个默认的返回值,例如:
timeout = map.value("TIMEOUT",30);
这表示如果在 map 里找到键“TIMEOUT”,就返回关联的值,否则返回值 30。
3.QMultiMap 类
QMultiMap定义一个多值映射表,即一个键可以对应多个值。QMultiMap 的使用示例如下:
QMultiMap<QString, int> map1, map2, map3;
map1.insert("plenty", 100);
map1.insert("plenty", 2000); // map1.size() == 2
map2.insert("plenty", 5000); // map2.size() == 1
map3 = map1 + map2; // map3.size() == 3
QMultiMap 不提供“[ ]”运算符,可以使用函数 value()访问最新插入的键的单个值。如果要获取一个键对应的所有值,可以使用函数 values(),返回值是 QList类型。
QList<int> values = map.values("plenty");
for (int i = 0; i < values.size(); ++i)
cout << values.at(i) << Qt::endl;
4.QHash 类
QHash 是基于哈希表的实现字典功能的模板类,查找 QHash存储的键值对的速度非常快。QHash 与 QMap 的功能和用法相似,区别如下:
• QHash 比 QMap 的查找速度快。
• 在 QMap 上遍历时,数据项是按照键排序的,而 QHash 的数据项是任意顺序的。
• QMap 的键必须提供“<”运算符,QHash 的键必须提供“==”运算符和一个名为 qHash() 的全局哈希函数。
三. 遍历容器的数据
迭代器为遍历访问容器里的数据项提供了统一的方法,Qt 提供两类迭代器:STL 类型的迭代器和 Java 类型的迭代器。STL 类型的迭代器效率更高,Java 类型的迭代器是为了向后兼容。Qt 6的程序设计推荐使用 STL 类型的迭代器,所以我们只介绍 STL 类型的迭代器。
1.STL 类型的迭代器概述
每一个容器类有两个 STL 类型的迭代器: 一个用于只读访问,另一个用于读写访问。无须修改数据时要尽量使用只读迭代器,因为其速度更快。
容器类 | 只读迭代器 | 读写迭代器 |
QList<T>、QStack<T>、QQueue<T> | QList<T>::const_iterator | QList<T>::iterator |
QSet<T> | QSet<T>::const_iterator | QSe<T>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 |
注意,定义只读迭代器和读写迭代器时的区别是使用了不同的关键字,const_iterator 定义只读迭代器,iterator 定义读写迭代器。此外,还可以使用 const_reverse_iterator 和 reverse_iterator 定义相应的反向迭代器。
STL 类型的迭代器是数组的指针,所以,“++”运算 符表示迭代器指向下一个数据项,“*”运算符返回数据项内容。STL 类型的迭代器直接指向数据项,STL 类型的迭代器指向位置示意如下图所示:
函数 begin()使迭代器指向容器的第一个数据项,函数 end()使迭代器指向一个虚拟的表示末尾位置的数据项,end()表示的数据项是无效的,一般用作循环结束条件。
下面以QList和QMap为例说明STL类型的迭代器的用法,其他容器类的迭代器的用法与之类似。
2.顺序容器类的迭代器用法
下面的代码会将 QList list 里的数据项逐项输出:
QList<QString> list;
list << "A" << "B" << "C" << "D";
QList<QString>::const_iterator i;
for (i = list.constBegin(); i != list.constEnd(); ++i)
qDebug() << *i;
函数 constBegin()和 constEnd()是用于只读迭代器的,表示起始位置和结束位置。
若使用反向读写迭代器,并将上面示例代码中 list 的数据项都改为小写,代码如下:
QList<QString>::reverse_iterator i;
for (i = list.rbegin(); i != list.rend(); ++i)
*i = i->toLower();
函数 rbegin()和 rend()用于反向迭代器,表示反向的起始位置和结束位置。迭代器变量 i 就是一个指针,指向列表中的元素,例如这段代码里 i 就是 QString 类型指针,所以可以使用 QString 的接口函数 toLower()。
3.关联容器类的迭代器用法
对于关联容器类 QMap 和 QHash,迭代器的“*”操作符表示返回数据项的值。如果想返回键,需要使用函数key()。对应地,可用函数value()返回一个项的值。例如,下面的代码会将QMap<int,int> map中所有项的键和值输出。
QMap<int, int> map;
...
QMap<int, int>::const_iterator i;
for (i = map.constBegin(); i != map.constEnd(); ++i)
qDebug() << i.key() << ':' << i.value();
Qt 中很多函数的返回值为 QList 或 QStringList 类型,要遍历这些返回的容器类,必须先复制。 由于 Qt 使用了隐式共享,这样的复制并不会产生太大开销。例如下面的代码是正确的。
const QList<int> sizes = splitter->sizes();
QList<int>::const_iterator i;
for (i = sizes.begin(); i != sizes.end(); ++i)
...
隐式共享(implicit sharing)是对象的管理方法。一个对象被隐式共享,意味着只传递该对象的一个指针给使用者,而不实际复制对象数据,只有在使用者修改数据时,才会实际复制共享对象给使用者。例如在上面的代码中,splitter->sizes()返回的是一个 QList列表对象 sizes,但是实际上并不将 splitter->sizes()表示的列表的内容完全复制给变量 sizes,而只是传递给它一个指针。 只有当 sizes 发生数据修改时,才会将共享对象的数据复制给 sizes,这样避免了不必要的复制,可减少资源占用。
对于 STL 类型的迭代器,隐式共享还涉及另外一个问题,即当有一个迭代器在操作一个容器变量时,不要去复制这个容器变量。
4.使用 foreach 遍历容器数据
如果只是想遍历容器中所有的项,可以使用宏 foreach,这是<QtGlobal>头文件中定义的一个宏。foreach 的用法如下,其中变量 variable 的类型必须是容器 container 的元素类型。
foreach (variable, container)
使用 foreach 的代码比使用迭代器更简洁。例如,使用 foreach 遍历 QList的代码如下:
QList<QString> list;
...
QString str;
foreach (str, list)
qDebug() << str;
用于迭代的变量也可以在 foreach 语句里定义,foreach 语句也可以使用花括号,可以使用 break 退出迭代,示例代码如下:
QList <QString> list;
...
foreach (const QString str, list)
{
if (str.isEmpty())
break;
qDebug() << str;
}
对于 QMap 和 QHash,foreach 会自动访问键值对里的值,所以无须调用 values()函数。如果需要访问键则可以使用函数 keys(),示例代码如下:
QMap<QString, int> map;
...
foreach (const QString str, map.keys())
qDebug() << str << ':' << map.value(str);
对于多值映射,可以使用两重 foreach 语句,示例代码如下:
QMultiMap<QString, int> map;
...
foreach (const QString str, map.uniqueKeys())
{
foreach (int i, map.values(str))
qDebug() << str << ':' << i;
}
注意,foreach 遍历容器时创建了容器的副本,所以不能修改原来容器中的数据项。
提示: foreach 是在 C++ 11 规范出现之前引入 Qt 的,而 C++ 11 中引入了基于范围的循环,所以从 Qt 5.7 开始,Qt 就建议不要使用 foreach。
(五). 其他常用的基础类
Qt 框架的 API 中经常用到 QVariant 和 QFlags,它们是很基础的类,理解它们的功能和操作方法对于理解和使用 Qt API 比较有用。
一. QVariant 类
QVariant 是 Qt 中的一种万能数据类型,它可以存储任何类型的数据。Qt 类库中很多函数的返回值是 QVariant 类型的,例如 QObject::property()函数,它的定义如下:
QVariant QObject::property(const char *name)
函数 property()通过属性名称返回属性值,而各种属性的数据类型是不同的,所以用 QVariant 作为这个函数的返回值类型。
一个 QVariant 变量在任何时候只能存储一个值,可以使用它的 toT 函数将数据转换为具体类型的数据,这些 toT 函数如 toBool()、toDouble()、toFloat()、toString()、toInt()、toUInt()、toTime()、 toStringList()等。还可以使用函数 value()返回某种类型的数据。
可以在定义 QVariant 变量时,通过其构造函数为其赋初值。QVariant 有很多参数形式的构造函数,基本覆盖 toT 函数涉及的类型,还可以使用函数 setValue()给 QVariant 变量赋值。示例代码如下:
QVariant var(173);
QString str= var.toString(); //str="173"
int val= var.value<int>(); //val=173
QStringList strList;
strList<<"One"<<"Two"<<"Three";
var.setValue(strList); //给 var 赋值一个字符串列表
QStringList value= var.toStringList(); //转换为字符串列表
对于 Qt GUI 模块中的一些类型,QVariant 没有相应的 toT 函数,例如没有 toColor()、toFont() 这样的函数,但是这些类型的值可以赋值给 QVariant 变量,之后通过 QVariant::value()函数来得到 指定类型的值。例如:
QFont font= this->font(); //窗口的字体
QVariant var= font; //赋值给一个 QVariant 变量
QFont font2= var.value<QFont>(); //转换为 QFont 类型
二. QFlags 类
QFlags是一个模板类,其中 Enum 是枚举类型。QFlags 用于定义枚举值的或运算组合, 在 Qt 中经常用到 QFlags 类。例如,QLabel 有一个 alignment 属性,其读写函数分别定义如下:
Qt::Alignment alignment()
void setAlignment(Qt::Alignment)
alignment 属性值是 Qt::Alignment 类型,Qt 帮助文档中显示的 Qt::Alignment 信息有如下表示:
enum Qt::AlignmentFlag //枚举类型
flags Qt::Alignment //标志类型
这表示 Qt::Alignment 是 QFlags类型,但是 Qt 中并没有定义实际的类型 Qt::Alignment,也就是不存在如下的定义:
typedef QFlags<Qt::AlignmentFlag> Qt::Alignment //这样的定义实际不存在
Qt::AlignmentFlag 是枚举类型,其有一些枚举常量,详见 Qt 文档。
Qt::Alignment 是一个或多个 Qt::AlignmentFlag 类型枚举值的组合,是一种特性标志。所以, 我们把 Qt::Alignment 称为枚举类型 Qt::AlignmentFlag 的标志类型。
给窗口上的 QLabel 组件 label 设置对齐方式,可以使用如下的代码:
ui->label->setAlignment(Qt::AlignLeft |Qt::AlignVCenter);
这实际上是创建了一个 Qt::Alignment 类型的临时变量,相当于如下的代码:
QFlags<Qt::AlignmentFlag> flags= Qt::AlignLeft | Qt::AlignVCenter;
ui->label->setAlignment(flags);
QFlags 类支持或、与、异或等位运算,所以也可以这样写代码:
QFlags<Qt::AlignmentFlag> flags= ui->label->alignment(); //获取 alignment 属性值
flags = flags | Qt::AlignVCenter; //增加垂直对齐
ui->label->setAlignment(flags); //设置 alignment 属性值
这里主要是要区分帮助文档中 enum Qt::AlignmentFlag 和 flags Qt::Alignment 的意义,不要把 QLabel 的 setAlignment()函数的输入参数认为是枚举类型,它实际上是标志类型。
QFlags 类有一个函数 testFlag()可以测试某个枚举值是否包含在此标志变量中,例如:
QFlags<Qt::AlignmentFlag> flags= ui->label->alignment(); //获取 alignment 属性值
bool isLeft = flags.testFlag(Qt::AlignLeft); //是否包含 Qt::AlignLeft
三. QRandomGenerator 类
1.随机数发生器和随机数种子
Qt 6 中已经舍弃了 Qt 5 中产生随机数的函数 qrand()和 qsrand(),取而代之的是 QRandomGenerator 类,它可以产生高质量的随机数。在创建 QRandomGenerator 对象(称为随机数发生器)时可以给 构造函数提供一个数作为随机数种子。如果两个随机数种子相同,则产生的随机数序列是完全相 同的;如果两个随机数种子不同,则产生的随机数序列是完全不同的。
QRandomGenerator 有多种参数形式的构造函数,其中的一种函数原型定义如下:
QRandomGenerator(quint32 seedValue = 1)
参数 seedValue 是随机数种子。如果创建两个随机数发生器时参数 seedValue 的值相同,则生成的随机数序列是完全相同的。所以,一般要确保随机数种子不同,也就是随机数种子要有随机性。
QRandomGenerator *rand1= new QRandomGenerator(QDateTime::currentMSecsSinceEpoch());
QRandomGenerator *rand2= new QRandomGenerator(QDateTime::currentSecsSinceEpoch());
for(int i=0; i<5;i++)
qDebug("R1=%u, R2=%u",rand1->generate(), rand2->generate());
在上面这段代码中,创建 rand1 和 rand2 时使用了不同的随机数种子,QDateTime 的两个静态函数与当前时间有关,是变化的。所以,rand1 和 rand2 产生的随机数序列是完全不同的,每次运行代码时生成的随机数序列也是不同的。
QRandomGenerator 有一个静态函数 securelySeeded()可以创建一个随机数发生器,其函数原型 如下:
QRandomGenerator QRandomGenerator::securelySeeded()
这个函数使用静态函数 QRandomGenerator::system()表示的系统随机数发生器生成的随机数作为种子,创建一个随机数发生器。所以,用于创建这个随机数发生器的种子是随机的。如果只是短期内使用随机数发生器,且生成的随机数的数据量比较小,就不要使用函数 securelySeeded()单独生 成随机数发生器,而可以使用静态函数QRandomGenerator::global()表示的全局的随机数发生器。
2.全局的随机数发生器
QRandomGenerator 有两个静态函数会返回随机数发生器,可以直接使用这两个函数返回的随机数发生器,无须给它们设置种子进行初始化。
QRandomGenerator *QRandomGenerator::system()
QRandomGenerator *QRandomGenerator::global()
静态函数 system()返回系统随机数发生器。这个发生器利用操作系统的一些特性产生随机数,在常用的操作系统上,使用这个发生器的随机数生成密码是安全的。这个发生器是线程安全的,可以在任何线程里使用。这个发生器可能会使用硬件的随机数发生器,所以不要用它生成大量的随机数,可以用它生成的随机数作为新建 QRandomGenerator 对象的种子。
静态函数global()返回全局的随机数发生器,这个发生器是Qt自动用静态函数securelySeeded()设置种子初始化的。程序中一般使用全局的随机数发生器即可,例如:
quint32 rand= QRandomGenerator::global()->generate();
3.QRandomGenerator 的接口函数
QRandomGenerator 生成随机数的几个基本函数定义如下:
quint32 QRandomGenerator::generate() //生成 32 位随机数
quint64 QRandomGenerator::generate64() //生成 64 位随机数
double QRandomGenerator::generateDouble() //生成[0,1)区间内的浮点数
注意,函数 generateDouble()生成的是区间[0,1)内的浮点数,包括 0,但不包括 1。
QRandomGenerator 还支持括号运算符,例如:
QRandomGenerator rand(QDateTime::currentSecsSinceEpoch());
for(int i=0; i<5;i++)
qDebug("number =%u",rand());
程序中的 rand()等同于 rand.generate()。
QRandomGenerator 的 fillRange()函数可以生成一组随机数,可将其填充到列表或数组里,例如:
QList<quint32> list;
list.resize(10); //设置列表长度
QRandomGenerator::global()->fillRange(list.data(), list.size());
//生成随机数并将其填充到列表里
quint32 array[10];
QRandomGenerator::global()->fillRange(array); //生成随机数并将其填充到数组里
QRandomGenerator 的 bounded()函数可以生成指定范围内的随机数,它有很多种参数类型, 例如:
double bounded(double highest) //生成区间[0,highest)内的随机双精度浮点数
quint32 bounded(quint32 highest) //生成区间[0,highest)内的 quint32 随机数
quint32 bounded(quint32 lowest, quint32 highest) //随机数区间为[lowest,highest)
int bounded(int highest) //生成区间[0,highest)内的 int 随机数
int bounded(int lowest, int highest) //生成区间[lowest,highest)内的 int 随机数
quint64 bounded(quint64 highest) //生成区间[0,highest)内的 quint64 随机数
qint64 bounded(qint64 lowest, qint64 highest) //随机数区间为[lowest,highest)
使用函数 bounded()时要注意生成随机数的区间,例如 bounded(quint32 lowest, quint32 highest) 生成的随机数区间为[lowest, highest),它包含下界,但不包含上界。例如下面的代码会生成区间 [60,100]内的随机数。
for(int i=0; i<10;i++)
{
quint32 score = QRandomGenerator::global()->bounded(60,101);
qDebug("score =%u",score);
}