1.前言
Qt 提供了一种独特的信号与槽机制,用于实现对象之间的通信。这个机制可以解耦对象间的依赖,简化事件处理,特别适用于 GUI 编程。通过信号与槽,Qt 能够轻松处理按钮点击、数据变化等事件,而不需要直接调用函数。
本章将通过一个简单示例,介绍信号与槽的基本概念、发送信号的方式、槽函数的处理方法,以及如何连接信号和槽。
2.基本概念
2.1什么是信号(Signal)
信号是 Qt 中的一种机制,用于通知其他对象某个事件发生了。当某个事件触发时,对象会"发出"信号。信号本身并不做任何操作,它只是一个事件的标识,通常由对象发出,比如按钮点击、文本变化等。
2.2什么是槽(Slot)
槽是用于接收信号并处理信号的响应函数。它可以是任何成员函数、静态函数,或者是一个 Lambda 表达式。槽的主要作用是响应信号并执行相应的逻辑。
2.3信号和槽的作用
信号与槽的主要作用是实现对象之间的解耦。通过信号与槽,发送信号的对象不需要知道接收信号的对象是谁,接收对象也不需要了解信号的发出方。这样,代码更加模块化,易于维护。
2.4信号槽连接
要将信号和槽函数关联,需要使用connect
函数进行连接,基本连接方式使用函数指针连接:
connect(sender, &SenderClass::signalName, receiver, &ReceiverClass::slotName);
2.5与传统回调函数的对比
传统回调函数 | Qt 信号与槽 |
---|---|
函数指针或函数对象,手动绑定 | 统一用 connect(),不需要直接操作函数指针 |
调用方必须知道处理方 | 信号发出者无需知道谁接收,解耦 |
只能一对一绑定 | 支持一对多、多对一绑定 |
销毁时需手动解除绑定,容易出错 | Qt 自动管理连接的生命周期 |
这种机制使得 Qt 更适合开发复杂的 GUI 应用,能够有效管理不同对象间的交互。
3.简单示例:按钮点击响应
新建一个不带ui
文件的Qt项目,使用QWidget
为基本窗口类。
在该项目中,添加一个按钮,当按钮被点击,使得当前窗口关闭。下面是相关代码
arduino
#include "widget.h"
#include<QPushButton>
#include<QHBoxLayout>
Widget::Widget(QWidget *parent)
: QWidget(parent)
{
//设置窗口固定大小
setFixedSize(300,100);
//创建窗口布局
QHBoxLayout* hlay = new QHBoxLayout(this);
//添加按钮
QPushButton* button = new QPushButton("Click me");
//在布局中添加控件
hlay->addWidget(button);
//连接按钮点击信号到窗口关闭函数
//当按钮被点击,窗口关闭
connect(button,&QPushButton::clicked,this,&QWidget::close);
}
Widget::~Widget() {}
运行项目,看到这样的界面:

