1 前言
Qt是一个用标准C++编写的跨平台开发类库,它对标准C++进行了扩展,引入了元对象系统、信号与槽、属性等特性,使应用程序的开发变得更高效。本章将开始介绍Qt的这些核心特点,对于理解和编写高效的QtC++程序是大有帮助的。
本章还介绍 <QtGlobal>头文件 中Qt的一些全局定义,包括数据类型、函数和宏等,介绍Qt的容器类及其相应迭代器的使用方法。这些全局定义和容器类在程序中经常用到,了解其原理便于理解后面遇到的一些实例程序。
公众号:Qt实战,各种开源作品、经验整理、项目实战技巧,专注Qt/C++软件开发,视频监控、物联网、工业控制、嵌入式软件、国产化系统应用软件开发。
公众号:Qt入门和进阶,专门介绍Qt/C++相关知识点学习,帮助Qt开发者更好的深入学习Qt。多位Qt元婴期大神,一步步带你从入门到进阶,走上财务自由之路。
官方店:https://shop114595942.taobao.com//
2 正文概述
这里啰嗦一下,这边的概述讲述的是下面整个核心机制的介绍概述
Qt本身并不是一种编程语言,它实质上是一个跨平台的C++开发类库,是用标准C++ 编写的类库,它为开发GUI应用程序和非GUI应用程序提供了各种类。Qt对标准C++进行了扩展,引入了一些新的概念和功能,例如信号与槽、对象属性等。Qt的元对象编译器(Meta-ObjectCompiler,MOC)是一个预处理器,在源程序被编译前先将这些Qt特性的程序转换为标准C++兼容的形式,然后再由标准C++编译器进行编译。这就是为什么在使用信号与槽机制的类里,必须添加一个 Q_ OBJECT 宏的原因,只有添加了这个宏,moc才能对类里的信号与槽的代码进行预处理。QtCore模块 是Qt类库的核心,所有其他模块都依赖于此模块,如果使用qmake来构建项目,QtCore模块则是被自动加入的。Qt为C++语言增加的特性就是在QtCore模块里实现的,这些扩展特性由Qt的元对象系统实现,包括信号与槽机制、属性系统、动态类型转换等。
3 什么是元对象系统
Qt的元对象系统(Meta-ObjectSystem)提供了对象之间通信的信号与槽机制、运行时类型信息和动态属性系统。
元对象系统由以下三个基础组成。
-
QObject类是所有使用元对象系统的类的基类。
-
在一个类的private 部分声明Q_OBJECT宏,使得类可以使用元对象的特性,如动态属性、信号与槽。
-
MOC(元对象编译器) 为每个QObject的子类提供必要的代码来实现元对象系统的特性。构建项目时,MOC工具读取C++ 源文件,当它发现类的定义里有Q_OBJECT宏时,它就会为这个类生成另外一个包含有元对象支持代码的C++源文件,这个生成的源文件连同类的实现文件一起被编译和连接。
除了信号与槽机制外,元对象还提供如下一些功能:
-
QObject:metaObject) 函数返回类关联的元对象,元对象类QMetaObject包含了访问元对象的一些接口函数,例如QMetaObjet::className()函数可在运行时返回类的名称字符串。
Q0bject *obj = new QPushButton; obj ->meta0bject ()->className(); //返回"QPushButton"
-
QMetaObject::newInstance( ) 函数创建类的一个新的实例。
-
QObject::inherits(const char *className) 函数判断一个对象实例是否是名称为className的类或QObject的子类的实例。例如:
QTimer *timer = new QTimer; // QTimer 是Qobject的子类 timer->inherits ("QTimer") ; //返回true timer->inherits ("QObject") ; //返回true timer->inherits ("QAbstractButton");//返回false, 不是QAbstractButton的子类
-
QObject:tr()和QObjec::trUtf8() 函数可翻译字符串,用于多语言界面设计,在后面章节会专门介绍多语言界面设计。
-
Qbjcet:setProperty() 和 QObjet:property() 函数用于通过属性名称动态设置和获取属性值。对于QObject及其子类,还可以使用qobject_ cast() 函数进行动态投射(dynamic cast)。例如,假设QMyWidget是QWidget的子类并且在类定义中声明了Q_OBJECT宏。创建实例使用下面的语句:
Qobject *obj = new QMyWidget;
变量obj定义为QObject指针,但它实际指向QMyWidget类,所以可以正确投射为QWidget,即:
Qwidget *widget = qobject_cast<QWidget *> (obj) ;
从QObject到QWidget的投射是成功的,因为obj实际是 QMyWidget 类,是QWidget 的子类。也可以将其成功投射为QMyWidget,即:
QMyWidget* myWidget =qobject_cast<QMyWidget *> (obj) ;
投射为QMyWidget是成功的,因为qobject_cast() 并不区分Qt内建的类型和用户自定义类型。
但是,若要将obj投射为QLabel 则是失败的,即:QLabel *labe1 = qobject_cast<QLabel *>(obj) ;
这样投射是失败的,返回指针label为nullptr,因为QMyWidget不是QLabel的子类。
使用动态投射,使得程序可以在运行时对不同的对象做不同的处理。
4 元对象特性案例讲解
QPerson类的定义
实例Meta-objectSystem 演示Qt元对象系统的一-些功能。Meta-objectSystem 是主窗口基于QWidget 的应用程序, 这个设计界面不多,主要是C++代码,涉及元对象特性相关的逻辑。
在项目创建后,创建一个类QPerson。QPerson.h 文件中的类定义如下所示:
class QPerson : public Q0bject
{
Q OBJECT
Q_CLASSINFO("author", "Wang")
Q_CLASSINFO("company", "UPC")
Q_CLASSINFO("version", "1.0.0")
Q_PROPERTY(int age READ age WRITE setAge NOTIFY ageChanged)
Q_PROPERTY(QString name MEMBER m_ name)
Q_PROPERTY(int score MEMBER m_ score)
private :
int m_ age=10;
QString m_ name ;
int m score=79;
public:
explicit QPerson (QString fName, Qobject *parent = nullptr) ;
int age () ;
void setAge (int value) ;
void incAge () ;
signals:
ageChanged(int value) ;
public slots:
};
QPerson是QObject的子类,在类定义部分使用了宏Q_OBJECT,这样QPerson就获得了元
对象系统支持,能使用信号与槽、属性等功能。
QPerson使用 Q_ CLASSINFO 宏定义了3个附加信息,使用Q_PROPERTY宏定义了3个属性,还定义了一个信号。下面是QPerson类的实现代码:
QPerson::QPerson (QString fName, Qobject *parent):QObject(parent)
{ //构造函数
m_ name= fName ;
}
int QPerson::age ()
{
//返回age
return m_ age;
}
void QPerson::setAge(int value) //设置age
{
m_ age=value;
emit ageChanged(m_ age); //发射信号
}
void QPerson::incAge ()
{
m_ age++;
emit ageChanged(m_ age) ;//发射信号
}
setAge(int)函数用于设置年龄,代码里设置年龄后发射信号ageChanged()。
incAge()是一个单独的接口函数,与属性无关,但是也发射信号ageChanged()。
5 元对象特性使用详解
主窗口是基于QWidget的可视化设计的类QmyWidget,其定义如下:
class QmyWidget : public QWidget
{
Q_ OBJECT .
private:
QPerson *boy;
QPerson *girl;
public:
explicit QmyWidget (QWidget *parent = 0) ;
~ QmyWidget() ;
private:
Ui::QmyWidget *ui;
private slots :
//自定义槽函数
void on_ageChanged(int value) ;
void on_ spin_ valueChanged(int arg1) ;
//界面按钮的槽函数
void on_ btnClear_ clicked() ;
void on_ btnBoyInc_ clicked() ;
void on btnGirlInc clicked() ;
void on_ btnClassInfo_ clicked() ;
}
QmyWidget 类里定义了两个QPerson类型的指针变量,定义了两个自定义槽函数。构造函数
的代码如下:
QmyWidget::QmyWidget (QWidget *parent) : Qwidget (parent), ui (new Ui::Qmywidget)
{
//构造 函数
ui ->setupUi (this) ;
boy=new QPerson("王小明"); .
boy->setProperty("score",95) ;
boy->setProperty("age", 10) ;
boy->setProperty("sex" , "Boy");//动态属性
connect(boy, &QPerson:: ageChanged, this, &QmyWidget::on_ ageChanged);
girl=new QPerson("张小丽");
girl->setProperty("score",81) ;
girl->setProperty("age",20) ;
girl->setProperty("sex", "Girl");//动态属性
connect(girl, &QPerson:: ageChanged, this, &QmyWidget::on_ ageChanged);
ui->spinBoy->setProperty("isBoy", true); //动态 属性
ui->spinGirl->setProperty ("isBoy", false);
connect(ui->spinGirl, SIGNAL (valueChanged(int)),
this, SLOT(on_spin_valueChanged(int)));
connect (ui->spinBoy, SIGNAL (valueChanged(int)),
this, SLOT(on_spin_valueChanged(int)));
创建QPerson类型对象boy后,使用setProperty()函数设置了score、 age属性的值,这两个属性是QPerson类里定义的。还设置了一个属性sex的值。
boy->setProperty("sex", "Boy") ;
sex属性在QPerson类里没有定义,所以这个属性是个动态属性。
创建对象boy和girl后,它们的ageChanged()信号都与槽函数on_ ageChanged() 关联,设置信号与槽关联时使用了函数指针的形式,因为QPerson只有一个**ageChanged()**信 号,具有缺省函数参数,这样设置关联是可以的。
为界面上的组件spinBoy和spinGirl也设置了–个逻辑型动态属性 isBoy ,分别赋值为true和false,并且这两个 spinBox 的信号 valueChanged(int) 都与槽函数on_ spin_ valueChanged(int) 相关联 。但是,这里使用 connect() 函数设置关联时必须使用SIGNAL和SLOT宏的形式,而不能使用函数指针的形式,因为QSpinBox有两个valueChanged()信号,只是参数不同。
自定义槽函数on_ ageChanged() 用于响应QPerson的ageChanged() 信号,其实现代码如下:
void QmyWidget: :on_ ageChanged( int value)
{
//响应QPerson的ageChanged()信号
Q_ UNUSED (value) ;
QPerson *aPerson = qobject_ cast<QPerson *>(sender()); //类 型投射
QString hisName=aPerson->property ("name").toString(); //姓名
QString hisSex=aPerson->property("sex").toString(); / /动态属性
int hisAge=aPerson->age() ; //通过接口函数获取年龄
// int hisAge=aPerson->property("age").toInt() ; / /通过属性获得年龄
ui ->textEdit->appendPlainText (hisName+","+hisSex+
QString::asprintf(",年龄=%d",hisAge)) ;
这里使用了QObject::sender() 函数获取信号发射者。因为信号发射者是QPerson类型对象boy或girl,所以可以用qobject_ cast() 将发射者投射为具体的类型。
QPerson * aPerson = qobject_ cast<QPerson *> (sender () ) ;
这样得到信号发射者QPerson类型的对象指针aPerson,它指向boy或girl。
使用aPerson指针,通过property()函数获取name属性的值,也可以获取动态属性sex的值。
因为在QPerson中,name 属性只用MEMBER关键字定义了一个私有变量表示这个属性,所以只能用property()读取此属性的值,也只能用setProperty()设 置此属性的值。
读取年龄时,直接用了接口函数,即:
int hisAge=aPerson->age() ;
当然也可以采用property()函数获取年龄,即:
int hisAge=aPerson->property ("age") . toInt() ;
因为定义age属性时用READ和WRITE指定了公共的接口函数,既可以通过property() 和setProperty() 进行属性读写,也可以直接使用接口函数进行读写。当然,直接使用接口函数速度更快。
界面上两个分别用于设置boy和girl年龄的spinBox 的valueChanged(int)信号与槽函数on_ spin_valueChanged(int) 关联,槽函数代码如下:
void QmyWidget: :on_spin_valueChanged(int arg1)
{
//响应界面上spinBox的valueChanged(int)信号
Q_ UNUSED(arg1) ;
QSpinBox *spinBox = qobject_ cast<QSpinBox *> (sender()) ;
if (spinBox->property ("isBoy").toBool())
boy->setAge (spinBox->value());
else
girl->setAge (spinBox->value());
}
这里也使用了信号发射者的类型投射,投射为QSpinBox类型指针spinBox,然后根据spinBox的动态属性isBoy的值,确定调用boy或girl的setAge() 函数。
这种编写代码的方式一般用于为多个同类型组件的同一信号编写-一个槽函数,在槽函数里区分信号来源分别做处理,避免为每个组件分别编写槽函数形成的代码冗余。
界面上 类的元对象信息 按钮的响应代码如下:
void QmyWidget::on_btnClassInfo_clicked()
{
//"类的元对象信息"按钮
const QMetaObject * meta=boy->meta0bject();
ui->textEdit->clear () ;
ui->textEdit->appendPlainText("==元对象信息==\n");
ui->textEdit->appendPlainText(
QString("类名称: %1\n") .arg (meta->className()));
ui->textEdit->appendPlainText ("property") ;
for (int i=meta->propertyOffset() ; i<meta->propertyCount() ;i++)
{
QMetaProperty prop=meta->property(i);
const char* propName=prop.name();
QString propValue=boy->property(propName).toString();
ui->textEdit->appendPlainText(QString("属性名称=%1,属性值=%2").arg(propName)
.arg(propValue));
}
ui->textEdit->appendPlainText("");
ui->textEdi t->appendPlainText("classInfo");
for (int i=meta->classInfo0ffset(); i <meta->classInfoCount(); ++i)
{
QMetaClassInfo classInfo=meta->classInfo(i);
ui->textEdit->appendPlainText(QString("Name=81; Value=82")
.arg(classInfo.name()).arg(classInfo.value());
}
}
代码里通过boy->metaObject() 获得对象boy的元对象。元对象类QMetaObject封装了访问类的元对象的各种接口函数,例如,QMetaObject:: className() 返回类的名称。
QMetaObject用于属性操作的函数有以下几种:
- propertyOffset(): 返回类的第一个属性的序号,第一个属性的序号不一定是0。
- propertyCount(): 返回类的属性个数。
- QMetaProperty property(int index): 返回序号为index的属性对象,返回值是QMetaProperty
类型,它封装了对属性的更多特征查询功能,以及属性值的读写功能。
QMetaClassInfo类封装了类的 classInfo 的访问接口函数,只有 name()和value() 两个接口函数。
好了! 到这里元对象系统也就差不多了,也不讲太多了,以免大家消化不完,今天元对象系统就到这里,后面我们继续讲属性系统!