文章目录
-
- [1. 信号和槽概述](#1. 信号和槽概述)
- [2. 信号和槽的使用](#2. 信号和槽的使用)
-
- 连接信号和槽
-
- 一、前提
- 二、connect函数
-
- [1. 函数原型(Qt4)](#1. 函数原型(Qt4))
- [2. 函数原型(Qt5)](#2. 函数原型(Qt5))
- 3.(点击按钮关闭窗口)
- 三、可视化生成
-
- [1. 核心原理](#1. 核心原理)
- [2. 点击按钮关闭窗口](#2. 点击按钮关闭窗口)
- [3. 自定义信号和槽](#3. 自定义信号和槽)
-
- 基本语法
- 带参数的信号和槽
-
- 一、槽函数重载的情况
- 二、信号和槽参数个数、类型关系
-
- [1. 参数完全相等](#1. 参数完全相等)
- [2. 信号参数多于槽参数](#2. 信号参数多于槽参数)
- [3. 信号参数少于槽参数](#3. 信号参数少于槽参数)
- [4. 信号和槽参数类型不匹配](#4. 信号和槽参数类型不匹配)
- 4.信号与槽的连接方式
-
- [1. 一对一:一个信号连接一个槽](#1. 一对一:一个信号连接一个槽)
- [2. 一对多:一个信号连接多个槽](#2. 一对多:一个信号连接多个槽)
- [3. 多对一:多个信号连接同一个槽](#3. 多对一:多个信号连接同一个槽)
- [5. 信号和槽的其他说明](#5. 信号和槽的其他说明)
-
- [5.1 信号与槽的断开](#5.1 信号与槽的断开)
- [5.2 使用Lambda表达式定义槽函数](#5.2 使用Lambda表达式定义槽函数)
- [5.4 信号与槽的优缺点](#5.4 信号与槽的优缺点)
1. 信号和槽概述
-
事件:
就是用户和控件的互动动作,比如:点击按钮、关闭窗口、鼠标移动/按下、键盘输入文字、窗口刷新等,这些都是触发后续反应的"引子"。
-
信号:
事件发生后,Qt控件会主动发信号,本质是把事件变成了可被识别的"通知",而且是以函数的形式呈现。
- 实例:点击按钮时,按钮会发出 "clicked(被点击)" 信号;关闭窗口时,窗口会发出 "close(要关闭)" 信号;鼠标移到输入框上,输入框会发出 ** "mousePress(被点击)" ** 信号。
- 注意:信号是"谁触发事件谁发",比如按钮被点就按钮发信号,窗口被关就窗口发信号。
-
槽:
规定收到信号后要做的事,本质是个普通函数,但能和信号"绑定"。
- 实例:窗口收到按钮的 "clicked" 信号后,执行 "close(关闭自己)" 的槽函数,这就是"点击按钮关窗口";输入框收到自己的 "mousePress" 信号后,执行"显示闪烁光标"的槽函数,这就是"点输入框就准备输文字"。
- 槽的特点:和普通函数一样,能随便放在类的公有/私有区域,能传参数、能重载,也能直接调用(
但不能有默认参数),唯一特别的是能跟信号绑定。
-
信号与槽:
按钮和窗口本来是没关系的,点击按钮不会影响窗口,但信号与槽把两者连起来:
- 没连线:点按钮只是按钮自己有反应,窗口没动静;
- 连了线:按钮发"clicked"信号,窗口的"close"槽接收到,就会自动执行关闭操作,实现"点按钮关窗口"这种联动效果。
说明:
-
信号和槽机制底层是通过函数间的相互调用实现的。每个信号都可以用函数来表示,称为信号函数;每个槽也可以用函数表示,称为槽函数。例如:"按钮被按下"这个信号可以用clicked()函数表示,"窗口关闭"这个槽可以用close()函数表示,假如使用信号和槽机制实现:"点击按钮会关闭窗口"的功能,其实就是clicked()函数调用close()函数的效果。
-
信号函数和槽函数通常位于某个类中,和普通的成员函数相比,它们的特别之处在于:
- 信号函数用
signals关键字修饰,槽函数用public slots、protected slots或者private slots修饰。signals和slots是Qt在C++的基础上扩展的关键字,专门用来指明信号函数和槽函数; - 信号函数只需要声明,不需要定义(实现),而槽函数需要定义(实现)。
2. 信号和槽的使用
连接信号和槽
一、前提
connect是QObject类的静态成员函数(Qt中大部分控件/类都直接/间接继承QObject),核心作用是 将"信号发送者"的信号,与"接收者"的槽函数绑定,实现"信号触发→槽函数自动执行"的联动。
二、connect函数
1. 函数原型(Qt4)
cpp
connect(
const QObject *sender, // 信号发送者(控件/对象指针)
const char *signal, // 发送的信号(Qt4用SIGNAL宏,Qt5用函数指针)
const QObject *receiver, // 信号接收者(控件/对象指针)
const char *method, // 响应的槽函数(Qt4用SLOT宏,Qt5用函数指针)
Qt::ConnectionType type = Qt::AutoConnection // 关联方式,默认自动
);
2. 函数原型(Qt5)
cpp
static QMetaObject::Connection connect(const QObject *sender, const char *signal,
const QObject *receiver, const char *member, Qt::ConnectionType = Qt::AutoConnection);
//Qt4还需要宏进行强转
static inline QMetaObject::Connection connect(const typename QtPrivate::FunctionPointer<Func1>::Object *sender, Func1 signal,
const typename QtPrivate::FunctionPointer<Func2>::Object *receiver, Func2 slot, Qt::ConnectionType type = Qt::AutoConnection)
//Qt5更安全的写法,无需SIGNAL/SLOT宏,直接用&类名::信号名/&类名::槽函数名,语法更简洁、编译时能检查错误:
3.(点击按钮关闭窗口)
cpp
// 1. 创建发送者:按钮对象(信号发送者)
QPushButton *btn = new QPushButton("关闭窗口", this);
// 2. 手动关联信号和槽
connect(
btn, // 发送者:按钮
&QPushButton::clicked,// 信号:按钮的"被点击"信号
this, // 接收者:当前窗口(Widget)
&QWidget::close // 槽函数:窗口的"关闭自己"函数
);
- 效果:点击
btn按钮,触发clicked信号,窗口接收信号后执行close槽函数,实现窗口关闭。
三、可视化生成
Qt Creator提供可视化操作,能自动生成槽函数和关联关系,适合快速开发,原文详细步骤如下:
1. 核心原理
Qt Creator会根据"槽函数命名规则"自动关联信号和槽,无需手动写connect------槽函数命名必须符合 on_对象名_信号名 规则:
on:固定前缀;对象名:控件的objectName属性(如按钮的objectName是pushButton);信号名:控件的信号(如clicked)。
2. 点击按钮关闭窗口
新建带UI文件的项目(确保生成widget.ui),打开widget.ui,拖入一个按钮控件,可修改按钮的objectName(默认是pushButton)。

- 选中按钮 → 鼠标右键 → 选择"转到槽..." → 选择要关联的信号(如
clicked())→ 确定; - Qt Creator会自动做两件事:
-
在
widget.h中添加槽函数声明(符合命名规则):cppprivate slots: void on_pushButton_clicked(); // on_对象名_信号名 -
在
widget.cpp中生成槽函数空实现:cppvoid Widget::on_pushButton_clicked() { // 后续添加功能代码 }
-
添加槽函数功能
在自动生成的槽函数中写逻辑(如关闭窗口):
cpp
void Widget::on_pushButton_clicked()
{
this->close(); // 窗口关闭功能
}
- 效果:无需手动写
connect,Qt会自动关联"按钮的clicked信号"和"on_pushButton_clicked槽函数",点击按钮直接触发窗口关闭。 - Qt 中QMetaObject类的静态函数
connectSlotsByName自动匹配并连接信号与槽,如果找不到对应的槽函数,则会报错!
3. 自定义信号和槽
基本语法
在Qt中,允许自定义信号的发送方以及接收方,即可以自定义信号函数和槽函数。但是对于自定义的信号函数和槽函数有一定的书写规范。
1、自定义信号函数书写规范
- 自定义信号函数必须写到"signals"下;
- 返回值为
void,只需要声明,不需要实现; - 可以有参数,也可以发生重载;
2、自定义槽函数书写规范
- 早期的Qt版本要求槽函数必须写到
"public slots"下,但是现在高级版本的Qt允许写到类的"public"作用域中或者全局下; - 返回值为
void,需要声明,也需要实现; - 可以有参数,可以发生重载;
3、发送信号
使用"emit"关键字发送信号。"emit"是一个空的宏。"emit"其实是可选的,没有什么含义,只是为了提醒开发人员。
结合前面已经创建的button,实例如下:
cpp
//widget.h
#ifndef WIDGET_H
#define WIDGET_H
#include <QWidget>
#include "QPushButton"
QT_BEGIN_NAMESPACE
namespace Ui { class Widget; }
QT_END_NAMESPACE
class Widget : public QWidget
{
Q_OBJECT
public:
Widget(QWidget *parent = nullptr);
~Widget();
QPushButton *mybutton;
private slots:
void on_pushButton_clicked();
public slots:
void handlerclick();
signals:
void mysignal();
private:
Ui::Widget *ui;
};
#endif // WIDGET_H
//widget.cpp
#include "widget.h"
#include "ui_widget.h"
#include "QLineEdit"
Widget::Widget(QWidget *parent)
: QWidget(parent)
, ui(new Ui::Widget)
{
ui->setupUi(this);
mybutton = new QPushButton("Helloworld",this);
mybutton->move(200,200);
//connect(mybutton,&QPushButton::clicked,this,&Widget::handlerclick);
connect(this,&Widget::mysignal,this,&Widget::handlerclick);
}
void Widget::handlerclick()
{
if(mybutton->text()=="Helloworld")
{
mybutton->setText("helloqt");
}
else
{
mybutton->setText("hellolinux");
}
}
Widget::~Widget()
{
delete ui;
}
void Widget::on_pushButton_clicked()
{
//发出自定义的信号,emit代表发送信号,emit可以省略
emit mysignal();
//this->close();
}

带参数的信号和槽
Qt的信号和槽也支持带有参数,同时也可以支持重载,信号函数的参数列表要和对应连接的槽函数参数列表一致。
一、槽函数重载的情况
有两个同名槽:
cpp
void handlerclick();
void handlerclick(const QString& str);
如果用旧语法:
cpp
connect(this, SIGNAL(mysignal(QString,int)),
this, SLOT(handlerclick(QString)));
字符串里写死了签名,不会有二义性。
用新语法(函数指针语法)时,如果槽有重载,就必须消除重载歧义,常见写法有两种:
cpp
// 1. 老版本指明函数指针
void (Widget::* sigptr)(const QString&,int)=&Widget::mysignal;
void (Widget::* handptr)(const QString&)=&Widget::handlerclick;
// 2. 显示类型定义
auto sigptr = static_cast<void (Widget::*)(const QString&,int)>(&Widget::mysignal);
auto handptr = static_cast<void (Widget::*)(const QString&)>(&Widget::handlerclick);
二、信号和槽参数个数、类型关系
Qt 官方规则:槽的签名必须与信号兼容,具体来说是:
槽可以比信号少参数,但不能比信号多参数;从前往后对应的参数类型必须相同或可隐式转换。 ([Qt Documentation][1])
1. 参数完全相等
形式:
cpp
signals:
void mysignal(const QString& str, int signo);
public slots:
void handlerclick(const QString& str, int signo);
连接:
cpp
connect(this, &Widget::mysignal,
this, &Widget::handlerclick);
emit mysignal("触发信号", 1);
特点:
- 参数个数、类型一一对应。
- 最直观,也最不容易出错。
- 一般建议业务里优先采用这种方式。
2. 信号参数多于槽参数
这是你当前代码的思路:
cpp
signals:
void mysignal(const QString& str, int signo);
public slots:
void handlerclick(const QString& str);
连接(注意要指定重载):
cpp
connect(this, &Widget::mysignal,
this, QOverload<const QString&>::of(&Widget::handlerclick));
发射信号时仍然要把所有参数补全:
cpp
emit mysignal("触发信号", 1); // int 参数会被忽略掉
规则说明:
-
Qt 允许槽比信号少参数,多余的信号参数会被默默丢弃。([Qt Documentation][1])
-
但你在
emit的时候,必须满足信号自己的完整签名,少传一个int,直接编译不过:cppemit mysignal("触发信号"); // 少了一个 int,编译错误
适用场景:
- 信号携带了比较详细的信息,但某些槽只关心前面几项。
3. 信号参数少于槽参数
例如:
cpp
signals:
void mysignal(const QString& str); // 1 个参数
public slots:
void handlerclick(const QString& str, int signo); // 2 个参数
直接用函数指针语法连接会编译失败 ,因为 Qt 无法凭空给槽补出 int signo 这个值。
几种处理方式:
-
槽使用默认参数 + 旧宏语法(只对宏语法有效)([Qt Documentation][3])
cpppublic slots: void handlerclick(const QString& str, int signo = 0); connect(this, SIGNAL(mysignal(QString)), this, SLOT(handlerclick(QString))); // signo 用默认值 0 -
用 lambda 适配(推荐写法,配合新语法)
cppconnect(this, &Widget::mysignal, this, [this](const QString& s){ handlerclick(s, 0); // 手动补上第二个参数 }); -
直接修改槽的签名,让槽参数不多于信号,这是最干净的写法。
4. 信号和槽参数类型不匹配
比如:
cpp
signals:
void mysignal(int value);
public slots:
void handlerclick(const QString& s);
这种情况,类型完全对不上,既不能直接连接,也不能靠"多/少参数"来弥补,会报不兼容错误。
通常做法:
- 改槽签名,使类型匹配;
- 或者同样用 lambda 做一个适配层,在 lambda 里完成 int → QString 的转换。
总结成几句话:
- 槽的参数个数必须小于等于信号的参数个数;不能比信号多。
- 从前往后对应的参数类型必须相同或可隐式转换,否则连接失败。
- 信号参数多于槽参数时,多出来的参数会被忽略;但
emit时必须把信号声明中的参数全部传齐。 - 信号参数少于槽参数时,函数指针语法不能直接连接;可以用旧宏语法配合默认参数,或者用 lambda 做适配。
- 槽/信号重载时,用新语法要通过
static_cast或QOverload消除重载歧义。 const QString&不是强制要求,只是推荐用法,用于避免拷贝并保证不修改参数。
4.信号与槽的连接方式
1. 一对一:一个信号连接一个槽
cpp
Widget::Widget(QWidget *parent)
: QWidget(parent)
, ui(new Ui::Widget)
{
ui->setupUi(this);
// 1. 用 static_cast 明确指定信号和槽的函数指针类型
auto sigptr = static_cast<void (Widget::*)(const QString&, int)>(
&Widget::mysignal);
auto handptr = static_cast<void (Widget::*)(const QString&)>(
&Widget::handlerclick);
// 2. 创建一个按钮
mybutton = new QPushButton("Helloworld", this);
mybutton->move(200, 200);
// 3. 连接信号和槽:mysignal(const QString&, int) -> handlerclick(const QString&)
connect(this, sigptr, this, handptr);
}
这就是最标准的一对一:
mysignal(const QString&, int)⟶handlerclick(const QString&)
还可以是信号对信号:
cpp
Widget::Widget(QWidget *parent)
: QWidget(parent)
, ui(new Ui::Widget)
{
ui->setupUi(this);
// 1. 精确指定两个信号的函数指针类型
auto sigptr1 = static_cast<void (Widget::*)(const QString&, int)>(
&Widget::mysignal);
auto sigptr2 = static_cast<void (Widget::*)(const QString&)>(
&Widget::forwardSignal);
// 2. 槽函数指针(有 QString 参数的那个重载)
auto handptr = static_cast<void (Widget::*)(const QString&)>(
&Widget::handlerclick);
// 3. 创建按钮
mybutton = new QPushButton("Helloworld", this);
mybutton->move(200, 200);
connect(this, sigptr1, this, sigptr2);
// 再把 forwardSignal 连到真正的槽上
connect(this, sigptr2, this, handptr);
}
mysignal(const QString&, int) -> forwardSignal(const QString&) -> handlerclick
2. 一对多:一个信号连接多个槽
同一个 mysignal,可以同时连到多个槽,只要参数兼容即可。
假设你再加两个槽(示意代码,实际你可以自己实现):
cpp
public slots:
void handlerclick(const QString&); // 已有
void logMessage(const QString&); // 打印日志
void showMessageBox(const QString&); // 弹提示框
在构造函数里可以这样连:
cpp
// 原来的
connect(this, sigptr, this, handptr); // mysignal -> handlerclick(const QString&)
// 直接用函数指针也行(无重载时可以省略 static_cast,如果有重载仍然需要)
connect(this, sigptr, this, &Widget::logMessage);
connect(this, sigptr, this, &Widget::showMessageBox);
当你 emit mysignal("触发信号", 0); 时:
handlerclick("触发信号");被调用(改变按钮文字)logMessage("触发信号");被调用(比如 qDebug 打印)showMessageBox("触发信号");被调用(例如弹出 QMessageBox)
- 一个信号可以连接任意多个槽;
- 发射一次信号,会按连接顺序依次调用所有槽;
- 这就是一对多。
3. 多对一:多个信号连接同一个槽
我们可以让多个信号都去触发同一个槽 handlerclick() 或 handlerclick(const QString&)。
例如:
- 自定义信号
mysignal(const QString&, int) - 按钮点击信号
QPushButton::clicked()
都去调用无参版 handlerclick():
cpp
// 1)mysignal -> handlerclick()
auto sigptr_noarg = static_cast<void (Widget::*)(const QString&, int)>(&Widget::mysignal);
connect(this, sigptr_noarg, this, &Widget::handlerclick); // 注意这里槽是无参版本
// 2)按钮的 clicked() -> handlerclick()
connect(mybutton, &QPushButton::clicked, this, &Widget::handlerclick);
当手动 emit mysignal("触发信号", 0);或者点击 mybutton都会触发同一个槽:
cpp
void Widget::handlerclick()
{
// 在这里做统一处理,比如:
qDebug() << "有东西触发了 handlerclick() 槽";
}
5. 信号和槽的其他说明
5.1 信号与槽的断开
在 Qt 中,disconnect() 是一个非常重要的函数,用于断开信号与槽之间的连接。信号和槽是 Qt 的核心机制之一,用于实现对象之间的通信。当一个对象发出信号时,Qt 会触发一个或多个连接到该信号的槽。disconnect() 函数允许用户断开这种连接,从而使得信号不再触发槽。
基本语法
cpp
bool QObject::disconnect(const QObject *sender, const char *signal, const QObject *receiver, const char *method);
sender:发出信号的对象。signal:发出的信号。receiver:接收信号并处理的对象。method:接收到信号后执行的方法。
示例
假设我们有一个按钮,点击按钮时触发一个信号并调用一个槽。我们可以在某个时刻断开这个连接,使得按钮的点击不再触发槽。
连接信号和槽
cpp
QPushButton *button = new QPushButton("Click me", this);
connect(button, &QPushButton::clicked, this, &MyClass::onButtonClicked);
断开连接
cpp
disconnect(button, &QPushButton::clicked, this, &MyClass::onButtonClicked);
这会断开 button 的 clicked 信号与 MyClass 中的 onButtonClicked 槽之间的连接。
作或其他条件动态改变信号槽行为时非常有用。
5.2 使用Lambda表达式定义槽函数
-
Qt5信号与槽的灵活性优化
Qt5在Qt4基础上提升了信号与槽的灵活性,允许将任意函数作为槽函数;若想简化槽函数编写(比如不想定义函数名),可通过Lambda表达式实现。
-
Lambda表达式的基础属性
Lambda表达式是C++11新增特性,用于定义并创建匿名函数对象,核心作用是简化编程工作。
-
Lambda表达式的语法格式
cpp[capture] (params) opt -> ret { Function body; // 函数体 };各占位符的含义:
capture:捕获列表(用于引入外部变量)params:参数表(接收信号传递的参数)opt:函数选项(如mutable等)ret:返回值类型(可省略,编译器会自动推导)
Lambda捕获列表
| 符号 | 说明 |
|---|---|
[] |
Lambda无法访问外部函数体的任何局部变量 |
[a] |
以值传递 方式访问外部变量a(函数内是副本) |
[&b] |
以引用传递 方式访问外部变量b(函数内是原变量) |
[=] |
外部所有局部变量都以值传递方式使用(Qt中最常用的安全形式) |
[&] |
外部所有局部变量都以引用传递方式使用(需注意变量生命周期) |
[=, &foo] |
foo用引用传递,其余变量用值传递 |
[&, foo] |
foo用值传递,其余变量用引用传递 |
[this] |
可访问类的成员函数/变量(=/&形式会默认引入this) |
演示如下:
cpp
Widget::Widget(QWidget *parent)
: QWidget(parent)
, ui(new Ui::Widget)
{
ui->setupUi(this);
mybutton = new QPushButton("Helloworld",this);
mybutton->move(200,200);
//connect(mybutton,&QPushButton::clicked,this,&Widget::handlerclick);
//connect(this,sigptr,this,handptr);
connect(this,&Widget::mysignal,this,[=](){
qDebug()<<"按钮即将要关闭啦!";
mybutton->close();
});
}
void Widget::on_pushButton_clicked()
{
//发出自定义的信号,emit代表发送信号,emit可以省略
emit mysignal("触发信号",0);
}
5.4 信号与槽的优缺点
-
优点: 松散耦合
信号发送者不需要知道发出的信号被哪个对象的槽函数接收,槽函数也不需要知道哪些信号关联了自己,Qt的信号槽机制保证了信号与槽函数的调用。支持信号槽机制的类或者父类必须继承于QObject类。
-
缺点: 效率较低
与回调函数相比,信号和槽稍微慢一些,因为它们提供了更高的灵活性,尽管在实际应用程序中差别不大。