当点击按钮,窗口关闭程序结束。
4.自定义信号槽
在上面的示例中,用到的是标准信号槽。所谓标准信号槽,信号和槽函数都是Qt框架定义好的。Qt也允许我们自定义信号和槽。
4.1自定义信号
自定义信号步骤简单,通常分为声明 和发送两个阶段。
信号声明
自定义信号需要写在 signals:
关键字下,通常位于类的头文件中。例如:
arduino
// widget.h
#include <QWidget>
class Widget : public QWidget
{
Q_OBJECT
public:
explicit Widget(QWidget *parent = nullptr);
signals:
void mySignal(); // 无参信号
void mySignalWithData(int value); // 带参数的信号
};
注意事项:
- 需要
Q_OBJECT
宏(通常写在类定义的开头,否则信号系统无法工作) - 信号只是声明,不需要实现。
- 信号可以有参数,参数类型要能被 Qt 的元对象系统(MOC)识别。
发射信号
要发射(发送)信号,使用 emit
关键字调用:
scss
emit mySignal(); // 发射无参信号
emit mySignalWithData(42); // 发射带参数信号
emit
是一个标识性关键字,实际就是函数调用,但它让代码更清晰地表达出"这是发信号"的动作。
4.2槽函数的常用写法
槽函数可以有多种写法,不同写法适用于不同场景。下面我们一一讲解。
(1)普通成员函数作为槽函数
最常见的写法是把一个成员函数作为槽函数。 在头文件中声明,在源文件中实现:
arduino
// widget.h
class Widget : public QWidget
{
Q_OBJECT
public:
explicit Widget(QWidget *parent = nullptr);
signals:
void mySignal();
private slots:
void mySlot(); // 普通成员槽函数
};
javascript
// widget.cpp
void Widget::mySlot()
{
qDebug() << "Slot triggered!";
}
说明:
slots:
关键字在 Qt6 可以省略,普通成员函数也能直接作为槽。- 传统上写在
private slots:
或public slots:
区域以示区分。 - qDebug()可以理解为C++
cout
函数,其输出内容后会自动换行,不需要像cout
手动换行。
(2)使用 Lambda 表达式作为槽
在实际开发中,Lambda表达式常用于快速处理简单逻辑,非常方便。
scss
connect(button, &QPushButton::clicked, this, [=](){
qDebug() << "Button clicked! (Lambda)";
});
说明:
[=]
表示按值捕获外部变量。- 适合处理简单、短小的响应逻辑。
- 没有类成员的额外声明,非常轻便。
(3)使用静态函数作为槽
虽然不常见,但静态函数也可以作为槽函数(前提是它能匹配信号的参数列表)。
arduino
class Widget : public QWidget
{
Q_OBJECT
public:
static void staticSlot();
};
void Widget::staticSlot()
{
qDebug() << "Static slot triggered!";
}
连接方式类似:
css
connect(button, &QPushButton::clicked, &Widget::staticSlot);
注意:
- 静态槽无法直接访问对象成员(因为没有
this
指针)。 - 多用于简单全局处理逻辑或工具类。
4.3connect
连接方式
Qt6(以及 Qt5 的现代写法)支持更加安全和灵活的连接方式。这里我们详细讲解常用写法。
(1)基本连接语法(函数指针方式)
这是推荐使用的连接方式,类型安全,编译期检查。
less
connect(senderObject, &SenderClass::signalName,
receiverObject, &ReceiverClass::slotName);
示例:
css
connect(button, &QPushButton::clicked,
this, &Widget::handleButtonClicked);
senderObject
:发出信号的对象signalName
:发送的信号receiverObject
:接收信号的对象slotName
:处理信号的槽函数
(2)连接到 Lambda 表达式
可以直接把信号连接到一个 Lambda,写法非常简洁:
scss
connect(button, &QPushButton::clicked,
this, [=](){
qDebug() << "Button clicked! (handled by Lambda)";
});
- 适合小逻辑处理,不需要单独写槽函数。
(3)信号到信号的连接
Qt 允许把一个信号连接到另一个信号,适用于转发事件。
css
connect(button, &QPushButton::clicked,
this, &Widget::mySignal);
(4)连接时的返回值
connect
会返回一个 QMetaObject::Connection
对象,可以用来管理连接,比如断开连接。
ini
QMetaObject::Connection conn = connect(...);
// 后续可以使用 disconnect(conn) 来取消连接
disconnect(conn);
(5)对象销毁时自动断开
当发送者 或者接收者对象 被销毁时,连接会自动失效。 这意味着你不需要手动 disconnect,可以有效避免悬空指针错误。
css
// 比如窗口关闭时,button和label自动销毁,信号连接也会自动解除。
4.4简单示例
4.4.1 类设计
SendSignal 类
- 定义一个自定义信号,比如
mySignal()
。 - 提供一个成员函数触发(emit)信号。
ReceivedSignal 类
- 定义不同风格的槽函数。
- 包括普通成员函数槽、Lambda槽等。
4.4.2 类创建
在Qt项目文件,鼠标右键点击项目文件,选择【添加新文件】,选择【C/C++】,选择【C++ class】,文件命名为【SendSignal】,基类选择【QObject】,点击【下一步】,点击【完成】

