信号和槽详解
信号和槽机制在元对象系统时简单介绍并使用了,这里详细介绍,包括:
1信号和槽的定义
2如何连接信号和槽
3信号和槽的参数匹配规则
4一个信号连接多个槽函数
5连接信号和信号
6信号和槽的几种连接方式
7发送信号
信号和槽的定义
信号需要声明在signals关键字下面
cpp
ClassA {
signals:
void mySignal(int value);
void myPropertyChanged(int value);
}
信号的定义包括 信号的返回类型,一般都是void,信号的名字,以及信号携带的参数。
例如mySignal这个信号返回类型为void,参数为int类型。
信号不需要写实现逻辑
槽函数的声明放在slots关键字下面,但是槽函数有权限控制,有的槽函数是公有的,有的是私有的,有的是受保护的。
槽函数返回类型一般为void,也可以携带参数。
cpp
ClassA {
public slots:
void publicSlot(int value);
protected slots:
void protectedSlot(int value);
private slots:
void privateSlot(int value);
}
槽函数需要写实现逻辑
cpp
void ClassA::publicSlot(int value)
{
qDebug()<<"called public slot, value is " << value;
}
void ClassA::protectedSlot(int value)
{
qDebug()<<"called protected slot, value is " << value;
}
void ClassA::privateSlot(int value)
{
qDebug()<<"called private slot, value is " << value;
}
关于访问控制,私有的槽函数只能在其所属的类中连接,比如privateSlot只能在ClassA的函数中连接
比如我们将私有的槽函数和信号连接逻辑放在ClassA的构造函数里
cpp
ClassA::ClassA()
{
//在构造函数中连接自己的信号和槽函数
connect(this,&ClassA::mySignal,this,&ClassA::privateSlot);
}
如何连接信号和槽
连接信号和槽的写法基本如下
cpp
connect(sender, &SenderType::signalName, receiver, &ReceiverType::slotName);
翻译过来就是
cpp
connect(信号发出者的指针,信号的指针,接收者的指针,槽函数的指针)
可以在任何函数, 连接ClassA的信号和公有槽函数
cpp
void con_sig_slot(){
ClassA ca;
//连接ca的mySignal信号和ca的publicSlot槽
QObject::connect(&ca, &ClassA::mySignal, &ca, &ClassA::publicSlot);
}
练习
练习一下,写一下信号和槽连接的代码
注意
在普通函数和main函数中连接信号和槽,需要使用QObject::connect, 需显示值明使用的是QObject下的connect信号。
如果在支持了元对象系统(1 继承了QObject, 2 声明了Q_OBJECT宏 ) 的类里连接信号和槽,可以直接使用connect进行连接。
注意
部分公司信号和槽的连接写法采用QT4版本之前的写法,使用 SIGNAL 和 SLOT 宏:
cpp
connect(sender, SIGNAL(signalName()), receiver, SLOT(slotName()));
cpp
void con_sig_slot_v4(){
ClassA ca;
//连接ca的mySignal信号和ca的publicSlot槽
QObject::connect(&ca, SIGNAL(mySignal()), &ca, SLOT(publicSlot()));
}
这种写法不推荐,编译器不做检测,很多错误在运行时才爆发,很危险。
C++11 风格
qt信号和槽也支持C++11风格连接
cpp
connect(sender, &SenderType::signalName, [=]() {
// Lambda 表达式中的代码
});
例如
cpp
void con_sig_slot_v4(){
ClassA ca;
//连接ca的mySignal信号和ca的publicSlot槽
QObject::connect(&ca, SIGNAL(mySignal()), [=](){
//Lambda 表达式
});
}
信号和槽函数匹配规则
在 Qt 的信号和槽机制中,信号的参数可以比槽函数的参数多,但必须满足以下条件:
- 参数类型和顺序匹配:槽函数的参数类型和顺序必须与信号的前几个参数类型和顺序一致。
- 参数数量:槽函数的参数数量必须小于或等于信号的参数数量。
示例
我们能对ClassA 新增一个携带多个参数的信号paramSignal和一个处理多个参数的槽函数paramSlot
cpp
class ClassA:public QObject
{
Q_OBJECT
public:
ClassA();
signals:
void paramSignal(int value, QString str, double dvalue);
public slots:
void paramSlot(int value, QString str);
};
因为paramSlot的参数比paramSignal的参数少,但是槽函数的参数类型和信号的前几个参数类型顺序一致,所以可以连接
cpp
void con_param_sig_slot(){
ClassA ca;
//连接ca的paramSignal信号和ca的paramSlot槽
QObject::connect(&ca, &ClassA::paramSignal, &ca, &ClassA::paramSlot);
}
一个信号匹配多个槽函数
一个信号可以连接到多个槽函数, 比如我们的类中声明并定义了多个槽函数和信号
cpp
class ClassA:public QObject
{
Q_OBJECT
public:
ClassA();
signals:
void mySignal(int value);
void mySignal2(int value);
public slots:
void publicSlot(int value);
void publicSlot2(int value);
};
我们可以让信号mySignal连接到publicSlot和publicSlot2
cpp
void con_sig_mulslots(){
ClassA ca;
//一个信号连接多个槽
QObject::connect(&ca, &ClassA::mySignal, &ca, &ClassA::publicSlot);
QObject::connect(&ca, &ClassA::mySignal, &ca, &ClassA::publicSlot2);
}
连接信号和信号
信号可以连接信号,将消息转发给其他信号
cpp
void con_sig_sig(){
ClassA ca;
//一个信号连接另一个信号
QObject::connect(&ca, &ClassA::mySignal, &ca, &ClassA::mySignal2);
}
信号和槽的几种连接方式
在 Qt 中,信号和槽的连接方式有几种不同的模式,分别是默认模式(Qt::AutoConnection)、直接连接(Qt::DirectConnection)、队列连接(Qt::QueuedConnection)、阻塞连接(Qt::BlockingQueuedConnection)以及唯一连接(Qt::UniqueConnection)。这些连接方式决定了槽函数在哪个线程中被调用。以下是每种连接方式的详细说明:
- 默认模式(
Qt::AutoConnection)
在连接信号和槽时,我们不写具体的连接方式,
说明:
- 行为:默认模式下,Qt 会根据信号和槽所在的线程自动选择连接方式。如果信号和槽在同一个线程中,使用直接连接;如果信号和槽在不同线程中,使用队列连接。
- 槽函数线程:如果信号和槽在同一个线程中,槽函数在发送信号的线程中调用。如果信号和槽在不同线程中,槽函数在接收信号的对象所属的线程中调用。
示例:
cpp
QObject::connect(sender, &Sender::signal, receiver, &Receiver::slot, Qt::AutoConnection);
- 直接连接(
Qt::DirectConnection)
说明:
- 行为:信号发出时,槽函数立即在发出信号的线程中被调用。
- 槽函数线程:槽函数在发送信号的线程中调用。
示例:
cpp
QObject::connect(sender, &Sender::signal, receiver, &Receiver::slot, Qt::DirectConnection);
- 队列连接(
Qt::QueuedConnection)
说明:
- 行为:信号发出时,槽函数调用被放入接收对象所在线程的事件队列中,槽函数将在接收对象的线程中被调用。
- 槽函数线程:槽函数在接收信号的对象所属的线程中调用。
示例:
cpp
QObject::connect(sender, &Sender::signal, receiver, &Receiver::slot, Qt::QueuedConnection);
- 阻塞连接(
Qt::BlockingQueuedConnection)
说明:
- 行为:信号发出时,槽函数调用被放入接收对象所在线程的事件队列中,且发送信号的线程会阻塞,直到槽函数执行完毕。仅在多线程环境下有效。
- 槽函数线程:槽函数在接收信号的对象所属的线程中调用。
示例
cpp
QObject::connect(sender, &Sender::signal, receiver, &Receiver::slot, Qt::BlockingQueuedConnection);
- 唯一连接(
Qt::UniqueConnection)
说明:
- 行为:确保信号和槽之间只有一个连接。如果尝试创建重复连接,连接操作会失败。可以与其他连接类型组合使用。
- 槽函数线程:根据与其他连接类型组合使用的结果确定。
示例
cpp
QObject::connect(sender, &Sender::signal, receiver, &Receiver::slot, Qt::UniqueConnection | Qt::AutoConnection);
以上连接方式不用过多关注,实际开发采用默认连接即可,面试会问到信号和槽的连接方式。
发送信号
发送信号使用emit 关键字,后面跟信号即可
cpp
void emit_sig(){
ClassA ca;
emit ca.mySignal(100);
emit ca.paramSignal(1024,"hello hema",3.14);
}
练习
定义一个类ClassA,ClassA包含一个信号sigNotify, 参数为(QString , int)类型。
定义一个类ClassB,ClassB包含一个公有的槽函数slotNofiy, 参数为(QString , int), slotNotify 打印收到的信息
实现函数nofity_func, 函数内部分别定义ca对象(Class A类型)以及cb对象(Class B类型),连接ca的sigNotify和cb的slotNotify。
并且发送ca的sigNotify, 测试cb的槽函数是否触发。
答案
分别定义ClassA和ClassB
cpp
class ClassA:public QObject
{
Q_OBJECT
public:
ClassA();
signals:
void sigNotify(QString name, int year);
};
classB
cpp
class ClassB: public QObject{
Q_OBJECT
public:
ClassB(){}
public slots:
void slotNotify(QString name, int year);
};
void ClassB::slotNotify(QString name, int year)
{
qDebug() << "slotNotify called, name is " << name
<< " year is " << year;
}
定义函数连接并发送信号
cpp
void con_ca_cb(){
ClassA ca;
ClassB cb;
QObject::connect(&ca, &ClassA::sigNotify, &cb, &ClassB::slotNotify);
emit ca.sigNotify("zack",28);
}
main函数调用con_ca_cb会看到程序输出
cpp
slotNotify called, name is "zack" year is 28