4.QT-信号和槽|存在意义|信号和槽的连接方式|信号和槽断开|lambda表达式|信号和槽优缺点(C++)

信号和槽存在意义

所谓的信号槽,终究要解决的问题,就是响应用户的操作
信号槽,其实在GUI开发的各种框架中,是一个比较有特色的存在
其他的GUI开发框架,搞的方式都要更简洁一些~~ 网页开发 (js + dom api)
网页开发中响应用户操作,主要就是挂回调函数

button.onclick = handle;

function handle(){
	...
}

处理函数就像控件的一个属性/成员一样
不需要搞一个单独的connect完成上述的信号槽连接~
一对一.
一个事件,只能对应一个处理函数
一个处理函数也只能对应到一个事件上.

Qt信号槽,connect这个机制,设想很美好的~^

  1. 解耦合.把触发用户操作的控件和处理对应用户的操作逻辑解耦合.
  2. "多对多"效果,一个信号,可以connect到多个槽函数上. 一个槽函数,也可以被多个信号connect.
    Qt中谈到的信号和槽"多对多”就和数据库中的多对多非常类似的

一个信号,可以connect到多个槽函数上
一个槽函数,也可以被多个信号connect
![[Pasted image 20250418082032.png]]

综上,Qt引I入信号槽机制,最本质的目的(初心)
就是为了能够让信号和槽之间按照“多对多”的方式来进行关联~~ 其他的GUI框架往往也不具备这样的特性~
其实在GUI开发的过程中,“多对多”这件事,其实是个“伪需求
实际开发很少会用到
绝大部分情况,一对一就够用了.

信号与槽的连接⽅式

⼀对⼀

主要有两种形式,分别是:⼀个信号连接⼀个槽和⼀个信号连接⼀个信号

  1. ⼀个信号连接⼀个槽
    ![[Pasted image 20250417223759.png]]

  2. ⼀个信号连接另⼀个信号
    ![[Pasted image 20250417223824.png]]

⼀对多

⼀个信号连接多个槽
![[Pasted image 20250417223843.png]]

多对⼀

多个信号连接⼀个槽函数
![[Pasted image 20250417223859.png]]

信号与槽的断开

使⽤disconnect即可完成断开.
disconnect的⽤法和connect基本⼀致
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();
  
    void handleClick();
  
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->pushButton, &QPushButton::clicked, this, &Widget::handleClick);
}
  
Widget::~Widget()
{
    delete ui;
}
  
void Widget::handleClick()
{
    this->setWindowTitle("修改窗口标题");
}

![[Pasted image 20250418084654.png]]

添加一个修改槽函数的Button
![[Pasted image 20250418084857.png]]

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();
  
    void handleClick();
    void handleClick2();
  
private slots:
    void on_pushButton_2_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);
  
    connect(ui->pushButton, &QPushButton::clicked, this, &Widget::handleClick);
}
  
Widget::~Widget()
{
    delete ui;
}
  
void Widget::handleClick()
{
    this->setWindowTitle("修改窗口标题");
}
  
void Widget::handleClick2()
{
    this->setWindowTitle("修改窗口标题2");
}
  
  
void Widget::on_pushButton_2_clicked()
{
    //1.先断开pushButton原来的信号槽
    disconnect(ui->pushButton, &QPushButton::clicked, this, &Widget::handleClick);
    //2.重新绑定信号槽
    connect(ui->pushButton, &QPushButton::clicked, this, &Widget::handleClick2);
}

![[Pasted image 20250418085159.png]]

void Widget::handleClick()
{
    this->setWindowTitle("修改窗口标题");
    qDebug() << "handleClick";
}
  
void Widget::handleClick2()
{
    this->setWindowTitle("修改窗口标题2");
    qDebug() << "handleClick2";
}

![[Pasted image 20250418085721.png]]

如果切换的时候不disconnect,到时候一个信号就会触发两个槽函数,点一下第一个按钮qdebug会同时输出handleClick和handleClick2

使用lambda表达式定义槽函数

Qt5在Qt4的基础上提⾼了信号与槽的灵活性,允许使⽤任意函数作为槽函数。
但如果想⽅便的编写槽函数,⽐如在编写函数时连函数名都不想定义,则可以通过Lambda表达式来达到这个⽬的。
Lambda表达式是C++11增加的特性。C++11中的Lambda表达式⽤于定义并创建匿名的函数对
象,以简化编程⼯作。
Lambda表达式的语法格式如下

[ capture ] ( params ) opt -> ret {  
	Function body;  
};
capture捕获列表
params参数表
opt函数选项
ret返回值类型
Function body函数体
1、局部变量引⼊⽅式[]

[]:标识⼀个Lambda表达式的开始。不可省略。

符号说明
[]局部变量捕获列表。Lambda表达式不能访问外部函数体的任何局部变量
[a]在函数体内部使⽤值传递的⽅式访问a变量
[&b]在函数体内部使⽤引⽤传递的⽅式访问b变量
[=]函数外的所有局部变量都通过值传递的⽅式使⽤,函数体内使⽤的是副本
[&]以引⽤的⽅式使⽤Lambda表达式外部的所有变量
[=,&foo]foo使⽤引⽤⽅式,其余是值传递的⽅式
[&,foo]foo使⽤值传递⽅式,其余引⽤传递
[this]在函数内部可以使⽤类的成员函数和成员变量,=和&形式也都会默认引⼊

