初识Qt——信号和槽

发布时间:2026/8/14 14:15:19
初识Qt——信号和槽 1. 信号和槽函数之前我们在Linux中也学过信号。虽然Qt的信号和Linux中的信号是完全不同的概念但两者在抽象层面上确实有异曲同工之处。Linux中的信号是系统内部的通知机制或进程间的通信方式其本质是操作系统层面上的异步中断机制。一旦信号被触发内核会中断当前进程的执行流直接跳转执行预先注册的信号处理器函数。Qt信号则是应用程序框架层面的通信机制它基于Qt的事件循环。当信号被emit触发时Qt会根据连接类型决定是立即同步调用槽函数本质上也是一种回调函数还是将信号封装为事件放入队列中异步等待处理。这两种信号机制都遵循一个共同的抽象模型——三个核心要素三个核心要素信号源谁发出的信号。Linux中是内核、硬件或其他进程Qt中是QObject派生类的实例对象。信号类型信号的标识。Linux中是固定的31个标准信号编号如SIGINT、SIGSEGVQt中是由开发者任意定义的可携带复杂参数的信号。信号处理方式谁来处理、如何处理。Linux中通过signal()或sigaction()注册处理器函数Qt中通过connect()将信号与槽函数绑定。2. connect函数connect(sender, Sender::signal, receiver, Receiver::slot);sender发送信号的对象指针必须是QObject派生类的指针——谁发的信号Sender::signal发送者类中的信号类型指针本质是成员函数指针——发送的什么类型的信号receiver接收信号的对象指针也必须是QObject派生类指针且该对象必须存活到信号触发时——谁来处理这个信号Receiver::slot接收者类中的槽函数指针可以是普通槽函数、信号、Lambda表达式、全局函数——怎么处理这个信号注意Qt中的connect函数和Linux中 TCP socket中建立连接的函数没有任何关系只是名字恰巧一样了下面我们在widget.cpp的构造函数中实现一个可以关闭窗口的按钮#include widget.h #include ui_widget.h #include QPushButton Widget::Widget(QWidget *parent) : QWidget(parent) , ui(new Ui::Widget) { ui-setupUi(this); QPushButton* close new QPushButton(this); close-setText(关闭窗口); close-move(200,200); connect(close,QPushButton::clicked,this,QWidget::close); } Widget::~Widget() { delete ui; }运行后界面出现一个“关闭窗口”的按钮点击按钮就可以实现窗口的关闭那么问题来了我们怎么知道有哪些信号和槽函数呢就像这里的clicked和close答案就是——看官方文档磨刀不误砍柴工用的多了记住的也就多了不记得的就去看看文档。有时候在对应的类中没有找到相对应的信号或者槽就找找这个类的父类或者祖宗类。3. 自定义槽函数3.1 普通成员函数定义自定义槽函数其实就和自定义一个普通的函数没什么区别//widget.h #ifndef WIDGET_H #define WIDGET_H #include QWidget QT_BEGIN_NAMESPACE namespace Ui { class Widget; } QT_END_NAMESPACE class Widget : public QWidget { Q_OBJECT public: explicit Widget(QWidget *parent nullptr); ~Widget() override; //自定义一个槽函数 void handlePush(); private: Ui::Widget *ui; }; #endif // WIDGET_H//widget.cpp #include widget.h #include ui_widget.h #include QPushButton #include QMessageBox Widget::Widget(QWidget *parent) : QWidget(parent) , ui(new Ui::Widget) { ui-setupUi(this); QPushButton* push new QPushButton(this); push-setText(按下); push-move(200,200); //使用自定义的槽函数 connect(push,QPushButton::clicked,this,Widget::handlePush); } Widget::~Widget() { delete ui; } void Widget::handlePush() { //设计一个弹出窗口提示显示“按钮被按下” QMessageBox::information(this, 提示, 按钮被按下); }运行后点击按钮之前的版本中槽函数必须放到public/private/protected slots下Qt5之后就可以不用这样写了。但是在使用图形化界面自动生成槽函数时也会自动生成。3.2 图形化界面自动生成下面我们来尝试图形化界面自动生成槽函数。双击.ui文件进入图形化界面拖拽一个按钮到界面中修改按钮显示文字右键单击按钮选择“转到槽”可以看到和按钮QPushButton及其父类和祖宗类相关的所有信号我们选中第一个clicked并确定或者双击clicked就会自动生成槽函数声明和定义并直接跳转到对应槽函数定义中我们可以直接在这里写相关的代码。但是我们会发现这样操作后并没有自动生成connect函数那么信号和槽怎么连接呢我们观察一下代码自动生成的槽函数的函数名(on_控件名_信号类型)当函数名符合上述规则后Qt就能自动把信号和槽建立上联系。所以如果是通过代码手动创建控件并自定义槽函数时我们还是要使用connect如果是使用图形化界面的方式来实现自定义槽函数就可以不用管connect。3.3Lambda表达式lambda表达式比较常用于回调函数而槽函数本质上也是一种回调函数所以也常用lambda表达式写槽函数而且lambda表达式写槽函数会更简洁。//widget.h #ifndef WIDGET_H #define WIDGET_H #include QWidget QT_BEGIN_NAMESPACE namespace Ui { class Widget; } QT_END_NAMESPACE class Widget : public QWidget { Q_OBJECT public: explicit Widget(QWidget *parent nullptr); ~Widget() override; private: Ui::Widget *ui; }; #endif // WIDGET_H#include widget.h #include ui_widget.h #include QPushButton #include QMessageBox Widget::Widget(QWidget *parent) : QWidget(parent) , ui(new Ui::Widget) { ui-setupUi(this); connect(ui-pushButton,QPushButton::clicked,this,[this](){ QMessageBox::information(this, 提示, lambda表达式实现槽函数); }); } Widget::~Widget() { delete ui; }点击界面按钮后如果我们的槽函数只是一次性使用并且比较简短就可以采用槽函数的形式。另外也要确认捕获到lambda内部的变量是有意义的因为槽函数执行的时机是不确定的无法确定用户什么时候触发我们要确保无论何时用户点击了按钮捕获到的变量都能正确使用。4. 自定义信号自定义信号是你在自己的类中声明的、由moc自动生成实现的特殊成员函数。它的作用是在特定事件发生时通知其他对象。class MyClass : public QObject {Q_OBJECTsignals: // 信号区域void mySignal();};自定义信号使用关键规则信号必须在signals: 关键字下声明这是Qt的特殊关键字信号只需要声明不需要实现moc会自动生成实现信号的返回值必须是void信号可以有任意数量和类型的参数在 signals: 下默认是protected信号被触发时要用到emit关键字也是Qt特有的关键字但是emit什么实际作用都没有去掉emit代码也不会有任何影响但是一般在开发过程中还是会加上它增加代码的可读性下面我们来演示一下通过自定义信号的方式也实现一个点击按钮出现弹窗的效果//widget.h #ifndef WIDGET_H #define WIDGET_H #include QWidget QT_BEGIN_NAMESPACE namespace Ui { class Widget; } QT_END_NAMESPACE class Widget : public QWidget { Q_OBJECT public: explicit Widget(QWidget *parent nullptr); ~Widget() override; //自定义一个槽函数 void handlePush(); signals: //自定义一个信号 void MySignal(); private slots: void on_pushButton_clicked(); private: Ui::Widget *ui; }; #endif // WIDGET_H//widget.cpp #include widget.h #include ui_widget.h #include QPushButton #include QMessageBox Widget::Widget(QWidget *parent) : QWidget(parent) , ui(new Ui::Widget) { ui-setupUi(this); connect(this,Widget::MySignal,this,Widget::handlePush); } Widget::~Widget() { delete ui; } void Widget::handlePush() { QMessageBox::information(this, 提示, 按钮被按下); } void Widget::on_pushButton_clicked() { //触发自定义的MySignal信号 emit MySignal(); //MySignal(); }在widget类中自定义一个MySignal信号将这个信号和handlePush连接起来按下按钮时触发该信号从而实现弹窗效果。这里只是为了讲解自定义信号才这样设计实际开发中肯定不会自定义一个Qt本身已经有相同效果的信号能用Qt中自带的信号就用已有的。5. 带参数的信号和槽信号和槽其实都是可以带参数的此时发射信号的时候就可以给信号函数传递实参与之对应的这个参数就会被传递到相应的槽函数中。传参可以起到复用代码的效果——有多个逻辑整体一致但是涉及到的数据不同就可以通过函数参数)来实现复用代码在不同的场景传入不同的参数即可。Qt中很多内置信号也是带有参数的大家也可以去了解一下。还是以我们前面的代码为例来实现一下通过传参来设计窗口弹出的内容//widget.h #ifndef WIDGET_H #define WIDGET_H #include QWidget QT_BEGIN_NAMESPACE namespace Ui { class Widget; } QT_END_NAMESPACE class Widget : public QWidget { Q_OBJECT public: explicit Widget(QWidget *parent nullptr); ~Widget() override; //自定义一个槽函数 void handlePush(const QString); signals: //自定义一个信号 void MySignal(const QString); private slots: void on_pushButton_clicked(); private: Ui::Widget *ui; }; #endif // WIDGET_H//widget.cpp #include widget.h #include ui_widget.h #include QPushButton #include QMessageBox Widget::Widget(QWidget *parent) : QWidget(parent) , ui(new Ui::Widget) { ui-setupUi(this); connect(this,Widget::MySignal,this,Widget::handlePush); } Widget::~Widget() { delete ui; } void Widget::handlePush(const QString context) { QMessageBox::information(this, 提示, context); } void Widget::on_pushButton_clicked() { //触发自定义的MySignal信号 emit MySignal(实现带参信号和槽); }点击按钮后的效果信号int和槽int的参数类型必须一致但是个数可以不一致。如果个数不一致要求信号int, QString的参数个数必须大于槽(int)的参数个数。信号传过来的参数槽可以选择接收前几个忽略后面的。在Qt中可以实现一个信号和多个槽函数绑定一个槽函数也能被多个信号绑定所以这样设计参数的传递机制可以让信号和槽的绑定更加灵活。但是这个“多对多”的功能在实际开发中不怎么用到绝大多数情况“一对一”就够用了所以现在很多GUI开发框架都不支持这种“多对多了”。6. 信号和槽断开disconnect(sender, Sender::signal, receiver, Receiver::slot);将绑定的信号和槽断开要用到disconnect函数用法和connect函数差不多。实际开发中很少会用到一般只有要将某个信号重新绑定到新的槽函数上才需要先disconnect再重新connect否则就会出现信号和槽“一对多”的情况虽然不会报错但是可能导致功能混乱。