前言
标准 C++对象模型(C++ object model)非常高效地支持对象范式(object paradigm)。
然而在某些方面却表现不足,比如信号的传递、事件的传递和处理等。
GUI编程既要求运行时的高效性又需要有更好的灵活性。
Qt GUI编程语言结合了Qt对象模型(Qt Object Model)的灵活性,以及标准 C++运行时的高效性。
为了更好地满足 GUI图形用户界面编程,Qt的对象模型在标准 C++的基础上新增了一些特性:
-
元对象系统(Meta-ObjectSystem),提供
对象通信机制:信号和槽
动态的对象转换(dynamic cast) -
可查询和可设计的对象属性(objectproperties)
-
层次结构、可查询的对象树(objecttrees)
-
安全的指针(OPointer),在对象销毁时,该指针自动设置为NULL值,这有别于C++的指针
-
强大的事件和事件过滤器
-
支持国际化的文本转换机制
-
定时器机制,使得多个任务(tasks)可以集成在一个事件驱动的(event-driven)GUI中
Qt对象模型中的大多数特性都是通过标准C++技术实施的。
其他的特性,像对象通信机制、动态属性系统(dynamic property system)等需要 Qt 自身提供的元对象编译器(Meta-Object Compiler,moc)的支持。
一、元对象系统
Qt 元对象系统提供了对象间的通信机制(信号和槽)、运行时类型信息和动态属性系统的支持,是标准C++的一个扩展,它使得Qt更好地实现GU图形用户界面编程。Qt 的元对象系统不支持C++模板,尽管模板扩展了标准C++的功能,但是元对象系统提供了模板无法提供的一些特性。Qt的元对象系统基于三个事实:
- 基类 QObiect,任何想使用元对象系统功能的类必须继承自QObject;
- QOBJECT宏,QOBJECT宏必须出现在类的私有声明区,以启动元对象的特性;
- 元对象编译器(Meta-Object Compiler,moc),为QObject 子类实现元对象特性提供必要的代码实现。
Qt moc工具的工作过程:
下面以 CFindFileForm 类的头文件 findfileform.h 为例,看一下 Qt moc 工具的工作过程:
01》在编译应用程序的时候,由make 工具调用moc 工具进行处理:
02》moc 工具读取头文件 findfleform.h,看是否包含 Q_OBJECT 宏,如果包含则进行下-步处理,否则moc放弃对findfileform.h的处理:
03》多moc 根据 fndflefomm,h生成另一个C++源文件(默认的情况下名字命名为moc_findfilefomm. cpp)该源文件包含了元对象代码的实现;
04》接着,C++编译器处理 moc_findfileform.cpp 文件,生成中间文件 moc_findfileform.o;
05》最后,连接器将moc_findfileform.o与其他应用程序的中间文件连接起来,生成可执行应用程序文件。
元对象系统除了支持信号/槽机制和对象的动态转换外,还提供一些其他的特性,包括:
QObject::metaObject(),返回一个类的元对象:
QMetaObject:className(),在运行时以字符串的形式返回类名,但不需要 C++编译器的 RTTI的支持(RTTI会降低C++应用程序的运行效率):
QObject::inherits(),判断一个对象是否是某个指定类的子类实例;
QObject::tr()和 QObject::trUtf8(),进行国际化的字符串翻译,等等
使用元对象特性的一个例子。
#include <QDebug>
#include <QtGui>
#include <QtCore>
int main(int argc, char *argv[])
{
QApplication app(argc,argv);
QTextCodec::setCodecForTr(QTextCodec::codecForName ("gb18030"));
QObject*obj new QLabel;
const QMetaObject* mo = obj->metaObject();
qDebug()<< QObject::tr("类名:号1").arg(mo->className();
qDebug()<< QObject::tr("是否继承自Qwidget:号1")
.arg(obj->inherits("Qwidget") ? QObject::tr("是"):QObject::tr("否");
return 0;
}
程序运行的结果为:
"类名:QLabel"
"是否继承自 Qwidget:是"
二、属性系统
Qt的属性系统建立在Qt元对象系统的基础上,可以工作在Qt支持的任何平台,并且能够使用任何标准的 C++编译器。
在类的定义文件中,通过使用QPROPERTY()宏声明一个属性,而且只有继承自QObject 的子类才可以使用 Qt的属性系统。一个属性类似于类的数据成员,不过同数据成员相比,属性具有下列一些特征:
- 必须有一个读(read)函数;
- 一个可选的写(write)函数(只读属性没有写函数);
- 一个可选的重置(reset)函数,将属性恢复到一个默认值,该函数必须返回void并且没有任何参数:
- 一个可选的“DESIGNABLE”特性,表明该属性是否在GU构造器(例如,Qt设计器)中可用的,可写属性的默认值为true,只读属性的默认值为false;。
- 一个可选的“SCRIPTABLE”特性,表明脚本引擎(scriptingengine)是否可以访问该属性可写属性的默认值为tue,只读属性默认值为false;
- 一个可选的“STORED”特性,表明该属性是否持久的,即当存储一个对象的状态时,是否保存该属性的值,该特性仅仅对可写属性有意义,默认值为true。
读、写和重置函数可以像一般的成员函数一样,既可以是虚的,也可以从父类继承。不同的是多继承情况下,读、写和重置函数必须继承自第一个父类(即类的定义中,继承类列表中最左边的类)
三、对象树
Qt提供了一种机制,能够自动、有效地组织和管理继承自QObject 的 Qt 对象(包括继承自QObject 类的自定义类),这种机制就是 Qt对象树。当应用程序生成一个具有父窗口部件的 Qt 对象的时候(通过在构造函数中的QObject*parent参数指定),这个新的对象将会被添加到父窗口部件的孩子列表中(通过 QObject:children()能够获取对象的子窗口部件),而当对象的父窗口部件被销毁的时候,Qt的对象树机制能够保证也销毁它所有的子窗口部件。
Qt 的对象树机制对于图形用户界面编程是非常有用的,例如,一个QShortcut 对象被设置为一个窗口部件的孩子,当用户关闭并销毁该窗口部件时,这个QShortcut 对象也就被销毁了,这极大地减少了程序员的工作,能够将主要精力放到系统的业务上,提高了编程效率,也增强了系统的稳健性。
C++编程一个重要的方面是防止内存泄露,方法是通过new操作创建的对象必须通过delete操作进行销毁。尽管程序员已经在这方面做得很小心,但好像做得并不出色。而Qt的对象树机制可以减轻内存泄露带来的压力。在进行 QtGUI编程的时候,可以选择 Qt对象的销毁方式:
- 销毁 Qt对象树中的顶层 Qt对象,由对象树机制保证子窗口部件的销毁
- 在代码中显示销毁(delete)子窗口部件
对于显式销毁子窗口部件对象,要保证先销毁子窗口部件之后,再销毁它的父窗口部件。否则的话,将会导致语义未定义。这是因为销毁一个不存在的窗口部件造成的(因为先销毁父窗口部件的话,子窗口部件也被销毁了,当再显式销毁子窗口部件的时候,相当于销毁一个不存在的对象,语义是未定义的)。
QT的构造函数传入qobejct才可以使用元对象系统功能
//此处定义传入Q_OBJECT必须在头文件中声明,在C中只可做列表初始化,否则会报错
//继承QObject写法
class SqlDialog : public QObject
{
Q_OBJECT
public:
SqlDialog(QObject*ptr = nullptr );
~SqlDialog();
}
/*.c:*/
SqlDialog::SqlDialog(QWidget *parent)
: QObject(ptr)
{
}
//继承部件写法,部件都继承自QObject
/*.h:*/
class SqlDialog : public QMainWindow
{
Q_OBJECT
public:
SqlDialog(QWidget *parent = nullptr );
~SqlDialog();
}
/*.c:*/
SqlDialog::SqlDialog(QWidget *parent)
: QMainWindow(parent)
{
}