信号和槽简介
信号和槽简介
Qt 的 信号(Signal)与槽(Slot)机制 是其最核心的特性之一,用于实现对象之间的事件驱动式通信,特别适用于 GUI 开发中控件响应用户操作的场景。它是一种比回调函数更安全、解耦性更高的通信方式。
信号
信号(Signal)是什么
信号是对象在特定事件发生时“发出的通知”,用于告诉其他对象“某事发生了”。
特点:
- 是 Qt 元对象系统(Meta-Object System) 的一部分。
- 通常是类的一部分(用
signals:声明),但不是函数实现。 - 可以传递参数,支持重载。
- 不需要手动调用,常通过控件内部事件自动触发。
- 只能由该类或其子类通过
emit发出。
信号的声明语法
signals:
void signalName(); // 无参数信号
void signalWithArgs(int value, QString name); // 有参数信号
注意:信号没有函数体!它们只是声明,不需要定义。
触发信号:使用 emit 关键字
在类中,可以在需要时调用 emit 发射信号:
emit signalWithArgs(42, "example");
使用信号的前提条件
-
类必须继承自
QObject。 -
类中必须包含
Q_OBJECT宏(启用元对象功能)。
槽
什么是槽(slot)
槽是一个普通的成员函数,但它可以连接(connect)到一个信号(signal),当信号被发射时,Qt 自动调用相应的槽函数。
语法示例
#include <QPushButton>
#include <QObject>
class MyObject : public QObject {
Q_OBJECT // 必须宏,启用 Qt 的元对象机制
public slots: // 标识该区域内的函数为槽函数
void onButtonClicked() {
qDebug("Button was clicked!");
}
};
// 连接信号和槽
QPushButton* button = new QPushButton("Click me");
MyObject* obj = new MyObject;
QObject::connect(button, &QPushButton::clicked, obj, &MyObject::onButtonClicked);
slot 是做什么的
-
在
Q_OBJECT宏支持下,slots关键字用于声明函数为槽。 -
本质上,它会让 Qt 的元对象编译器(moc)生成额外的代码,以支持运行时的动态连接。
-
只有标记为
slot的函数才能被信号连接调用,且能被元对象系统识别。
slots 的写法方式
public slots:
void foo(); // 公有槽
protected slots:
void bar(); // 保护槽
private slots:
void baz(); // 私有槽
这些和访问权限修饰符一样作用于槽函数。
信号和槽的连接方式
Qt 中信号与槽机制有两种连接方式:老式字符串宏方式 和 现代函数指针方式。
// 宏方式
connect(ui->pushButton, SIGNAL(clicked(bool)), this, SLOT(FoodComing()));
// 函数指针方式
connect(ui->pushButton, &QPushButton::clicked, this, &Widget::FBIComing);
对比:
| 比较项 | 宏方式 (SIGNAL, SLOT) |
函数指针方式 |
|---|---|---|
| 类型检查 | ❌ 无编译期检查 | ✅ 编译期检查 |
| lambda 支持 | ❌ 不支持 | ✅ 支持 |
槽是否必须加 slots |
✅ 是(否则无法注册) | ❌ 否(普通成员函数即可) |
| Qt 版本支持 | Qt 4 和 Qt 5,Qt 6 已不推荐 | Qt 5+(特别推荐),Qt 6 默认 |
示例 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 // 必须宏,启用 Qt 的元对象机制
public:
Widget(QWidget* parent = nullptr);
~Widget();
public slots: // 标识该区域内的函数为槽函数
void FBIComing();
private:
Ui::Widget* ui;
};
#endif // WIDGET_H
widget.cpp
#include "widget.h"
#include "./ui_widget.h"
#include <QMessageBox>
Widget::Widget(QWidget* parent) : QWidget(parent), ui(new Ui::Widget) {
ui->setupUi(this);
// 连接信号和槽
// connect(ui->pushButton, SIGNAL(clicked(bool)), this, SLOT(FoodComing()));
connect(ui->pushButton, &QPushButton::clicked, this, &Widget::FBIComing);
}
Widget::~Widget() {
delete ui;
}
void Widget::FBIComing() {
QMessageBox::information(this, tr("查水表"), tr("FBI!Open the door!"));
}
&QPushButton::clicked 是 C++11 引入的指向成员函数的指针写法,在 Qt 中用于实现类型安全的信号与槽连接,它本质上是一个「指向 QPushButton 的成员函数 clicked」的函数指针。
// 信号的函数指针类型:
void (QPushButton::*clickedSignal)(bool) = &QPushButton::clicked;
这表示:clicked 是 QPushButton 的一个成员函数,返回类型为 void,参数为 bool。
示例 2:自动关联
对于之前的例子,先删除 void FBIComing() 在 widget.h 中的声明,在 widget.cpp 中的定义以及手动关联的代码。然后打开界面文件 widget.ui ,进入图形界面设计模式,右击 “ 敲门 ” 按钮,转到槽 -> 选择信号 -> QAbstractButton -> clicked(),点击确认后会自动生成一个 void Widget::on_pushButton_clicked() 函数声明和定义,函数定义中填写之前的代码。
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:
Widget(QWidget* parent = nullptr);
~Widget();
// 自动生成
private slots:
void on_pushButton_clicked();
private:
Ui::Widget* ui;
};
#endif // WIDGET_H
widget.cpp
#include "widget.h"
#include "./ui_widget.h"
#include <QMessageBox>
Widget::Widget(QWidget* parent) : QWidget(parent), ui(new Ui::Widget) {
ui->setupUi(this);
// 没有手动关联的代码
}
Widget::~Widget() {
delete ui;
}
// 自动生成
void Widget::on_pushButton_clicked() {
// 自己填写
QMessageBox::information(this, tr("查水表"), tr("FBI!Open the door!"));
}
没有手动关联的 connect 函数,信号和槽是如何工作的?
Qt 使用一种固定的命名约定来自动将信号连接到槽函数,命名格式如下:
on_<objectName>_<signalName>()
on_<objectName>_<signalName>([参数列表])
示例中的是:
on_pushButton_clicked()
只要在代码里写出了这个函数(并且有 Q_OBJECT 宏),Qt 的元对象机制会自动帮你连接这个信号。所以就不用手动写 connect 函数。如果不通过 widget.ui 右键点击自动创建槽函数的方式,也可以直接在代码里写,只要严格按照命名规则,也可以实现自动关联的功能。
注意事项
| 限制 | 描述 |
|---|---|
必须启用 Q_OBJECT 宏 |
否则元对象机制无效 |
| objectName 必须准确 | 比如 lineEdit_1、pushButton_OK |
| 不支持 lambda 或重载自动连接 | 只能匹配完整信号名 |
| 需要在构造函数之后才能自动连上 | 自动连接是在构造函数之后进行的 |
| 不推荐用于复杂逻辑或大型项目 | 可读性差、调试困难 |
示例 3:文本同步(使用原有的信号和槽)
使用拖控件的方式创建一个单行文本编辑器(QLineEdit)和一个标签控件(QLabel),然后用垂直布局包裹这两个控件。
widget.cpp
#include "widget.h"
#include "./ui_widget.h"
Widget::Widget(QWidget* parent) : QWidget(parent), ui(new Ui::Widget) {
ui->setupUi(this);
// 函数指针写法中不能加上参数列表 ()
connect(ui->lineEdit, &QLineEdit::textEdited, ui->label, &QLabel::setText);
}
Widget::~Widget() {
delete ui;
}
QLineEdit 自带的信号:
void QLineEdit::textChanged(const QString &text)
QLabel 自带的槽:
void setText(const QString &)
错误写法:
&QLineEdit::textEdited(QString) 错误:这不是合法的 C++ 函数指针写法
-
在函数指针语法中,不能加括号和参数列表,比如
&Class::functionName就足够了; -
参数信息是由编译器通过模板推导自动判断的。
示例 4:一对多关联
打开 widget.ui 向垂直布局里增加一个文本浏览控件(QTextBrowser),然后自定义一个槽函数。
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:
Widget(QWidget* parent = nullptr);
~Widget();
public slots:
// 自定义的槽函数
void printText(const QString& text);
private:
Ui::Widget* ui;
};
#endif // WIDGET_H
widget.cpp
#include "widget.h"
#include "./ui_widget.h"
Widget::Widget(QWidget* parent) : QWidget(parent), ui(new Ui::Widget) {
ui->setupUi(this);
// 接收端是标签控件
connect(ui->lineEdit, &QLineEdit::textEdited, ui->label, &QLabel::setText);
// 接收端是文本浏览控件
connect(ui->lineEdit, &QLineEdit::textEdited, ui->textBrowser, &QTextBrowser::setText);
// 接收端是主窗口的 printText 槽
connect(ui->lineEdit, &QLineEdit::textEdited, this, &Widget::printText);
// 接收端是主窗口内的一个 lambda 表达式,用于打印文本
// 只有在使用 lambda 表达式时才能省略接收者
connect(ui->lineEdit, &QLineEdit::textEdited,
[](const QString& text) { qDebug() << "printText2: " << text; });
}
Widget::~Widget() {
delete ui;
}
void Widget::printText(const QString& text) {
// 打印到调试输出面板
qDebug() << "printText: " << text;
}
示例 5:多对一关联
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:
Widget(QWidget* parent = nullptr);
~Widget();
public slots:
void someoneComing();
private:
Ui::Widget* ui;
};
#endif // WIDGET_H
widget.cpp
#include "widget.h"
#include "./ui_widget.h"
#include <QMessageBox>
Widget::Widget(QWidget* parent) : QWidget(parent), ui(new Ui::Widget) {
ui->setupUi(this);
// 三个按钮都关联到同一个槽函数,省略第三个参数 this
connect(ui->fbiBtn, &QPushButton::clicked, this, &Widget::someoneComing);
connect(ui->ciaBtn, &QPushButton::clicked, this, &Widget::someoneComing);
connect(ui->deaBtn, &QPushButton::clicked, this, &Widget::someoneComing);
}
Widget::~Widget() {
delete ui;
}
void Widget::someoneComing(){
QString name = this->sender()->objectName();
qDebug() << name;
QString msg = "我去!是";
if("fbiBtn" == name){
msg.append("FBI!");
}else if("ciaBtn" == name){
msg.append("CIA!");
}else if("deaBtn" == name){
msg.append("DEA!");
}else{
return;
}
QMessageBox::information(this, tr("有人敲门"), msg);
}
在 Qt 中,当信号触发其关联的槽函数时,槽函数可以通过 sender() 获取发出该信号的对象指针。这个 sender() 函数是继承自 QObject 的一个成员函数,返回类型是 QObject*。
QObject* QObject::sender() const;
-
sender()仅在槽函数是在事件调用链中由信号触发时才有效,否则返回nullptr。 -
sender()只能在同一个线程中被安全调用,跨线程信号槽连接(使用Qt::QueuedConnection)时,结果可能不确定。 -
返回的是
QObject*,如果你想调用派生类函数,需要手动qobject_cast。
if (auto btn = qobject_cast<QPushButton*>(sender())) {
btn->setText("已点击");
}
什么时候 sender() 不靠谱?
-
槽函数是手动调用的(而不是 connect 后自动触发的);
-
槽函数是由不同线程中的信号触发的;
-
使用 lambda 表达式捕获
sender()时,由于作用域不同,也不建议依赖它。
示例 6:解除关联
理论
1. 精确解除某个信号和槽的连接
disconnect(sender, signal, receiver, slot);
对应 connect 的写法是:
connect(ui->button, &QPushButton::clicked, this, &Widget::onButtonClicked);
解除方式:
disconnect(ui->button, &QPushButton::clicked, this, &Widget::onButtonClicked);
2. 只写发送者和信号,断开该信号的所有连接
disconnect(ui->button, &QPushButton::clicked, nullptr, nullptr);
表示断开 ui->button 所有发出的 clicked 信号连接。
3. 只写接收者和槽,断开接收者中对应槽的所有连接
disconnect(nullptr, nullptr, this, &Widget::onButtonClicked);
4. 断开发送者和接收者之间的所有连接
disconnect(ui->button, nullptr, this, nullptr);
5. 对 lambda 表达式的连接,断开比较麻烦
// 连接
auto connection = connect(ui->button, &QPushButton::clicked, [](){
qDebug() << "lambda";
});
// 断开
disconnect(connection);
补充说明
-
所有
disconnect(...)的重载都在QObject类中; -
也可以使用
QObject::disconnect(sender)断开所有与某个对象相关的连接; -
disconnect()返回bool,指示是否成功解除连接。
推荐实践
-
使用
QMetaObject::Connection conn = connect(...)保存连接对象; -
在需要时使用
disconnect(conn)断开。
实践
创建一个 QLineEdit,一个 QLabel,两个 QPushButton,自动创建出两个槽函数。
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:
Widget(QWidget* parent = nullptr);
~Widget();
private slots:
void on_connBtn_clicked();
void on_disconnBtn_clicked();
private:
Ui::Widget* ui;
};
#endif // WIDGET_H
widget.cpp
#include "widget.h"
#include "./ui_widget.h"
Widget::Widget(QWidget* parent) : QWidget(parent), ui(new Ui::Widget) {
ui->setupUi(this);
}
Widget::~Widget() {
delete ui;
}
void Widget::on_connBtn_clicked() {
// 关联
connect(ui->lineEdit, &QLineEdit::textEdited, ui->label, &QLabel::setText);
ui->connBtn->setEnabled(false);
ui->disconnBtn->setEnabled(true);
}
void Widget::on_disconnBtn_clicked() {
// 断开关联
disconnect(ui->lineEdit, &QLineEdit::textEdited, ui->label, &QLabel::setText);
ui->connBtn->setEnabled(true);
ui->disconnBtn->setEnabled(false);
}

浙公网安备 33010602011771号