【QT】Qt 信号与槽的使用详解&&连接方式&&Lambda表达式定义槽函数

文章目录


一、信号与槽的连接方式

1.1⼀对⼀

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

(1)⼀个信号连接⼀个槽

⽰例

⽰例:

1、在"widget.h"中声明信号和槽以及信号发射函数;

cpp 复制代码
#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 EmitSignal();//信号发射函数
    
signals:
    void MySignal();
    
public slots:
    void MySlot();
private:
    Ui::Widget *ui;
};
#endif // WIDGET_H

2、在"widget.cpp"中实现槽函数,信号发射函数以及连接信号和槽;

(2)⼀个信号连接另⼀个信号

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

Widget::Widget(QWidget *parent)
    : QWidget(parent)
    , ui(new Ui::Widget)
{
    connect(this, &Widget::MySignal, this, &Widget::MySlot);
    ui->setupUi(this);
}

void Widget::MySlot()
{
    qDebug() << "好好学习,天天向上";
}
void Widget::EmitSignal()
{
    emit MySignal();
}
Widget::~Widget()
{
    delete ui;
}

(2)⼀个信号连接另⼀个信号

⽰例:

在上述⽰例的基础上,在"widget.cpp"⽂件中添加如下代码:

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

Widget::Widget(QWidget *parent)
    : QWidget(parent)
    , ui(new Ui::Widget)
{
    QPushButton *btn = new QPushButton("按钮", this);
    resize(800, 600);

    connect(this, &Widget::MySignal, this, &Widget::MySlot);

    connect(btn, &QPushButton::clicked, this, &Widget::MySignal);
    ui->setupUi(this);
}

void Widget::MySlot()
{
    qDebug() << "好好学习,天天向上";
}
void Widget::EmitSignal()
{
    emit MySignal();
}
Widget::~Widget()
{
    delete ui;
}

4.2 ⼀对多
⼀个信号连接多个槽

⽰例:

(1)在"widget.h"头⽂件中声明⼀个信号和三个槽;

cpp 复制代码
#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 EmitSignal();

signals:
    void MySignal();

public slots:
    void MySlot1();
    void MySlot2();
    void MySlot3();

private:
    Ui::Widget *ui;
};
#endif // WIDGET_H

( 2)在"widget.cpp"文件中实现槽函数以及连接信号和槽;

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

Widget::Widget(QWidget *parent)
    : QWidget(parent)
    , ui(new Ui::Widget)
{
    connect(this, &Widget::MySignal, this, &Widget::MySlot1);
    connect(this, &Widget::MySignal, this, &Widget::MySlot2);
    connect(this, &Widget::MySignal, this, &Widget::MySlot3);


    ui->setupUi(this);
}


void Widget::MySlot1()
{
    qDebug()<< "MySlot_1";
}

void Widget::MySlot2()
{
    qDebug()<< "MySlot_2";
}

void Widget::MySlot3()
{
    qDebug()<< "MySlot_3";
}

void Widget::EmitSignal()
{
    emit MySignal();
}

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

1.2 多对⼀

多个信号连接⼀个槽函数

⽰例:

(1)在"widget.h"头⽂件中声明两个信号以及⼀个槽;

cpp 复制代码
#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 EmitSignal();

signals:
    void MySignal_1();
    void MySignal_2();

public slots:
    void MySlot();

private:
    Ui::Widget *ui;
};
#endif // WIDGET_H

(2)在"widget.cpp"⽂件中实现槽函数以及连接信号和槽;

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

Widget::Widget(QWidget *parent)
    : QWidget(parent)
    , ui(new Ui::Widget)
{
    QPushButton *btn = new QPushButton("按钮", this);
    connect(this, &Widget::MySignal_1, this, &Widget::MySlot);
    connect(this, &Widget::MySignal_2, this, &Widget::MySlot);
    connect(btn, &QPushButton::clicked, this, &Widget::MySlot);

    EmitSignal();

    ui->setupUi(this);
}

void Widget::MySlot()
{
    qDebug()<<"MySlot";
}

void Widget::EmitSignal()
{
    emit MySignal_1();
    emit MySignal_2();
}

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

二、信号和槽的其他说明

2.1 信号与槽的断开

使⽤disconnect即可完成断开.

disconnect 的⽤法和connect基本⼀致.

⽰例:

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

Widget::Widget(QWidget *parent)
    : QWidget(parent)
    , ui(new Ui::Widget)
{
    QPushButton* btn = new QPushButton("按钮", this);

    btn->move(100, 100);

    resize(800, 600);

    //信号与槽连接
    connect(btn, &QPushButton::clicked, this, &Widget::close);
    //断开信号与槽连接
    disconnect(btn, &QPushButton::clicked, this, &Widget::close);

    ui->setupUi(this);
}

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