以通样方式创建【ReceivedSignal】类。新添加类结构如下:
arduino
#ifndef SENDSIGNAL_H
#define SENDSIGNAL_H
#include <QObject>
//继承自QObject类
class SendSignal : public QObject
{
//添加Q_OBJECT,才能正常使用qt提供的信号槽机制
Q_OBJECT
public:
explicit SendSignal(QObject *parent = nullptr);
//signals下可以添加自定义信号
signals:
};
#endif // SENDSIGNAL_H
4.4.3 代码示例
SendSignal.h
arduino
#ifndef SENDSIGNAL_H
#define SENDSIGNAL_H
#include <QObject>
//继承自QObject类
//发送者类,负责发送自定义信号
class SendSignal : public QObject
{
//添加Q_OBJECT,才能正常使用qt提供的信号槽机制
Q_OBJECT
public:
explicit SendSignal(QObject *parent = nullptr);
//成员函数,用于发送信号
void triggerSignal();
// 发送带参数的信号
void triggerSignalWithParams(int value);
//signals下可以添加自定义信号
signals:
void mySignal(); //自定义无参信号
// 带参数的信号
void mySignalWithParams(int value);
};
#endif // SENDSIGNAL_H
SendSignal.cpp
arduino
#include "sendsignal.h"
SendSignal::SendSignal(QObject *parent)
: QObject{parent}
{}
void SendSignal::triggerSignal()
{
//使用emit发射自定义信号
emit mySignal(); //无参信号
}
void SendSignal::triggerSignalWithParams(int value)
{
//发射带参数的信号
emit mySignalWithParams(value);
}
ReceivedSignal.h
arduino
#ifndef RECEIVEDSIGNAL_H
#define RECEIVEDSIGNAL_H
#include <QObject>
class ReceivedSignal : public QObject
{
Q_OBJECT
public:
explicit ReceivedSignal(QObject *parent = nullptr);
//普通成员函数作为槽
void normalSlot();
//处理带参数信号的槽
void handleSignalWithParms(int value);
// 静态函数作为槽(比较少见)
static void staticSlot();
//槽函数返回值和参数,和信号保持一致
public slots:
void ProcessingSignals();
};
#endif // RECEIVEDSIGNAL_H
ReceivedSignal.cpp
arduino
#include "receivedsignal.h"
#include<QDebug>
ReceivedSignal::ReceivedSignal(QObject *parent)
: QObject{parent}
{}
void ReceivedSignal::normalSlot()
{
qDebug()<<"普通成员槽函数被调用";
}
void ReceivedSignal::handleSignalWithParms(int value)
{
qDebug() << "带参数信号接收到的值:" << value;
}
void ReceivedSignal::staticSlot()
{
qDebug() << "静态槽函数被调用";
}
测试程序:widget.cpp
scss
#include "widget.h"
#include<sendsignal.h>
#include<receivedsignal.h>
Widget::Widget(QWidget *parent)
: QWidget(parent)
{
SendSignal sender;
ReceivedSignal receiver;
//1.连接到普通成员槽函数
connect(&sender,&SendSignal::mySignal,
&receiver,&ReceivedSignal::normalSlot);
// 2. 连接带参数信号到带参数槽
connect(&sender, &SendSignal::mySignalWithParams,
&receiver,&ReceivedSignal::handleSignalWithParms);
// 3. 连接到 Lambda 表达式
connect(&sender,&SendSignal::mySignal,
[](){
qDebug()<<"Lambda槽函数被调用";
});
// 4. 连接到静态函数(演示用)
connect(&sender,&SendSignal::mySignal,
&ReceivedSignal::staticSlot);
//发送信号
sender.triggerSignal();
sender.triggerSignalWithParams(42); // 传递参数42
}
Widget::~Widget() {}
运行效果说明
程序运行后在【应用程序输出】会看到以下内容输出到控制台:
makefile
普通成员槽函数被调用
Lambda槽函数被调用
静态槽函数被调用
带参数信号接收到的值: 42
解释:
无参信号 mySignal
触发时,会依次调用普通槽、Lambda槽、静态槽。
带参数信号 mySignalWithParams
触发时,handleSignalWithParams
槽函数根据传递的参数 42
进行响应。
5.总结
Qt 信号与槽机制是对象间通信的核心方式,能有效解耦代码。本章内容归纳如下:
- 信号(Signal) 用于发出事件通知,槽(Slot) 用于响应处理。
- 建议优先使用 新版 connect() 写法(函数指针或成员指针+lambda),避免旧版字符串匹配导致的运行时错误。
- 槽函数写法推荐 :
- 简单响应用 Lambda 表达式,简洁直观。
- 复杂处理用 普通成员函数,逻辑清晰。
- 自定义信号与槽 时,要注意:
- 自定义信号在
signals:
区域声明,不需要实现。 - 发送信号使用
emit
。 - 信号可以携带参数,便于根据不同参数调用不同槽函数。
- 自定义信号在
- 连接检测 :connect 返回
QMetaObject::Connection
,可用于判断连接是否成功。
实战建议:在实际开发中,优先使用 lambda 作为槽,保持代码简洁;信号与槽的连接尽量明确绑定,避免动态字符串连接。掌握自定义信号与带参信号的使用,为项目开发中灵活处理复杂交互打下基础。
💡 小提示
- 简单响应用 Lambda,快速直观。
- 复杂逻辑用 成员函数槽,结构清晰。
- 信号连接时,优先使用 新版 connect(函数指针/Lambda),避免字符串方式。
- 自定义信号时注意参数设计,让槽函数更灵活高效。
- 保持信号与槽清晰明确,有助于后期维护和扩展。