Qt在C++标准的基础上添加了一些特性,也即属于Qt自己的核心技术。这些核心技术在Qt Core模块中实现。这些特性主要包括:
Qt的元对象系统提供了 对象之间通信的信号和槽机制、运行时的类型信息和动态属性系统。元对象系统基本构成为:
1.QObject类。该类是所有元对象系统类的基类。
2.Q_OBJECT宏。在类的成员变量开始的地方声明该宏,就可以使这个类具有元对象的特性,如动态属性和信号与槽。
3.元对象编译器(MOC)。MOC工具在编译时,将含有Q_OBJECT宏的类解释为标准C++源文件,使得通用编译器可以编译Qt项目。
元对象系统是Qt 的核心,我们在开发过程中必然会用到QObject类,Q_OBJECT宏,在编译时也会在VS的输出框看到MOC过程。但是,元对象系统的功能不仅仅如此,它与后面的属性系统,对象模型以及信号与槽有这密切联系,或者说,是它们的基础。
标准C++对象模型为对象范例提供了非常有效的运行效率支持。但GUI编程是一个既需要运行效率,又需要高度灵活性的领域。Qt则通过对象模型则非常好的结合了C+++的速度和灵活性。许多的Qt特性是通过继承QObject类,用标准C++技术实现的。其他的如对象通信机制和动态属性系统,需要Qt自己的MOC来实现。总之,元对象系统时一个C++扩展,使得Qt语言更适合于GUI编程开发。
下面列举一些元对象模型中的设计的基础类:
类 |
说明 |
QMetaClassInfo |
Additional information about a class |
QMetaEnum |
Meta-data about an enumerator |
QMetaMethod |
Meta-data about a member function |
QMetaProperty |
Meta-data about a property |
QMetaType |
Manages named types in the meta-object system |
QObject |
The base class of all Qt objects |
QSignalBlocker |
Exception-safe wrapper around QObject::blockSignals() |
QObjectCleanupHandler |
Watches the lifetime of multiple QObjects |
QMetaObject |
Contains meta-information about Qt objects |
QPointer |
Template class that provides guarded pointers to QObject |
QSignalMapper |
Bundles signals from identifiable senders |
QVariant |
Acts like a union for the most common Qt data types |
Qt提供了一个复杂的属性系统,就像一些编译器开发商提供的属性系统一样。但是,作为一个独立于编译器和开发平台的类库,Qt不依赖非标准的编译指令比如__property or [property].Qt的工程可以在任何Qt支持的平台上用标准C++编译器编译。这个技术是通过上述的元对象系统实现的。
在QObject的派生类中,用宏Q_PROPERTY()定义属性,其格式如下:
Q_PROPERTY(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])
其中的一些主要关键字意义:
READ:指定一个读取属性值的函数。
WRITE:指定一个设定属性值的函数。
MEMBER:指定一个成员变量与属性关联。
RESET:指定一个设置属性缺省值的函数,可选。
NOTIFY:设置一个信号,当属性值发生变化时触发。可选。
Q_PROPERTY宏定义声明一个返回值类型为Type,名称为name的属性,用READ和WRITE关键字定义属性的读取和写入函数。它看上去更像是类的成员变量。如下述的例子:
class MyClass : public QObject
{
Q_OBJECT
Q_PROPERTY(Priority priority READ priority WRITE setPriority NOTIFY priorityChanged)
public:
MyClass(QObject *parent = 0);
~MyClass();
enum Priority { High, Low, VeryHigh, VeryLow };
Q_ENUM(Priority)
void setPriority(Priority priority)
{
m_priority = priority;
emit priorityChanged(priority);
}
Priority priority() const
{ return m_priority; }
signals:
void priorityChanged(Priority);
private:
Priority m_priority;
};
当存在多个关联的对象时,这些对象可以在树中组织自己。比如,当您创建一个派生类的对象时,它将被添加到父类对象的子对象列表中,并在父对象释放时同时被释放。这个技术实现了在开发Qt项目时,你可能不需要逐个释放你new出来的GUI控件,因为他们继承于QObject类,该类的父对象被删除析构时,这些所有的GUI控件都将删除析构,不会造成内存泄漏。当然你也可以手动删除这些控件,当你delete时实际上也从其父类对象中删掉了该子对象。
看下面的例子:
int main()
{
QWidget window;
QPushButton quit("Quit", &window);
...
}
复习下C++的知识,分析下上述代码的析构过程。函数内部的临时变量(局部变量)是在栈上分配控件,析构则是相反顺序进行(先进后出)。quit是类QPushButton的对象,代码指定了它的父对象是window。析构时先析构quit,同时将该对象从window的子对象列表中删除,再析构父对象window。这个过程是正确的。如果是下面的这种情况:
int main()
{
QPushButton quit("Quit");
QWidget window;
quit.setParent(&window);
...
}
先析构windows会将其所有的子对象析构,包括quit。当再析构quit时,相当于对同一对象执行两次delete,我们知道这在C++标准中是不允许的,程序会“崩溃”。
在开发Qt是几乎每个官方类和自定义类都会有父类,指定其parent。理解好对象树和所有权的概念是至关重要的。
信号与槽是Qt的核心的核心,它是区别于其他框架的重要特性。信号与槽是对象间通信的机制,需要Qt元对象系统的支持。
信号与槽隐藏了复杂的底层实现,完成信号和槽的关联后,发射信号并不需要知道Qt是如何找到槽函数的。这类似于一些嵌入式设备的回调函数,但与回调函数相比,信号与槽稍微慢一些,但它的灵活性比回调函数强很多。
信号在某个事件发生的时候发射出去,槽是一个回应这个信号的函数。把这两者联系起来用到connect函数。
connect函数的参数有多种方式,一种是:
connect(sender, SIGNAL(signal(int)), receiver, SLOT(slot(int)));
其中用宏SIGNAL和SLOT指定信号和槽函数,如果信号需要传递参数,那么必须在函数内注明参数类型。
另一种方式是:
connect(sender, QSender::signal, receiver, QSlot::slot);
对具有默认参数的信号和槽,即信号名称是唯一的,没有参数不同而同名的两个信号,可以使用这种方式。
举个自定义信号的例子:
class MyClass:public QObject
{
Q_Object
private:
int counter = 0;
public:
MyClass(QObject *parent = 0){};
~MyClass();
int add();
signals:
void valChanged(int val);
}
void MyClass::add()
{
counter++;
emit valChanged(counter);
}
//mainwindow.h
class QtGuiApplication1 : public QMainWindow
{
Q_OBJECT
public:
QtGuiApplication1(QWidget *parent = Q_NULLPTR);
private:
Ui::QtGuiApplication1Class ui;
private slots:
void onValChanged(int val);
};
//mainwindow.cpp
MainWinow::MainWindow (QWidget *parent)
: QMainWindow(parent)
{
m_class = new MyClass(this);
connect(m_calss, SIGNAL(valChanged(int)), this, SLOT(onValChanged(int)));
}
void QtGuiApplication1::onValChanged(int val)
{
//do sth for val
}