2.2 Qt4版本信号与槽的连接

Qt4中的connect用法和Qt5相比是更复杂的.需要搭配SIGNALSLOT宏来完成.而且缺少必要的函数类型的检查.使代码更容易出错.

⽰例:

(1)在"widget.h"头⽂件中声明信号和槽

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

Widget::Widget(QWidget *parent)
    : QWidget(parent)
    , ui(new Ui::Widget)
{
    // Qt4版本的信号与槽连接
    connect(this, SIGNAL(MySignal()), this, SLOT(MySlot()));

    EmitSignal();

    ui->setupUi(this);
}

void Widget::MySlot()
{
    qDebug() << "MySlot()";
}
void Widget::EmitSignal()
{
    emit MySignal();
}
Widget::~Widget()
{
    delete ui;
}

(2)在"widget.cpp"⽂件中实现槽函数以及连接信号与槽;

Qt4版本信号与槽连接的优缺点:

  • 优点:参数直观;
  • 缺点:参数类型不做检测;

⽰例:

2.3 使⽤Lambda表达式定义槽函数

Qt5 在Qt4的基础上提⾼了信号与槽的灵活性,允许使⽤任意函数作为槽函数。

但如果想⽅便的编写槽函数,⽐如在编写函数时连函数名都不想定义,则可以通过Lambda表达式来达到这个⽬的。

Lambda表达式是C++11增加的特性。C++11中的Lambda表达式⽤于定义并创建匿名的函数对象,以简化编程⼯作。

Lambda表达式的语法格式如下:

cpp 复制代码
[ 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"⽂件中添加:

cpp 复制代码
CONFIG += C++11

因为Lambda表达式是C++11标准提出的。Qt5以上的版本⽆需⼿动添加,在新建项⽬时会⾃动添加。

⽰例1:Lambda表达式的使⽤

⽰例2:以=⽅式传递,外部的所有变量在Lambda表达式中都可以使⽤

⽰例3:以a⽅式传递,在Lambda表达式中只能使⽤传递进来的a

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

⽰例:

cpp 复制代码
auto f = [](int x, int y){return x + y; };
int result = f(2, 3);
qDebug() << "result: " << result;
  1. 选项Opt

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

QPushButton* Btn1 = new QPushButton("按钮1", this);

QPushButton* Btn2 = new QPushButton("按钮2", this);

int m = 10;

Btn1->move(800, 100);

Btn2->move(800, 100);

//添加mutable, 可以修改局部变量m的拷贝

connect(Btn1, &QPushButton::clicked, this, mmutable{m = 20; qDebug() << "m= " << m; });

connect(Btn1, &QPushButton::clicked, this, ={qDebug() << "m= " << m; });

ui->setupUi(this);

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

⽰例1:

⽰例2:

  1. Lambda表达式的函数体{ }

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

⽰例:

6、槽函数使⽤Lambda表达式来实现

⽰例1:点击按钮关闭窗⼝;

⽰例2:当"connect"函数第三个参数为"this"时,第四个参数使⽤Lambda表达式时,可以省略掉"this";

三、 信号与槽的优缺点

优点:松散耦合

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

缺点:效率较低

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

⼀个客⼾端程序中,最慢的环节往往是"⼈"

假设本⾝基于回调的⽅式是10us,使⽤信号槽的⽅式是100us.对于使⽤程序的⼈来说,是感知不到的


🚩总结

相关推荐
疋瓞1 小时前
python和C++对比(1)_数据类型和数据结构
数据结构·c++·python
C语言小火车1 小时前
C++ 堆排序深度精讲:基于完全二叉树的选择排序进化,最坏情况 O(n log n) 的稳定王者
开发语言·c++·算法·排序算法·堆排序
机智的张尼玛1 小时前
我基于 Kotlin Multiplatform 实现了一套跨平台的弱网离线同步引擎
android·开源·kotlin
北冥you鱼1 小时前
abigen 最佳实践:从入门到精通,高效生成 Go 语言合约绑定
开发语言·golang·区块链
ALex_zry2 小时前
C++26 std::complex 结构化绑定详解:auto [re, im] = c
c语言·开发语言·c++
她说..3 小时前
Java 默认值设置方式
java·开发语言·后端·springboot
忧郁的紫菜3 小时前
基础实现:单篇 Markdown 转 Word
开发语言·c#·word
甜美的小天鹅3 小时前
Swifter C#之inline还是不inline,这是个问题
开发语言·c#
小短腿的代码世界3 小时前
Qt Bluetooth源码深度解析:从HCI协议到跨平台API的完整架构
开发语言·qt·架构
_olone3 小时前
Luogu P2704 [NOI2001] 炮兵阵地
c++·算法·状压dp