说明:

  • 由于使⽤引⽤⽅式捕获对象会有局部变量释放了⽽Lambda函数还没有被调⽤的情况。如果执⾏Lambda函数,那么引⽤传递⽅式捕获进来的局部变量的值不可预知。所以绝⼤多数场合使⽤的形式为: [=] () { }
  • 早期版本的Qt,若要使⽤Lambda表达式,要在".pro"⽂件中添加: CONFIG += C++11
    因为Lambda表达式是C++11标准提出的。Qt5以上的版本⽆需⼿动添加,在新建项⽬时会⾃动添加
#include "widget.h"
#include "ui_widget.h"
#include <QPushButton>
#include <QDebug>
  
Widget::Widget(QWidget *parent)
    : QWidget(parent)
    , ui(new Ui::Widget)
{
    ui->setupUi(this);
    QPushButton* button = new QPushButton(this);
    button->setText("按钮");
    button->move(200, 200);
    connect(button, &QPushButton::clicked, this, [](){
        qDebug() << "lambda 被执行了!";
    });
}
  
Widget::~Widget()
{
    delete ui;
}

![[Pasted image 20250418091847.png]]

![[Pasted image 20250418093924.png]]

lambda表达式,是一个回调函数
这个函数,无法直接获取到上层作用域中的变量的~~
lambda为了解决上述问题,引l入了“变量捕获”语法通过变量捕获,获取到外层作用域中的变量

connect(button, &QPushButton::clicked, this, [button](){
	qDebug() << "lambda 被执行了!";
	button->move(300, 300); 
});

![[Pasted image 20250418094151.png]]

connect(button, &QPushButton::clicked, this, [button, this](){
	qDebug() << "lambda 被执行了!";
	button->move(300, 300);
	this->move(100, 100);
});

![[Pasted image 20250418094850.png]]

写作[=]
这个写法的含义就是把上层作用域中所有的变量名都给捕获进来

connect(button, &QPushButton::clicked, this, [=](){
	qDebug() << "lambda 被执行了!";
	button->move(300, 300);
	this->move(100, 100);
});

后续如果我们对应的槽函数比较简单,而且是一次性使用的.就经常会写作这种lambda的形式
另外也要确认捕获到lambda内部的变量是有意义的
回调函数执行时机是不确定的(用户啥时候点击按钮不知道的)无论何时用户点击了按钮,捕获到的变量都能正确使用
![[Pasted image 20250418100015.png]]

由于此处的button是new出来的变量,生命周期跟随整个窗口(挂到对象树上,窗口关闭才会释放)
这个东西就可以在后面随时使用了
类似的this指向的对象widget

这个变量是在main函数结束销毁 main结束,说明进程结束了,
只要进程不结束,widget就可用.this也就可用了,
lambda除了可以按照值的方式来捕获变量[=]还可以按照引l用的方式来捕获[&](Qt中很少这么写)捕获到的变量一般就是各种控件的指针. 指针变量按照值传递或者引用来传递,都无所谓
如果按引用,还得更关注这个引用的变量本身的生命周期

lambda语法是C++11中引l入的
对于Qt5及其更高版本,默认就是按照C++11来编译的
如果使用Qt4或者更老的版本,就需要手动在.pro文件中加上C++11的编译选项

2、函数参数 ( )

(params)表⽰Lambda函数对象接收的参数,类似于函数定义中的⼩括号表⽰函数接收的参数类型和个数。参数可以通过按值(如:(int a,int b))和按引⽤(如:(int &a,int &b))两种⽅式进⾏传递。函数参数部分可以省略,省略后相当于⽆参的函数
![[Pasted image 20250418110809.png]]

3、选项Opt

Opt部分是可选项,最常⽤的是 mutable声明 ,这部分可以省略。
Lambda表达式外部的局部变量通过值传递进来时,其默认是const,所以不能修改这个局部变量的拷⻉,加上mutable就可以修改。
![[Pasted image 20250418110934.png]]

4、Lambda表达式的返回值类型 ->

可以指定Lambda表达式返回值类型;如果不指定返回值类型,则编译器会根据代码实现为函数推导⼀个返回类型;如果没有返回值,则可忽略此部分。
![[Pasted image 20250418111008.png]]

5、Lambda表达式的函数体 { }

Lambda表达式的函数体部分与普通函数体⼀致。⽤ { } 标识函数的实现,不能省略,但函数体可以为空。

信号与槽的优缺点

优点:松散耦合

信号发送者不需要知道发出的信号被哪个对象的槽函数接收,槽函数也不需要知道哪些信号关联了⾃⼰,Qt的信号槽机制保证了信号与槽函数的调⽤。⽀持信号槽机制的类或者⽗类必须继承于QObject类。

缺点:效率较低

与回调函数相⽐,信号和槽稍微慢⼀些,因为它们提供了更⾼的灵活性,尽管在实际应⽤程序中差别不⼤。通过信号调⽤的槽函数⽐直接调⽤的速度慢约10倍(这是定位信号的接收对象所需的开销;遍历所有关联;编组/解组传递的参数;多线程时,信号可能需要排队),这种调⽤速度对性能要求不是⾮常⾼的场景是可以忽略的,是可以满⾜绝⼤部分场景

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值