信号槽机制
1、信号槽、回调函数及观察者模式间的关系
信号槽可以说是Qt框架中最核心的机制,也可以说是Qt的灵魂,是每个学习或使用Qt必须掌握的技能之一。看过很多博客,大家对信号槽本质的理解都有各自不同的看法,这里不做详细说明,如果大家有兴趣,可以看看涛哥-认清信号槽的本质 这篇文章。
2、元对象编译器MOC原理
所谓的元对象编译器MOC,简单的理解其实就是一个编译工具,它是将我们Qt中的Q_SIGNALS、Q_SLOTS、Q_OBJECT及自定义信号等信息编译成C++所识别的信息,并一“元信息”的方式存放于moc_xxx.cpp文件中,供C++对其进行后续的编译和链接。MOC的本质是反射器,java中有,C++中是没有的,简单的来说就是在程序运行时,通过“元信息”+“invokeMethord()”函数映射的调用类的构造函数、成员函数及成员变量等信息。
3、信号与槽函数
- 信号
所谓的信号就是用户点击鼠标或者输入按键信息,所发送的消息。Qt中有自带的信号,也可以自定义信号,信号无返回值,不需要实现(MOC已经帮我们实现),信号可带参数也可不带参数。参数的类型,必须是元对象系统能够识别的类型, 即****元类型**
(定义在QMetaType
**中)。 - 槽函数
槽函数可以理解为普通的函数,需要我们对其实现。 - 信号和槽函数参数类型
信号和槽函数参数类型必须是元对象系统能够识别的类型,如果想要自定义参数类型,需要提前注册**元类型
。
Qt已经将大部分常用的基础类型,都注册进了元对象系统,可以在**QMetaType类**
中看到。通常写的继承于QObject的子类,本身已经附带了元信息,可以直接在信号-槽中使用。但是,对于不是继承于QObject的结构体、类等自定义类型,可以通过Q_DECLARE_METATYPE宏
** 和**qRegisterMetaType函数**
进行注册,之后就可以在信号-槽中使用。用法如下:
class MyClass
{
QString strValue;
};
Q_DECLARE_METATYPE(MyClass)
// 在当前类的顶部包含:
#include <QMetaType>;
// 构造函数中加入代码:
qRegisterMetaType<MyClass>("Myclass");
4、connect函数的参数
// connect函数的参数
bool isSuccess = connect(发送者, 发送信号, 接收者, 槽函数, 连接类型);
// connect返回值是bool类型,返回true表示连接成功,否则,失败;
connect函数总共有五个参数,前四个参数此处不做解释,主要来看下第五个参数,一般面试时常常会问到。connectType对应的有五个值,每个值都代表不同的含义。
- 自动连接
默认链接。信号发送方与接收方在同一线程时,使用直接连接;
发送方与接受方不在同一线程时,采用队列链接方式。 - 直接连接
槽函数在运行在信号发送者所在线程,相当于函数调用; - 队列连接
槽函数运行于信号接收者所在线程。信号发送后,槽函数不会立即被调用
需要等到接收者当前函数执行完,进入事件循环之后,槽函数才会被执行。 - 阻塞连接
信号发送后,发送者所在线程会被阻塞,知道槽函数执行完。
接收者和发送者不能在同一线程,否则会死锁。多线程同步的时候会用到。 - 重复连接
通过按位或与其他参数进行匹配,主要是避免重复链接;
那么,有的人会问既然已经有了自动连接了,为什么还要有直接连接和队列链接两个值呢,这不是多此一举吗?其实不然,自动连接是在程序运行时根据发送者和接收者是否在同一线程自动调用,有时候我们为了实现某种特殊要求,需要手动添加直接连接和队列链接。那么,什么情况下使用直接连接,什么情况下使用队列链接?
- 跨线程DirectConnection
A线程为内部代码,不能修改,一些特定的节点会有信号发出,B线程为用户代码,有一些功能函数,希望在A线程中去执行。这种情况,将A的信号连接到B的函数,连接方式指定为DirectConnection,就可以把B的函数插入到A线程发信号的地方了。效果类似于子类重载父类的函数。 - 跨多个线程手动添加 QueuedConnection
A线程中写connect,让B线程中信号连接到C线程的槽上,这时候希望C中的槽
在C中执行;不明确指出C中的槽会在A中执行
5、connect连接写法
- 传统类型
// 传统方式
QPushButton btn;
QWidget widget;
connect(&btn, SIGNAL(sendSignal(QString)), &widget, SLOT(reciver(QString)));
缺点:程序在编译时,不做类型检查,如果出现参数不匹配、信号或槽不存在,都不会报错;
- 函数指针型
QPushButton btn;
QWidget widget;
connect(&btn, &QPushButton::sendSignal, &widget, &QWidget::reciver);
需要注意:出现重载函数,需要对其进行类型转换
。
// 方式一:C++11新写法(lambda写法)
connect(ui->comboBox, QOverload<int>::of(&QComboBox::currentIndexChanged), this, [=](int index){
QString info = QString("SELECT INDEX: %1").arg(index);
ui->textEdit_Info->append(info);
});
// 方式二:C++14支持(非lambda写法)
connect(ui->comboBox, qOverload<const QString &>(&QComboBox::activated),
label, &QLabel::setText);
- lambda类型
特点:对于函数所占用代码行数不多,可以采用lambda形式,项目中往往不建议使用,出现问题,不好跟踪。