Qt是一个用标准C++编写的跨平台开发类库,它对标准C++进行了扩展,引入了元对象系统,信号与槽,属性等特性,使得应用程序的开发变得更为高效
Qt的元对象编译器(Meta-Object Compiler,MOC)是一个预处理器,在源程序被编译前先将这些Qt特性的程序转换为标准C++兼容的形式,然后再由标准C++编译器进行编译。
这就是为什么在使用信号与槽机制的类里,必须添加一个Q_OBJECT宏的原因,只有添加了这个宏,moc才能对类里的信号与槽的代码进行预处理
Qt Core模块的Qt类库的核心,所有其他模块都是依赖于此模块,如果使用qmake来构建项目,Qt Core模块则是被自动加入的
Qt为C++语言增加的特性就是在Qt Core模块里实现的,这些拓展特性由Qt的元对象系统实现,包括信号与槽机制,属性系统,动态类型转换等
Qt5 元对象系统 Qt5 元对象系统提供了对象间的通信机制(信号与槽),运行时类型信息和动态属性系统的支持,是标准C++的一个扩展,它使Qt能够更好地实现GUI图形用户界面编程
Qt5的元对象系统不支持C++模板,尽管该模板扩展了标准C++的功能
Qt5元对象系统基于以下三个基础组成:
- 基类QObject:任何需要使用元对象系统功能的类必须继承自QObject
- Q_OBJECT宏:Q_OBJECT宏必须出现在类的私有声明区中,用于启动元对象的特性,如动态属性,信号与槽
- 元对象编译器(Meta-Object Compiler, moc):为QObject子类实现元对象特性提供必要的代码实现
构建项目时,MOC工具读取C++源文件,当它发现类的定义里由Q_OBJECT宏时,它就会为这个类生成另外一个包含有元对象支持代码的C++源文件,这个生成的源文件连同类的实现文件一起被编译和连接
除了信号与槽机制外,元对象系统还提供了如下的一些功能
- QObject::metaObject()函数返回类关联的元对象,元对象类QMetaObject包含了访问元对象的一些接口函数
- QMetaObject::newInstance()函数创建类的一个新的实例
- QObject::inherits(const char* className)函数判断一个对象实例是否是名称为className的类或QObject的子类的实例
- QObject::tr()和 QObject::trUtf8()函数可翻译字符串,用于多语言界面设计
- QObject::setProperty()和 QObject::property()函数用于通过属性名称动态设置和获取属性值
信号与槽是Qt的一个核心特点,也是它区别于其他框架的重要特性,信号与槽机制是完成任意两个Qt对象之间的通信机制,也需要由Qt的元对象系统支持才能实现的
Qt使用信号与槽的机制实现对象间通信,它隐藏了复杂的底层实现,完成信号与槽的关联后,信号会在某个特定情况或动作下被触发,槽是等同于接收并处理信号的函数
每个Qt对象都包含若干个预定义的信号和若干个预定义的槽。当某一个特定事件发生时,一个信号被发送,与信号相关联的槽则会响应信号并完成相应的处理。当一个类被继承的时候,该类的信号和槽也同时被继承,也可以根据需要自定义信号和槽
总结
信号与槽(Signal & Slot)是Qt GUI编程的基础,它让Qt在处理界面各个组件的交互操作变得简单而直观。
信号(Signal)就是在特定情况下被发射的事件,例如,PushButton控件最常见的信号就是鼠标单击时发射的clicked()信号,而一个ComboBox控件最常见的信号就是选择的列表项发生变化时发射的CurrentIndexChanged()信号。
而槽(Slot)就是对接收Qt控件发射的信号进行处理的函数,例如,在槽函数中我们可以定义mainWindow窗口对于Pushbutton所发射的信号进行处理,做出相关的操作,比如窗口的关闭,退出,最小化等。和一般的C++函数一样,但槽函数与一般的函数不同的地方是,槽函数可以与一个信号关联,当信号被发射时,关联的槽函数就自动被执行。
信号与槽机制的连接信号与槽关联是用QObject::connect()函数实现的,其基本格式:
QObject::connect(sender, SIGNAL(signal()), receiver, SLOT(slot()));
connect()是QObject类的一个静态函数,而QObject是所有Qt类的基类,在实际调用的时候可以忽略前面的限定符(QObject::),所有我们可以直接写成:
connect(sender, SIGNAL(signal()), receiver, SLOT(slot()));
其中,sender是发送信号的对象的名称,**signal()**是信号名称。信号可以看做是特殊的函数,需要带括号,有参数时还需要指明参数。receiver是接收信号的对象名称,**slot()**是槽函数的名称,需要带括号,有参数时还需要指明参数
SIGNAL和SLOT是Qt的宏,用于指明信号和槽,并将它们的参数转换为相应的字符串
连接方式和注意事项(1)一个信号可以连接多个槽
connect(Object1, SIGNAL(signal1), Object2, SIGNAL(slot1));
connect(Object1, SIGNAL(signal1), Object2, SIGNAL(slot2));
当一个信号与多个槽函数关联时,槽函数按照建立连接时的顺序依次执行
当信号和槽函数带有参数时,在connect()函数里,要写明参数的类型,但可以不写参数名称
(2)一个槽可以响应多个信号
connect(Object1, SIGNAL(signal2), Object2, SIGNAL(slot2));
connect(Object3, SIGNAL(signal2), Object2, SIGNAL(slot2));
(3)一个信号可以与另一个信号相连
connect(Object1, SIGNAL(signal1), Object2, SIGNAL(signal2));
表示Object1的信号1发送可以触发Object2的信号1发送
这样,当一个信号发射时,也会发射另外一个信号,实现某些特殊的功能
(4)严格的情况下,信号与槽的参数个数和类型需要一致,至少信号的参数不能少于槽的参数。如果不匹配,会出现编译错误或运行错误
(5)在使用信号与槽的类中,必须在类的定义中加入宏Q_OBJECT
(6)当一个信号被发射时,与其关联的槽函数通常被立即执行,就像正常调用一个函数一样。只有当信号关联的所有槽函数执行完毕后,才会执行发射信号处后面的代码
注:
SIGNAL()和 SLOT()是Qt定义的两个宏,它们返回其参数的C语言风格的字符串(const char*)。因此,下面关联信号和槽的两个语句是等同的:
connect(button, SIGNAL(clicked()), this, SLOT(rejected()))
connect(button, "clicked()", this, "rejected()");
信号与槽机制的优点 1.类型安全 需要关联的信号和槽的签名必须是等同的,即信号的参数类型和参数个数与接收该信号的槽的参数类型和参数个数相同
2.松散耦合 信号和槽机制减弱了Qt对象的耦合度。激发信号的Qt对象无须知道是哪个对象的哪个槽需要接收它发出的信号,它只需要在适当的时间发送适当的信号,槽同理之。一个类若要支持信号和槽,就必须从QObject或QObject的子类继承。注意,Qt信号和槽机制不支持对模板的使用
信号与槽机制的效率 正所谓,有得必有失,Qt中信号与槽机制在增强了对象间通信的灵活性时,也损失了一些性能。同回调函数相比,信号和槽机制运行速度较慢。通常,通过传递一个信号来调用槽函数会比直接调用非虚函数的运行速度慢10倍
信号与槽机制效率影响的主要原因- 需要定位接收信号的对象
- 安全地遍历所有的关联(如一个信号关联多个槽的情况)
- 编组(marshal)/解组(unmarshal)传递的参数
- 在多线程时,信号可能需要排队等待
Qt GUI界面中的图形坐标和前端坐标一致,即坐标原点在窗口的左上角
坐标基本概念- 在Qt关于窗口的显示是需要指定位置的,并且位置是通过坐标来确定的,坐标的确定依赖于坐标原点
- 在Qt的坐标系统中,坐标原点在窗口的左上角,即
- X轴向右递增
- Y轴向下递增
如图所示:
窗口相对坐标 在Qt的众多控件中,总会出现窗口嵌套的情况,而这其中的嵌套窗口坐标为其父窗口的相对坐标,而不是其主窗口的相对坐标
概述 在一个Qt窗口中一般都有若干个子窗口内嵌到这个父窗口中,其中每个窗口都有自己的坐标原点,子窗口的位置坐标就是它的父窗口坐标体系中的坐标点
窗口相对坐标的简单图示 相对坐标使用图示 Qt中的对象树(Object Tree Model) 在我们C++的编程学习中,我们往往会通过类型指针new一个对应类型的对象或其对象数组,而当我们使用完毕,一个合理且安全的选择便是将其delete释放对应空间的内存,否则会出现内存泄漏的危险
而在Qt的基础学习中,我们往往会看见这样的一个情况,通过类型指针new完对应的控件类型后,没有写出对应的delete操作,而Qt也能够正常地帮我们释放内存,这是为什么呢?
了解程序内存分布 在程序中,内存分布为栈区,堆区,.bss,.data,.rodata,.text等
相关详情:
- 栈区 —— 函数的参数,局部变量,函数调用完毕,内存自动释放
- 堆区 —— malloc函数手动申请的控件,需要手动释放(free)
- .bss —— 未初始化的全局变量,静态变量
- .data —— 存放已经初始化的全局变量,静态变量
- .rodata —— 只读数据段,用于存放一些常量,例如常量字符串
- 使用局部变量
- 局部变量存储在栈中,在超过控件对象的作用域之后,其控件所占用的内存就会被立即释放
- 使用new的方式创建
- new的空间并没有在栈区中,如果没有delete,则内存不会自动释放
我们重新了解一些QObject的构造函数
在帮助文档之中,我们可以了解到的就是,QObject 的构造函数中会传入一个 Parent 父对象指针,children() 函数返回 QObjectList。即每一个 QObject 对象有且仅有一个父对象,但可以有很多个子对象。
按照这种形式排列就会形成一个树的结构,下层是其上层的子对象,依次类推,而Qt开发者选择这样设计的主要原因就是,构建这样对象树的结构方便内存的管理。
根据C++析构的方法,当父对象被析构的时候,这个子对象列表中的所有对象都会被析构,而当析构子对象的时候,会自动地从父对象的子对象列表中删除掉
这就是为什么我们经常看见Qt GUI编程中,new了很多控件对象,但没有delete的原因,这是因为Qt所有常见的控件对象的基类都为QObject,而在控件销毁时这些子控件以及布局管理对象都会一并销毁
如图所示:
关于QObject对象树 QObject内部有一个list,会保存到children,还有一个指针保存parent,当QObject对象析构时,会自动从parent列表中删除并且析构所有的children



