【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.对于使⽤程序的⼈来说,是感知不到的


🚩总结

相关推荐
西红柿煎蛋7 分钟前
C++17的 std::string_view 是为了解决什么问题?它和 const std::string& 相比有什么核心优势和潜在陷阱?
c++
Q_Q51100828517 分钟前
python的驾校培训预约管理系统
开发语言·python·django·flask·node.js·php
凡小烦28 分钟前
Retrofit源码解析
android·面试
_祝你今天愉快32 分钟前
Java垃圾回收(GC)探析
android·java·后端
Dxy123931021639 分钟前
Python正则表达式使用指南:从基础到实战
开发语言·python·正则表达式
YLCHUP1 小时前
题解:P4447 [AHOI2018初中组] 分组
开发语言·数据结构·c++·经验分享·算法·贪心算法·抽象代数
R-G-B1 小时前
【05】大恒相机SDK C#开发 —— Winform中采集图像并显示
开发语言·c#·大恒相机sdk·winform中采集图像·winform中采集图像并显示
R-G-B1 小时前
【10】大恒相机SDK C++开发 ——对相机采集的原图像数据IFrameData裁剪ROI 实时显示在pictureBox中,3种方法实现(效率不同)
c++·大恒相机开发·大恒相机·工业相机原图像数据裁剪roi·iframedata裁剪roi·原图像数据裁剪roi 实时显示
qq_393828221 小时前
苹果MAC 安卓模拟器
android·软件需求·模拟器
三小尛2 小时前
C++友元
开发语言·c++·算法