auther: abinng date: 2026-03-21 11:29
createDate:2026-03-21 11:29
信号和槽的连接
上一篇我们学过了 Qt 的元对象系统,就是为了信号和槽做铺垫
信号和槽的作用是什么呢?
实现 Qt 的对象之间通信、解耦
信号和槽的实现:
设计信号、槽函数
关联信号和槽函数
在一个合适的位置发射信号
怎么将信号和槽关联起来呢?
1 2 3 4 QMetaObject::Connection QObject::connect (const QObject *sender, const char *signal, const QObject *receiver, const char *method, Qt::ConnectionType type = Qt::AutoConnection) QMetaObject::Connection QObject::connect (const QObject *sender, const char *signal, Functor functor)
所以,信号和槽其实就是有若干信号和若干处理函数,将某信号和某槽函数连接起来,之后调用信号函数,就会自动触发与之绑定的槽函数。且该连接是可以断开的,并不是死板的绑定
直接看代码,代码中有对应注释,结合上面引入中的讲解,更好理解
简单的信号和槽连接
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 #include <QObject> #include <qtmetamacros.h> class A : public QObject { Q_OBJECT public : explicit A (QObject *parent = nullptr ) ; signals: void a_signal () ; }; class B : public QObject { Q_OBJECT public : explicit B (QObject *parent = nullptr ) ; public slots: void b_slot () ; };
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 #include "info.h" #include <QDebug> A::A (QObject *parent) : QObject (parent) { } B::B (QObject *parent) : QObject (parent) { } void B::b_slot () { qDebug () << "start b_slot()" ; }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 #include <QApplication> #include <QObject> #include <qapplication.h> #include "info.h" void test01 () { A a; B b; QObject::connect (&a, SIGNAL (a_signal ()), &b, SLOT (b_slot ())); getchar (); emit a.a_signal (); } int main (int argc, char * argv[]) { test01 (); return 0 ; }
上面使用了几种方式来连接信号和槽
字符串形式的 connect
函数,但是这种方法有一个弊端就是,当我们不小心写错了信号函数/槽函数的名称,编译器也不会报错,会导致
Debug 的时候效率很低
(可以去实际测试一下,故意写错函数名称,也不会报错)
所以现在更推荐的是用函数指针的方式而不是函数名称的方式,这种方式可以进行检查
函数指针形式的 connect
函数,就是将函数名称部分换成函数指针了
信号和槽是同步还是异步
是同步的,我们可以通过下面的代码进行验证
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 #include "info.h" #include <QDebug> #include <chrono> #include <thread> A::A (QObject *parent) : QObject (parent) { } B::B (QObject *parent) : QObject (parent) { } void B::b_slot () { qDebug () << "start b_slot()" ; std::this_thread::sleep_for (std::chrono::milliseconds (1000 )); qDebug () << "end b_slot()" ; }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 #include <QApplication> #include <QObject> #include <qapplication.h> #include "info.h" void test01 () { A a; B b; QObject::connect (&a, &A::a_signal, &b, &B::b_slot); getchar (); emit a.a_signal (); } int main (int argc, char * argv[]) { QApplication app (argc, argv) ; test01 (); qDebug () << "=====================" ; return app.exec (); }
如果是异步的,那么运行后应该看到的结果是:
1 2 3 start b_slot () =====================end b_slot ()
但是实际的结果是:
1 2 3 start b_slot () end b_slot () =====================
说明槽函数执行完之前,是不会执行其他代码的
但同时我们也要注意,槽函数不能占用太多时间,不然就会导致 CPU
一直在执行槽函数,对于用户体感来说会出问题。例如用户短时间执行了两次操作,但是第一个操作对应的槽函数需要处理十几秒,那么显然会影响另一个槽函数的执行
函数指针形式的 connect
上面写的都是无参数的,那我们可以试试带参数的信号和槽
1 2 3 4 5 // info.h - void a_signal(); + void a_signal(int); // 因为信号函数只需要写声明,所以不写形参名也可以 - void b_slot(); + void b_slot(int a); // 槽函数需要写实现,所以得带上形参名
1 2 3 4 5 6 7 8 9 10 11 // info.cpp - void B::b_slot() { - qDebug() << "start b_slot()"; - std::this_thread::sleep_for(std::chrono::milliseconds(1000)); - qDebug() << "end b_slot()"; - } + void B::b_slot(int a) { + qDebug() << "start b_slot()" << a; + std::this_thread::sleep_for(std::chrono::milliseconds(1000)); + qDebug() << "end b_slot()"; + }
1 2 3 4 5 6 7 // main.cpp void test01() { ... ... - emit a.a_signal(); + emit a.a_signal(10); }
之后执行,输出为:
1 2 3 start b_slot () 10 end b_slot () =====================
看到代码和对应的输出之后,就大概理解了这个参数是怎么在信号和槽之间传递的了
就是
信号无参--槽无参、信号有参---槽有参
但是有一个明显的缺点是:函数重载时该怎么办呢?
1 2 3 4 5 6 7 // info.h class A 中: + void a_signal(); + void a_signal(int); class B 中: + void b_slot(); + void b_slot(int a);
1 2 3 4 5 6 7 // info.cpp + void B::b_slot() { + qDebug() << "start b_slot()"; + } + void B::b_slot(int a) { + qDebug() << "start b_slot(int a)" << a; + }
此时回到 main.cpp ,就会发现已经报错了,编译一下:
error: no matching function for call to xxxxxxxx
就是,没有可以匹配的,不知道怎么匹配信号和槽
这时候,字符串形式的 connect
函数就可以解决该问题了,毕竟我们传信号函数和槽函数时,用的是
SIGNAL(a_signal()) 和 SLOT(b_slot())
就直接代表,将两个无参数的函数连接起来
试试:
1 2 3 4 5 6 - QObject::connect(&a, &A::a_signal, &b, &B::b_slot); + // QObject::connect(&a, &A::a_signal, &b, &B::b_slot); + QObject::connect(&a, SIGNAL(a_signal()), &b, SLOT(b_slot())); getchar(); emit a.a_signal(10);
运行,会发现没有输出,因为我们绑定的是无参数的信号函数,但是我们调用/发射的是有参数的信号。只要切换成调用/发射无参数的信号就可以运行并得到输出了
那么,我就不能继续使用函数指针的方式吗?不能破吗?
还是可以的— qOverload
上面说编译出错的地方是
1 QObject::connect (&a, &A::a_signal, &b, &B::b_slot);
只要我们通过 qOverload 写成:
1 2 3 4 5 6 QObject::connect (&a, qOverload<>(&A::a_signal), &b, qOverload<>(&B::b_slot)); QObject::connect (&a, qOverload <int >(&A::a_signal), &b, qOverload <int >(&B::b_slot)); QObject::connect (&a, QOverload<>::of (&A::a_signal), &b, QOverload<>::of (&B::b_slot)); QObject::connect (&a, QOverload<int >::of (&A::a_signal), &b, QOverload<int >::of (&B::b_slot));
写过之后其实已经就不会报错了,可以编译运行试试,也是不会报错的
参数匹配问题
两种情况:
信号传递参数,槽函数不需要参数,或者少于信号传递的参数
1 QObject::connect (&a, qOverload <int >(&A::a_signal), &b, qOverload<>(&B::b_slot));
此时是可以正常运行的
信号不传递参数,槽函数需要参数,或者信号传参少于槽函数需要的参数
1 QObject::connect (&a, qOverload<>(&A::a_signal), &b, qOverload <int >(&B::b_slot));
此时编译不能通过,会报错:error: static assertion failed: The slot requires more arguments than the signal provides.
意思就是槽函数需要的参数多余信号函数提供的参数
信号和槽的重复关联
假设我们连接两次:
1 2 3 4 QObject::connect (&a, qOverload<>(&A::a_signal), &b, qOverload<>(&B::b_slot)); QObject::connect (&a, qOverload<>(&A::a_signal), &b, qOverload<>(&B::b_slot)); emit a.a_signal ();
输出:
1 2 3 start b_slot () start b_slot () =====================
也就是说,连接两次,发射信号的时候,就会执行两次槽函数
同理,一个信号连接两个槽函数也是一样的:
1 2 3 start b_slot () start b_slot1 () =====================
其实一个信号连接一个槽函数两次,可以看作一个信号连接两个相同的槽函数一次
所以我们建立连接关系的时候,最好是放在一块来
connect 总结
总结一下,有 7 种情况
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 void test01 () { A a; B b; getchar (); emit a.a_signal (); }
槽函数的合并
拿 Windows 计算器举例:
这几个数字按键对应的槽函数极其相似,那我们能不能多个按键合并成一个槽函数呢?
但是问题又来了,合并后,怎么区分是哪个按键呢?—>就是接下来要说的内容了
1 2 3 4 5 6 7 [protected ] QObject *QObject::sender () const template <typename T> T qobject_cast (const QObject *object)
info.h 中
点击展开
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 #include <qtmetamacros.h> #include <QObject> class A : public QObject { Q_OBJECT public : explicit A (int age = 10 , QObject *parent = nullptr ) ; int getAge () const ; signals: void a_signal () ; void a_signal (int ) ; void click () ; private : int m_age; }; class B : public QObject { Q_OBJECT public : explicit B (QObject *parent = nullptr ) ; public slots: void b_slot () ; void b_slot1 () ; void b_slot (int a) ; void abc () ; };
info.cpp 中
点击展开
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 #include "info.h" #include <QDebug> #include <chrono> #include <thread> A::A (int age, QObject *parent) : QObject (parent), m_age (age) {} int A::getAge () const { return m_age; } B::B (QObject *parent) : QObject (parent) {} void B::b_slot () { qDebug () << "start b_slot()" ; } void B::b_slot1 () { qDebug () << "start b_slot1()" ; } void B::b_slot (int a) { qDebug () << "start b_slot(int a)" << a; } void B::abc () { A *obj = qobject_cast <A *>(sender ()); if (obj == nullptr ) { qDebug () << "No Support!" ; return ; } qDebug () << obj->getAge (); }
main.cpp 中
点击展开
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 void test02 () { A x1 (19 ) ; A x2 (22 ) ; QWidget w; B b; QObject::connect (&x1, &A::click, &b, &B::abc); QObject::connect (&x2, &A::click, &b, &B::abc); QObject::connect (&w, &QWidget::destroyed, &b, &B::abc); } int main (int argc, char *argv[]) { QApplication app (argc, argv) ; test02 (); qDebug () << "=====================" ; return app.exec (); return 0 ; }