Qt信号与槽
Qt信号与槽学习笔记
核心:信号槽是Qt实现观察者模式(发布‑订阅)的核心机制,依靠Moc元对象编译器实现。
1. 信号和槽概述
1.1 信号的本质
信号本质就是事件:按钮点击、鼠标移动、键盘输入、窗口刷新等。
- 用户操作控件,控件检测到事件,Qt框架就会发出对应信号。
- 信号是以函数形式呈现;事件产生,框架调用信号函数,通知使用者。
- 信号的发出者必须是实例化后的QObject对象。
通俗比喻:女朋友肚子饿了,发出“饿了”这个信号。
1.2 槽的本质
槽函数是专门用来处理信号的函数,可以当作普通成员函数调用。
- 信号发生,绑定的槽函数自动执行。
- 槽函数所属对象,也必须是实例化的QObject派生对象。
通俗比喻:我收到“饿了”信号,执行“带她吃饭”这个槽处理动作。
1.3 信号槽的关系
信号与槽本身互相独立,需要使用 connect() 函数建立关联,如同喜鹊搭桥。
connect函数原型(Qt5函数指针版本)
QMetaObject::Connection QObject::connect(
const QObject *sender, // 发出信号的对象
PointerToMemberFunction signal,// 信号函数地址(函数指针)
const QObject *receiver, // 接收信号的对象
PointerToMemberFunction method,// 槽函数地址
Qt::ConnectionType type = Qt::AutoConnection
);
- sender、receiver都必须实例化,否则连接无效。
- connect只是注册回调关系,不是立刻执行;只有信号触发,槽才执行。
2. 标准信号槽使用
Qt内置控件自带大量标准信号、标准槽。
- 标准信号:控件检测用户事件之后,Qt内部自动发出。
- 标准槽:Qt类内置,可用来响应信号。
查找信号槽:Qt帮助文档,输入类名,查看
signals部分;很多信号继承自父类,例如QPushButton信号来自父类QAbstractButton。
示例:按钮点击触发窗口关闭
connect(ui->closeBtn,&QPushButton::clicked,this,&MainWindow::close);
3. 自定义信号槽使用
当Qt自带信号无法满足业务,就自己定义信号和槽,依然使用
connect绑定。
使用自定义信号槽前置条件
- 类必须直接/间接继承QObject
- 头文件内部添加宏:
Q_OBJECT
⚠添加Q_OBJECT之后,CMake/qmake需要重新构建,否则编译报错。
class GirlFriend : public QObject
{
Q_OBJECT //❗必须写
public:
explicit GirlFriend(QObject *parent = nullptr);
// 自定义信号
signals:
void hungry();
void hungry(QString foodName); //信号支持重载
};
3.1 自定义信号规则
- 信号是类的成员函数,放在
signals:下 - 返回值必须是
void - 只写声明,不需要手写函数实现,由Moc自动生成
- 支持重载,可以带参数;参数用于传递数据给槽函数
- 发射信号:调用信号函数;习惯加
emit关键字,emit是空宏,只起提示作用
m_girl->hungry();
emit m_girl->hungry("意大利面"); // emit可省略
3.2 自定义槽函数规则
槽本质就是普通函数,Qt5以后slots:关键字可以省略。
- 返回值一般
void - 支持重载,可以有参数
- 参数匹配规则:
- 槽参数个数、类型,尽量和信号一一对应
- 信号参数 ≥ 槽参数,多余信号参数会被忽略
- 槽不能比信号参数更多
- 槽可以是:类成员函数、全局函数、静态函数、Lambda表达式
class Me : public QObject
{
Q_OBJECT
public:
explicit Me(QObject *parent = nullptr);
public slots:
void eat();
void eat(QString msg);
};
me.cpp
#include "me.h"
#include <QDebug>
Me::Me(QObject *parent)
: QObject{parent}
{}
void Me::eat()
{
qDebug()<<"我带你去吃麻辣烫...";
}
void Me::eat(QString msg)
{
qDebug()<<"我带你去吃:"<<msg;
}
⚠信号重载坑:同一个信号名多个版本,直接写
&类::函数名编译器分不清,需要qOverload指定版本。
4. 信号槽拓展
4.1 使用拓展特性
- 一个信号绑定多个槽函数
- 写多个connect;执行顺序不保证和connect顺序一致。
- 接收者可以是不同对象。
- 一个槽绑定多个信号
- 多个不同信号,触发同一个处理逻辑,写多条connect。
- 信号可以连接信号
- 接收方不写业务处理,直接在槽/lamdba内部发射新信号,做信号转发。
//信号连接信号示例
connect(ui->hungry,&QPushButton::clicked,m_girl,&GirlFriend::hungry);
- 断开连接:
disconnect(),解除信号‑槽绑定。
4.2 Lambda表达式(Qt最常用)
Lambda就是C++11匿名函数,非常适合写简单槽逻辑,不需要额外写成员函数。
语法格式:
[capture](params) opt -> ret { body; };
- 捕获列表 capture(重点)
|写法|含义|
|----|----|
|[]|不捕获任何变量|
|[&]|全部外部变量,按引用捕获(可修改原变量)|
|[=]|全部外部变量,值拷贝捕获,lambda内部只读副本|
|[=,&foo]|全部值拷贝,唯独foo按引用捕获|
|[bar]|只值拷贝bar这一个变量|
|[&bar]|只引用捕获bar|
|[this]|捕获类this指针,可以访问类成员|
mutable选项:值拷贝模式下,允许修改lambda内部的副本,不会修改外部原始变量。
- params:参数列表;opt可选(mutable、异常);ret返回值可省略自动推导;body函数体。
示例,connect中使用Lambda:
connect(ui->hungry,&QPushButton::clicked,this,[=](){
m_girl->hungry();
m_girl->hungry("意大利面");
});
4.3 qOverload处理重载信号
当信号存在重载,编译器无法自动识别取哪个版本的函数指针,使用qOverload<参数类型列表>(&类名::信号名)。
// 无参版本 void hungry()
connect(m_girl, qOverload<>(&GirlFriend::hungry), this, [=](){
qDebug() << "收到无参hungry信号!";
});
// QString参数版本 void hungry(QString)
connect(m_girl, qOverload<QString>(&GirlFriend::hungry), this, [=](QString msg){
qDebug() << "收到带参hungry:" << msg;
});
底层等价于手写冗长的static_cast函数指针强制转换;Qt封装简化写法。
完整MainWindow示例代码
mainwindow.h
#ifndef MAINWINDOW_H
#define MAINWINDOW_H
#include <QMainWindow>
QT_BEGIN_NAMESPACE
namespace Ui { class MainWindow; }
QT_END_NAMESPACE
class GirlFriend;
class Me;
class MainWindow : public QMainWindow
{
Q_OBJECT
public:
MainWindow(QWidget *parent = nullptr);
~MainWindow();
private slots:
void hungrySlot();
void eatSlot();
private:
Ui::MainWindow *ui;
GirlFriend *m_girl;
Me *m_me;
};
#endif // MAINWINDOW_H
mainwindow.cpp
#include "mainwindow.h"
#include "./ui_mainwindow.h"
#include "GirlFriend.h"
#include "me.h"
#include <QDebug>
MainWindow::MainWindow(QWidget *parent)
: QMainWindow(parent)
, ui(new Ui::MainWindow)
{
ui->setupUi(this);
m_me = new Me;
m_girl = new GirlFriend;
connect(ui->hungry,&QPushButton::clicked,this,[=](){
// 发射自定义信号
m_girl->hungry();
m_girl->hungry("意大利面");
});
// 接收无参 hungry()
connect(m_girl, qOverload<>(&GirlFriend::hungry), this, [=](){
qDebug() << "收到无参hungry信号!";
});
// 接收带参 hungry(QString)
connect(m_girl, qOverload<QString>(&GirlFriend::hungry), this, [=](QString msg){
qDebug() << "收到带参hungry:" << msg;
});
connect(ui->closeBtn,&QPushButton::clicked,this,&MainWindow::close);
}
MainWindow::~MainWindow()
{
delete ui;
}
void MainWindow::hungrySlot()
{
// 发射自定义信号
m_girl->hungry();
}
void MainWindow::eatSlot()
{
qDebug()<<"我带你去吃海鲜...";
}
GirlFriend.h
#ifndef GIRLFRIEND_H
#define GIRLFRIEND_H
#include <QObject>
class GirlFriend : public QObject
{
Q_OBJECT
public:
explicit GirlFriend(QObject *parent = nullptr);
signals:
void hungry();
void hungry(QString foodName);
};
#endif // GIRLFRIEND_H
GirlFriend.cpp
#include "GirlFriend.h"
GirlFriend::GirlFriend(QObject *parent)
: QObject{parent}
{}
me.h
#ifndef ME_H
#define ME_H
#include <QObject>
class Me : public QObject
{
Q_OBJECT
public:
explicit Me(QObject *parent = nullptr);
public slots:
void eat();
void eat(QString msg);
};
#endif // ME_H
me.cpp
#include "me.h"
#include <QDebug>
Me::Me(QObject *parent)
: QObject{parent}
{}
void Me::eat()
{
qDebug()<<"我带你去吃麻辣烫...";
}
void Me::eat(QString msg)
{
qDebug()<<"我带你去吃:"<<msg;
}
面试高频总结
- 使用自定义信号槽两大硬性条件:继承
QObject、头文件写Q_OBJECT。 - 信号:只声明,不许手写实现,Moc工具自动生成;返回值只能void。
- emit是空宏,只做提示,可省略。
- 信号重载:必须用
qOverload挑选版本,否则编译报错。 - 槽参数:槽的参数数量不能多于信号;多余信号参数自动丢弃。
- Lambda捕获:
[=]值拷贝只读副本,[&]引用捕获修改外部原始变量,[this]访问类成员。 - connect只是注册关系;发射信号才触发槽;只发射信号,没有connect绑定,不会执行任何逻辑。
浙公网安备 33010602011771号