Qt6.0开发 第三章 Qt框架功能概述
第三章 Qt框架功能概述
Qt全局定义
头文件<QtGlobal>包含一系列Qt框架中的全局定义.包括基本数据类型,函数与宏.
函数
qt中常用的函数包括:
函数原型 | 功能 |
---|---|
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替换 |
bool qFuzzyCompare(double p1,double p2) | 判断p1与p2是否近似相等 |
bool qFuzzyIsNull(double d) | 判断d是否近似于0 |
double qInf() | 返回无穷大的数 |
const T& qMax(const T& value1, const T& value2) |
返回较大值 |
const T& qMin(const T& value1, const T& value2) |
返回较小值 |
qint64 qRound64(double value) | 将value近似为最接近的qint64整数 |
int qRound(double value) | 将value近似为最接近的int整数 |
其实上面那些函数大部分都可以查文档...
宏定义
头文件中还定义了很多宏,下面这些是较为常见的:
- QT_VERSION: 表示Qt版本,通常展开为数字形式0xMMNNPP,例如Qt6.2.3为0x060203.
- Q_BYTE_ORDER: 表示系统内存中数据的字节序.
- Q_BIG_ENDIAN: 表示大端字节序.
- Q_LITTLE_ENDIAN: 表示小端字节序.
- Q_UNUSED(name): 用于消除声明函数中未使用的参数带来的警告.
- qDebug(const char* msg,...): 用于调试程序时提示中间信息.
Qt元对象系统
Qt引入了元对象系统对标准C++语言进行扩展,增加了信号与槽,属性系统,动态翻译等特性,为GUI程序提供了方便.
Qt元对象系统的功能建立在以下三个方面:
- QObject类是所有使用元对象系统的类的基类.
- 必须在一个类的开头部分插入部分宏Q_OBJECT,这样该类才可使用元对象系统特性.
- MOC为每个QObject的子类提供必要的代码来实现元对象系统的特性.
QObject
QObject类是所有使用元对象系统的类的基类,也就是说,只要一个类的父类中有QObject,它就可以使用相关特性.
QObject的特性主要有:
- 元对象(meta object): 每个QObject及其子类的实例都有一个元对象,这个元对象是自动创建的.
- 静态变量staticMetaObject:返回该元对象.
- 函数metaObject():返回该元对象指针.
QPushButton* btn = new QPushButton();
const QMetaObject* metaPtr = btn->metaObject();
const QMetaObject* metaObj = btn->staticMetaObject;
- 类型信息: inherits()函数可以判断对象是不是某个类的子类的实例.
- 动态翻译: 函数tr()用于返回一个字符串的翻译版本,用于多语言界面应用程序设计.
- 对象树(object tree): 对象树指的是表示对象间从属关系的树状结构.
- 信号与槽: 通过在一个类的定义中插入宏Q_OBJECT,我们就可以使用Qt相关语言特性.
- 属性系统: 在类的定义代码中可以用宏Q_PROPERTY定义属性.
QMetaObject
每个QObject及其子类的实例都有一个自动创建的元对象,元对象是QMetaObject类型的实例.
元对象存储了嘞实例所属类的各种源数据,包括信息元数据,方法元数据,属性元数据等.
元对象实质上是对类的描述.
QMetaObject类的主要接口函数有:
- 类的信息:
- className():返回对象的类名称
- metaType():返回原对象的元类型
- superClass():返回类的上层父类的元对象
- inherits():判断该类是否继承自metaObject描述的类
- newInstance():返回该类的一个实例
- 类信息元数据:
- classInfo():返回序号为index的一条类信息的元数据,类信息为类中通过Q_CLASSINFO定义的信息
- indexOfClassInfo():返回名称为name的类信息的序号,序号可用于classInfo函数
- classInfoCount():返回这个类的类信息条数
- classInfoOffset():返回这个类的第一条类信息的序号
- 构造函数元数据:
- constructorCount():返回该类构造函数的个数
- constructor():返回该类序号为index的构造函数的元数据
- indexOfConstructor():返回一个构造函数的序号,char*包括正则化后的函数名与参数名
- 方法元数据:
- method():返回序号为index的方法的元数据
- methodCount():返回这个类的方法的个数
- methodOffset():返回这个类的第一个方法的序号
- indexOfMethod():返回名称为method的方法的序号
- 枚举类型元数据:
- enumerator():返回序号为index的元数据
- enumeratorCount():返回这个类的枚举类型个数
- enumeratorOffset():返回这类的第一个枚举类型的序号
- indexOfEnumerator():返回名称为name的枚举类型的序号
- 属性元数据:
- property():返回序号为index的元数据
- propertyCount():返回这个类的属性的个数
- propertyOffset():返回这个类的第一个属性的序号
- indexOfProperty():返回名称为name的属性的序号
- 信号与槽:
- indexOfSignal():返回名称为signal的信号的序号
- indexOfSlot():返回名称为slot的槽函数的序号
- 静态函数:
- checkConnectArgs():检查信号与槽函数的参数是否兼容
- connectSlotsByName():迭代搜索object的所有子对象,连接匹配的信号与槽函数
- invokeMethod():运行QObject对象的某个方法,包括信号,槽函数或成员方法
- normalizedSignature():将方法method的名称和参数字符串正则化.
通过上述函数名称及功能描述,我们不难发现,QMetaObject对象的作用主要是服务于QObject对象相关信息的查询.
运行时类型信息
RTTI(Runtime Type Identification),是"运行时类型识别"的意思.
C++引入这个机制是为了让程序在运行时能根据基类的指针或引用来获得该指针或引用所指的对象的实际类型.
而在Qt中,通过QObject与QMetaObject提供的接口函数,我们可以绕开C++编译器的RTTI支持.
- QMetaObject::className(): 运行时返回类名称字符串
QPushButton* btn = new QPushButton();
const QMetaObject* meta = btn->metaObject();
QString str = QString(meta->className());//str=="QPushButton"
- QObject::inherits(): 用于判断一个对象是否继承自某个类的实例
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
- QMetaObject::superClass: 该函数返回该元对象所描述类的父类的元对象.
QPushButton* btn= new QPushButton();
const QMetaObject* meta= btn->metaObject();
QString str1= QString(meta->className());//"QPushButton"
const QMetaObject* metaSuper= btn->metaObject()->superClass();
QString str2= QString(metaSuper->className());//"QAbstractButton"
- qobject_cast(): 对于QObject及其子类对象,可以使用qobject_cast()进行动态类型转换.
如果自定义类要支持函数qobject_cast()那么其需要继承自QObject,且在类定义中插入宏Q_OBJECT.
QObject* btn= new QPushButton();
const QMetaObject* meta= btn->metaObject();
QString str1= QString(meta->className());//"QPushButton"
QPushButton* btnPush= qobject_cast<QPushButton*>(btn);
const QMetaObject* meta2= btnPush->metaObject();
QString str2= QString(meta2->className());//"QPushButton"
QCheckBox* chkBox= qobject_cast<QCheckBox*>(btn);//无法自己转换自己
属性系统
属性是Qt C++基于元对象系统实现的一个扩展特性.在QObject的子类中,我们可以使用宏Q_PROPERTY定义属性.
属性系统的作用与成员变量十分相像.
- 添加一条属性
Q_PROERTY(type name ...)
注意理解下面属性的形式
(READ getFunction[WRITE setFunction]|
MEMBER memName[(READ getFunction[WRITE setFunction])])
宏Q_PROPERTY定义一个值类型为type,名称为name的属性.
- 属性值函数相关:
- READ:指定一个读取属性值的函数,没有MEMBER关键字时必须设置READ
- WRITE:指定一个设置属性值的函数,只读属性不设置WRITE
- MEMBER:指定一个成员变量与属性关联,使之成为可读可写的属性
- 其他可选:
- RESET:可选的,用于指定一个设置属性默认值的函数
- NOTIFY:可选的,当属性值发生变化时发射此信号
- DESIGNABLE:表示属性是否在属性编辑器里可见,默认true
- USER:表示用户是否可编辑,默认为false
- CONSTANT:表示属性是一个常数,不能与NOTIFY共存
- FINAL:表示所定义的属性不能被子类重载
Qt属性机制还可以在运行时添加与使用属性,这种属性被称为动态属性.
- 使用一个动态属性
object->property("属性名");
- 设置一个动态属性
object->setProperty("属性名",value);
由于Qt的元对象机制,属性也存在对应的属性元数据类型.
- 获得属性的序号
int index= metaObject->indexOfProperty("属性名");
- 获得属性的元数据
QMetaProperty prop= metaObject->property(index);
元对象系统还支持使用宏Q_CLASSINFO在类中定义一些类信息,类信息有名称和值,只能用字符串表示.
- 添加一条类信息
Q_CLASSINFO("名称","值");
- 获得类信息
QMetaClassInfo info= metaObject->classInfo(index)
其中,QMetaClassInfo类有name()与value()两个成员函数.
信号与槽
信号与槽是Qt的核心机制,也是它区别于其他C++开发框架的重要特性.
信号与槽作为对象间通信机制,其也是由Qt元对象系统支持实现的.其隐藏了复杂的底层实现.
函数connect()有一种成员函数形式,还有多种静态函数形式.一般使用静态函数形式.
- connect()函数一般形式
connect(sender,SIGNAL(signal()),receiver,SLOT(slot()));
这里使用了SIGNAL()与SLOT()指定信号与槽函数,如果信号与槽函数带有参数,还需要说明参数类型.
如下所示:
connect(sender,SIGNAL(signal(int)),receiver,SLOT(slot(int)));
另一种参数形式的静态函数QObject::connect()无需通过宏实现:
- connect()函数另一种静态函数形式
connect(sender,&QSomeObject::signal,receiver,&QOtherObject::slot);
这样可以用上面的语句将信号与槽关联起来,无需出现函数参数.
如果出现具有不同参数列表的同名信号函数,那么这一形式connect()也将根据参数将其与不同槽函数对应.
但是当槽函数与信号函数都存在同名函数且不止一个参数列表匹配,那么就会出现错误.
UI文件经过MOC编译转换为C++头文件时,在Action编辑器里设置的信号与槽的连接会在函数setupUi中自动生成.
不管是哪种参数形式的connect()函数,最后都有一个参数type,它是枚举类型Qt::ConnectionType,默认值为Qt::AutoConnection.表示信号与槽的关联方式,有以下几个值:
- Qt::AutoConnection:如果信号的接收者与发送者在同一个线程中,就使用Qt::DirectConnection方式,否则使用Qt::QueuedConnection方式.
- Qt::DirectConnection:信号被发射时,槽函数立即运行,槽函数与信号函数在同一个线程中.
- Qt::QueuedConnection:在事件循环回到接收者线程后运行槽函数,槽函数与信号在不同的线程中.
- Qt::BlockingQueuedConnection:与Qt::QueuedConnection相似,区别是信号线程会阻塞,直到槽函数运行完毕.
还有一个作为QObject成员函数的connect(),其没有表示接收者的参数,接收者就是对象自身:
- connect()的成员函数形式
this->connect(sender,SIGNAL(signal(int)),SLOT(slot(int)));
与connect()相应的,存在函数disconnect()用于解除信号与槽的连接.
- 解除与一个发射者所有信号的连接(静态函数形式)
disconnect(sender,nullptr,nullptr,nullptr);
- 解除与一个发射者所有信号的连接(成员函数形式)
object->disconnect();
- 解除与一个特定信号的所有连接(静态函数形式)
disconnect(sender,SIGNAL(signal()),nullptr,nullptr);
- 解除与一个特定信号的所有连接(成员函数形式)
object->disconnect(SIGNAL(signal));
- 解除与一个特定接收者的所有连接(静态函数形式)
disconnect(sender,nullptr,receiver,nullptr);
- 解除与一个特定接收者的所有连接(成员函数形式)
object->disconnect(receiver);
- 解除特定的一个信号与槽的连接
disconnect(sender,&QSomeObject::signal,receiver,&QOtherObject::slot);
此外,Qt还在QObject中提供了一个sender()函数用于获得发射者的QObject对象指针.
一般是在槽函数中使用sender()可以获得发射者的QObject对象指针.
而且,在自己设计的类里,不仅可以自定义槽函数,也可以自定义信号.
信号就是在类定义里声明的一个无返回值函数.
信号函数可以有输入参数,其无须实现,而只需在某些条件下被发射.
下面是一个例子:
class Dialog : public QDialog
{
Q_OBJECT
//....
private slots:
//测试自定义信号
void do_reactionToSignal();
signals:
void do_checkedRadio();
};
//Dialog.h
void Dialog::do_pushButtonOk()
{
emit do_checkedRadio();
return;
}
void Dialog::do_reactionToSignal()
{
QPalette plet= txtEdit->palette();
plet.setColor(QPalette::Text,Qt::green);
txtEdit->setPalette(plet);
return;
}
Dialog::Dialog(QWidget *parent)
: QDialog(parent)
{
//...
//新建竖向布局
QVBoxLayout *VLay= new QVBoxLayout;
//按钮
btnOk= new QPushButton("确定");
QHBoxLayout *HLay3= new QHBoxLayout;
HLay3->addWidget(btnOk);
connect(btnOk,SIGNAL(clicked()),this,SLOT(do_pushButtonOk()));
//文本栏
txtEdit= new QPlainTextEdit;
txtEdit->setPlainText("Qt6.0 学习\n第二章 项目二");
//测试自定义信号函数
connect(this,SIGNAL(do_checkedRadio()),this,SLOT(do_reactionToSignal()));
//...
}
//Dialog.cpp
在其中,信号函数do_checkedRadio只需要声明,而不需要实现.
将其与槽函数connect()后,实际上只需对其emit便可发送信号.
这是很方便的.
对象树
使用QObject及其子类创建的对象是以对象树的形式来组织的.
创建一个QObject对象时若设置一个父对象,它就会被添加到父对象的子对象列表里.
一个父对象被删除时,其全部子对象就会被自动删除.
这种对象树的结构对于窗口上的对象管理特别有用.
与之相关的函数主要有三个
- children():这个函数返回对象的子对象列表,为QObjectList类型,为QObject类型指针列表.
typedef Qlist<QObject*>QObjectList;
- findChild():这个函数用于在对象的子对象中查找可以转换为类型T的子对象.
例如:我们要查找窗口上对象名称为btnOK的QPushButton按钮:
QPushButton *btn= this->findChild<QPushButton*>("btnOK");
- findChildren():这个函数用于在对象的子对象中查找可以转换为类型T的子对象,且以子对象列表形式返回.
例如:
QList<QPushButton*>btnList= ui->groupBox_Btns->findChildren<QPushButton*>();
上述知识综合应用
下面是本章的一个综合应用:
tperson.h
#ifndef TPERSON_H
#define TPERSON_H
#include <QObject>
class TPerson : public QObject
{
Q_OBJECT
Q_CLASSINFO("author","MesonoxianY");
Q_CLASSINFO(
"note",
"you should know that is just a test."
);
Q_CLASSINFO("version","0.01beta");
Q_PROPERTY(int age MEMBER m_age);
Q_PROPERTY(int score MEMBER m_score);
Q_PROPERTY(QString name MEMBER m_name);
public:
explicit TPerson(QObject *parent = nullptr,QString name= "nobody");
~TPerson();
int getPersonAge();
QString getPersonName();
void setPersonAge(int input_age);
void increaseAge();
private:
QString m_name;
int m_age,m_score;
private slots:
signals:
void changeAgeSignal(int input_age);
};
#endif // TPERSON_H
widget.h
#ifndef WIDGET_H
#define WIDGET_H
#include "tperson.h"
#include <QWidget>
#include <QMetaProperty>
#include <QMetaClassInfo>
QT_BEGIN_NAMESPACE
namespace Ui {
class Widget;
}
QT_END_NAMESPACE
class Widget : public QWidget
{
Q_OBJECT
public:
Widget(QWidget *parent = nullptr);
~Widget();
private:
Ui::Widget *ui;
TPerson *boy,*girl;
private slots:
void do_ageChange(int input_age);
void do_ageSpinChange(int change_age);
void do_btnBoy(bool checked);
void do_btnGirl(bool checked);
void do_btnClear(bool checked);
void do_btnMetaObjectInfo(bool checked);
};
#endif // WIDGET_H
tperson.cpp
#include "tperson.h"
#include <QDebug>
TPerson::TPerson(QObject *parent,QString name)
: QObject{parent},m_name(name),m_age(8),m_score(78)
{
return;
}
TPerson::~TPerson()
{
qDebug()<<"TPerson object destoryed.";
}
int TPerson::getPersonAge()
{
return this->m_age;
}
QString TPerson::getPersonName()
{
return this->m_name;
}
void TPerson::setPersonAge(int input_age)
{
this->m_age=input_age;
emit changeAgeSignal(m_age);
return;
}
void TPerson::increaseAge()
{
this->m_age++;
emit changeAgeSignal(m_age);
return;
}
widget.cpp
#include "widget.h"
#include "ui_widget.h"
Widget::Widget(QWidget *parent)
: QWidget(parent)
, ui(new Ui::Widget)
{
ui->setupUi(this);
ui->spinBoxBoy->setProperty("sex","male");
ui->spinBoxGirl->setProperty("sex","female");
//TPerson相关
boy= new TPerson(this,"Tom");
boy->setProperty("sex","male");
girl = new TPerson(this,"Jenny");
girl->setProperty("sex","female");
connect(boy,SIGNAL(changeAgeSignal(int)),this,SLOT(do_ageChange(int)));
connect(girl,SIGNAL(changeAgeSignal(int)),this,SLOT(do_ageChange(int)));
connect(boy,SIGNAL(changeAgeSignal(int)),ui->spinBoxBoy,SLOT(setValue(int)));
connect(girl,SIGNAL(changeAgeSignal(int)),ui->spinBoxGirl,SLOT(setValue(int)));
//输入框相关
connect(ui->spinBoxBoy,SIGNAL(valueChanged(int)),this,SLOT(do_ageSpinChange(int)));
connect(ui->spinBoxGirl,SIGNAL(valueChanged(int)),this,SLOT(do_ageSpinChange(int)));
//按钮相关
connect(ui->btnBoy,SIGNAL(clicked(bool)),this,SLOT(do_btnBoy(bool)));
connect(ui->btnGirl,SIGNAL(clicked(bool)),this,SLOT(do_btnGirl(bool)));
connect(ui->btnClearText,SIGNAL(clicked(bool)),this,SLOT(do_btnClear(bool)));
connect(ui->btnMetaObjectInfo,SIGNAL(clicked(bool)),this,SLOT(do_btnMetaObjectInfo(bool)));
}
Widget::~Widget()
{
delete ui;
}
void Widget::do_ageChange(int input_age)
{
Q_UNUSED(input_age);
QString str;
TPerson *person=qobject_cast<TPerson*>(sender());
str=QString("%1,%2的年龄=%3")
.arg(person->property("name").toString())
.arg(person->property("sex").toString())
.arg(person->getPersonAge());
ui->textEdit->appendPlainText(str);
return;
}
void Widget::do_ageSpinChange(int change_age)
{
QSpinBox *spin_box=qobject_cast<QSpinBox*>(sender());
if(spin_box->property("sex")=="male")
boy->setPersonAge(change_age);
else
girl->setPersonAge(change_age);
return;
}
void Widget::do_btnBoy(bool checked)
{
boy->increaseAge();
return;
}
void Widget::do_btnGirl(bool checked)
{
girl->increaseAge();
return;
}
void Widget::do_btnClear(bool checked)
{
ui->textEdit->clear();
return;
}
void Widget::do_btnMetaObjectInfo(bool checked)
{
const QMetaObject *meta= boy->metaObject();
ui->textEdit->appendPlainText(QString("type:%1\n")
.arg(meta->className()));
ui->textEdit->appendPlainText(QString("attribution:"));
for(auto i=meta->propertyOffset();i<meta->propertyCount();i++){
auto propName=meta->property(i).name();
QString propValue=boy->property(propName).toString();
ui->textEdit->appendPlainText(QString("property name:%1\nproperty value:%2\n")
.arg(propName)
.arg(propValue));
}
ui->textEdit->appendPlainText("classinfo:");
for(auto i=meta->classInfoOffset();i<meta->classInfoCount();i++){
QMetaClassInfo classInfo=meta->classInfo(i);
ui->textEdit->appendPlainText(QString("classinfo name:%1\nclassinfo value:%2\n")
.arg(classInfo.name())
.arg(classInfo.value()));
}
return;
}
main.cpp内容为默认生成.widget.ui请自行结合理解设计.
容器类
Qt提供了多个基于模板的容器类,它们的使用方式与STL相似,且较STL而言更具优势:
- Qt的容器类更轻巧,使用更安全且更容易使用.
- Qt容器类是隐式共享和可重入的,而且进行了速度与存储上的优化.
- Qt容器类是线程安全的,作为只读容器可被多个线程访问.
Qt的容器类分为 顺序容器(sequential container)类 和 关联容器(associative container)类.
容器迭代器(iterator)用于遍历容器内的数据项,Qt有STL类型的迭代器与Java类型的迭代器.
常用的容器
顺序容器类:
- QList类:列表,对应std::list<T>的容器,但是十分灵活.
- QVector类:向量,对应std::vector<T>的容器,但是成员函数更多的按照QList习惯.
- QStack类:栈,对应std::stack<T>,是QList的子类.以pop()和push()为主.
- QQueue类:队列,对应std::queue<T>,是QList的子类.与QStack相似.
关联容器类: - QSet类:集合,对应于std::set<T>,是基于哈希表的集合模板类.
- QMap类:散列,对应于std::map<T>,如果不在意存储顺序,QHash会更快.
- QMultiMap类:多值映射表.
- QHash类:字典,基于哈希表实现的字典类,速度更快.
Qt中的迭代器
Qt中,每一个容器有两个STL类型的迭代器:一个用于只读访问(const_iterator),一个用于读写访问(iterator)
而且,在Qt中,由于使用了隐式共享,容器类作为返回值带来的复制并不带来太大开销.
隐式共享(implicit sharing): 是对象的管理方法.一个对象被隐式共享,意味着只会传递该对象的一个指针给使用者,而不实际复制对象数据,只有在使用者修改数据时,才会实际复制共享对象给使用者.
C++中没有自带的foreach语句,通常通过范围for语句与<algorithm>中的foreach()函数实现.
Qt中则加入了foreach语句,用于容器的遍历:
- qt中使用foreach语句进行遍历
foreach (const QType iter,list){
//body of foreach
}
其他常用的基础类
- QVariant类:是Qt中的万能数据类型,可以存储任何类型的数据.
QObject::property()函数就是返回QVariant类型的值.
一个QVariant变量在任何时候只能存储一个值,可以使用它的toT函数将其转换为具体数据.例如toString().
对于QVariant中没有相应toT函数的类型,可以通过value<T>模板函数转换.
- QFlags<Enum>类
QFlags<Enum>类是一个模板类,其中Enum是枚举类型.QFlags用于定义枚举值或运算组合.
Qt中经常用到QFlags类:
例如:QLabel有一个alignment属性,其属性值为Qt::Alignment类型,其信息有如下表示:
enum Qt::AlignmentFlag//枚举类型
flags Qt::Alignment//标志类型
那么则表示Qt::Alignment是QFlags<Qt::AlignmentFlag>类型.
Qt::AlignmentFlag是枚举类型,其有一些枚举常量.
Qt::Alignment是一个或多个Qt::AlignmentFlag类型枚举值的组合,所以,我们把Qt::Alignment称为枚举类型Qt::AlignmentFlag的标志类型.
- QRandomGenerator类
其可以提供高质量的随机数,在创建QRandomGenerator对象时,可以为构造函数提供一个数作为随机数种子.
QRandomGenerator中有一个静态函数QRandomGenerator::securelySeeded()可以创建一个随机数发生器.
这个函数使用QRandomGenerator::system()表示的系统随机数发生器生成的随机数为种子.
QRandomGenerator有两个静态函数会返回随机数发生器,可以直接使用而不初始化:
- QRandomGenerator *QRandomGenerator::system()
- QRandomGenerator *QRandomGenerator::global()
system代表系统随机数发生器,global代表全局随机数发生器.
一般直接调用即可,如下所示:
quint32 rand= QRandomGenerator::global()->generate();
QRandomGenerator生成随机数的基本函数主要有:
- quint32 QRandomGenerator::generate()
- quint64 QRandomGenerator::generate64()
- double QRandomGenerator::generateDouble()//区间[0,1)
QRandomGenerator还支持括号运算符,如:
QRandomGenerator rand(QDateTime::currentSecsSinceEpoch());
rand()//生成随机数
其中rand()等同于rand.generate().
而QRandomGenerator::bounded可以生成指定范围内的随机数,其有很多参数类型.