【Qt初识】信号槽(三):机制意义、断开连接与 Lambda 表达式


大家好,我是Halcyon.平安

欢迎文末添加好友交流,共同进步!

  • 目录
    • 一、信号槽的意义
      • [1.1 为什么要有信号槽](#1.1 为什么要有信号槽)
      • [1.2 松散耦合的体现](#1.2 松散耦合的体现)
    • 二、信号和槽断开连接
      • [2.1 为什么要断开](#2.1 为什么要断开)
      • [2.2 disconnect 的使用](#2.2 disconnect 的使用)
    • [三、lambda 表达式定义槽函数](#三、lambda 表达式定义槽函数)
      • [3.1 传统写法的痛点](#3.1 传统写法的痛点)
      • [3.2 lambda 表达式回顾](#3.2 lambda 表达式回顾)
      • [3.3 lambda 充当槽函数](#3.3 lambda 充当槽函数)
      • [3.4 捕获列表的妙用](#3.4 捕获列表的妙用)
    • 四、总结

目录

上一章节我们讲完了自定义信号函数和槽函数,并学会了带参信号槽的灵活使用,本章节我们继续深入信号槽,来聊聊它的意义、如何断开连接,以及一种更简洁的定义槽函数的方式!

一、信号槽的意义

1.1 为什么要有信号槽

我们前面几篇文章其实已经一直在使用信号和槽了,但是仔细想想,Qt 为什么要专门搞出这么一套机制呢?我们用普通 C++ 的函数调用不也能实现功能吗?

为了搞清楚这个问题,我们得先看看传统 C++ 函数调用存在的问题:

  • 强耦合:A 想调用 B 的方法,就必须直接持有 B 的对象,A 和 B 是死死绑在一起的,谁也离不开谁
  • 难以应对变化:如果哪天 B 的方法名变了,或者干脆换成了 C,那 A 的代码也得跟着改,牵一发动全身

但是在实际开发中,我们往往希望控件之间是相互独立、互不依赖的!比如一个按钮,它根本不需要知道点击它之后会发生什么,它只需要在被点击的时候"喊一嗓子"(发信号),至于谁来响应、怎么响应,那是别人的事!

信号槽机制恰恰就是为了解决这个问题而出现的!它做到了:

  • 解耦 :信号发送方和接收方互不依赖,按钮只管发信号,槽函数只管响应,两者通过 connect 关联,互相不需要直接持有
  • 灵活:一个信号可以连接多个槽(一对多),一个槽也可以接收多个信号(多对一),随时关联随时断开

1.2 松散耦合的体现

我们可以这么理解:信号槽就像是一个"中介",发送方和接收方都只跟中介打交道,相互之间谁也不认识谁!所以这种关系是松散耦合的,随时可以建立,也可以随时解除。

这也就是为什么 Qt 能把界面和业务逻辑分得这么清晰------界面控件只负责发信号,业务逻辑只负责写槽函数,中间用 connect 一连,整个程序就跑起来了!

二、信号和槽断开连接

2.1 为什么要断开

既然信号和槽能够通过 connect 建立连接,那么自然也应该能断开!那什么时候需要断开呢?比如:

  • 某个功能只想触发一次,触发之后就不再需要了
  • 程序运行过程中业务逻辑发生了变化,需要把旧的槽换成新的
  • 接收方对象快要销毁了,再接收信号已经没有意义,甚至会导致程序崩溃

所以 Qt 给我们提供了断开连接的方式,它就是------disconnect

2.2 disconnect 的使用

disconnect 的用法和 connect 几乎一模一样,参数顺序、参数含义完全相同,唯一不同的就是它的作用是把原本连在一起的信号和槽拆开!函数原型如下:

cpp 复制代码
disconnect(const QObject *sender,
           const char *signal,
           const QObject *receiver,
           const char *method);

我们直接来看个例子,就用前面学过的"点击按钮修改窗口标题"来演示:

cpp 复制代码
#include "widget.h"
#include "ui_widget.h"
#include <QPushButton>

Widget::Widget(QWidget *parent)
    : QWidget(parent)
    , ui(new Ui::Widget)
{
    ui->setupUi(this);

    QPushButton* btn = new QPushButton(this);
    btn->setText("点我修改标题");

    // 先建立连接
    connect(btn, &QPushButton::clicked, this, &Widget::handleclicked);

    // 断开连接
    disconnect(btn, &QPushButton::clicked, this, &Widget::handleclicked);
}

Widget::~Widget()
{
    delete ui;
}

void Widget::handleclicked()
{
    this->setWindowTitle("标题被修改了!");
}

我们运行后会发现,无论怎么疯狂点击按钮,窗口标题都没有反应!这就说明信号和槽之间的连接已经被 disconnect 给切断,点击按钮发出的信号再也无法被槽函数接收到了!

小提示 :实际开发中 disconnect 用得其实不算多,因为大多数连接在对象生命周期内都是有效的,对象销毁时 Qt 会自动断开相关的所有连接,不需要我们手动去管!但是当我们需要"动态切换槽函数"或者"只触发一次"的时候,它就派上用场了!

三、lambda 表达式定义槽函数

3.1 传统写法的痛点

我们前面写的槽函数,都是在 widget.h 里声明,再去 widget.cpp 里实现,每次都要在两个文件之间来回切换,写起来是比较繁琐的!特别是有时候槽函数里面的逻辑非常简单,可能就一两行代码,单独搞一个函数出来未免有点小题大做!

那么有没有一种办法,能让我们在建立连接的时候直接把处理逻辑写进去,省去单独定义槽函数的麻烦呢?答案就是------lambda 表达式

3.2 lambda 表达式回顾

lambda 表达式本质上是 C++11 引入的"匿名函数",它允许我们在用到函数的地方直接把函数体写出来,而不用单独起个名字!基本语法如下:

cpp 复制代码
[capture](parameters) -> return_type { 函数体 }
  • [capture]:捕获列表,用来捕获外部变量,决定 lambda 内部能用到外部的哪些东西
  • (parameters):参数列表,和普通函数一样,没有参数可以省略
  • -> return_type:返回值类型,可以省略,编译器会自动推导
  • { 函数体 }:具体执行的代码

捕获列表常用的几种写法,我们用表格来归纳一下:

写法 含义
[] 不捕获任何变量
[=] 按值捕获所有外部变量
[&] 按引用捕获所有外部变量
[this] 捕获当前类的 this 指针
[=, &a] 默认按值捕获,但变量 a 按引用捕获

3.3 lambda 充当槽函数

接下来我们就用 lambda 表达式来改写前面的"点击按钮修改窗口标题",看看有多么简洁!

cpp 复制代码
#include "widget.h"
#include "ui_widget.h"
#include <QPushButton>

Widget::Widget(QWidget *parent)
    : QWidget(parent)
    , ui(new Ui::Widget)
{
    ui->setupUi(this);

    QPushButton* btn = new QPushButton(this);
    btn->setText("点我修改标题");

    // 直接在 connect 里用 lambda 写槽函数
    connect(btn, &QPushButton::clicked, this, [=]() {
        this->setWindowTitle("lambda 表达式成功修改标题!");
    });
}

是不是简洁多了!我们不用再去 widget.h 里声明、去 widget.cpp 里实现了,整个逻辑就清清楚楚地写在 connect 里面,一目了然!

3.4 捕获列表的妙用

lambda 表达式真正强大的地方在于它的捕获列表!我们可以在 lambda 内部访问外部的变量!比如我们想让按钮被点击的时候,把按钮自己的文本也改了,那直接捕获按钮对象即可:

cpp 复制代码
QPushButton* btn = new QPushButton(this);
btn->setText("点我");

connect(btn, &QPushButton::clicked, this, [=]() {
    this->setWindowTitle("标题被修改了!");
    btn->setText("我被点过了!");
});

这里 [=] 按值捕获了 btn,所以 lambda 内部可以访问到这个按钮对象,从而修改它的文本!

注意事项

  1. 当信号带有参数的时候(比如 clicked 带一个 bool 参数表示是否选中),lambda 的参数列表也要对应上,不过通常我们用不到这个参数,可以省略不写
  2. 在 lambda 中修改按值捕获的变量,需要加上 mutable 关键字,否则编译会报错!这是 C++ 的语法规定
  3. 如果 lambda 中用到了 this,那么一定要确保 this 指向的对象还活着,否则会导致野指针崩溃!

四、总结

到这里,我们信号槽的系列就讲完了!我们整体回顾一下这三章的内容:

章节 核心内容
信号槽(一) 概念、connect 的基本使用、官方文档查询
信号槽(二) 自定义信号函数、自定义槽函数、带参信号槽
信号槽(三) 机制意义、disconnect 断开、lambda 槽函数

信号槽是 Qt 最核心、最精华的机制,也是 Qt 区别于其他框架的关键所在!把这三章内容掌握好,后续我们写任何控件交互、业务逻辑,都会游刃有余!接下来的章节我们会进入更多 Qt 控件的学习,让我们继续加油!

相关推荐
老迟到的茉莉2 小时前
Hermes 是谁?跟 Claude Code 差在哪
开发语言·python
BomanGe12 小时前
NSK W1501FA高速精密滚珠丝杠技术手册
数据库·经验分享·规格说明书
学逆向的2 小时前
汇编——内存
开发语言·汇编·算法·网络安全
kiki-bf2 小时前
可用的mysql本地安装教程
数据库·mysql
lbb 小魔仙3 小时前
MuMu 模拟器 6.0 硬核测评:这次升级确实不一样
java·开发语言·模拟器·mumu·硬核测评
r_oo_ki_e_3 小时前
Java 线程池
java·开发语言
多巴胺梦想家3 小时前
数据库恢复技术:当灾难来临
网络·数据库·oracle
杜子不疼.4 小时前
【Qt初识】信号槽(二):自定义信号函数与槽函数
开发语言·qt
北冥you鱼4 小时前
Go 语言初始化 MySQL 数据库实战指南:从零到生产环境部署
数据库·mysql·golang