[Qt的学习日常]--信号和槽

前言

作者:小蜗牛向前冲

名言:我可以接受失败,但我不能接受放弃

如果觉的博主的文章还不错的话,还请点赞,收藏,关注👀支持博主。如果发现有问题的地方欢迎❀大家在评论区指正

本期学习:什么是信号和槽,自定义槽函数和信号函数,信号和槽的传参,断开,对lambda表达式的使用

目录

一、信号和槽

1、信号和槽的理解

2、信号的本质

3、槽的本质

二、信号和槽的使用

[1、 connect()函数](#1、 connect()函数)

2、自定义槽函数二种实现方法

3、自定义信号

4、信号和槽的带参传递

[三、 信号和槽的其他说明](#三、 信号和槽的其他说明)

1、信号与槽的断开

[2、使⽤ Lambda 表达式定义槽函数](#2、使⽤ Lambda 表达式定义槽函数)

3、信号与槽的优缺点


一、信号和槽

1、信号和槽的理解

在Qt中用户和控件的每一次交互都称为一个事件,⽐如 "⽤户点击按钮" 是⼀个事件,"⽤户关闭窗⼝" 也是⼀个事件。**每个事件都回发送一个信号,**当用户点击按钮,就会发送按钮被点击的信号。

Qt中所有的控件都具备接收信号的能力,一个控件可以接受多种信号,对每种信号都会做出相应的响应动作。例如,按钮所在的窗⼝接收到 "按钮被点击" 的信号后,会做 出 "关闭⾃⼰" 的响应动作;在Qt中,对信号做出响应动作就称为槽。

**信号和槽是 Qt 特有的消息传输机制,**它能将相互独⽴的控件关联起来。⽐如,"按钮" 和 "窗⼝" 本⾝是两个独⽴的控件,点击 "按钮" 并不会对 "窗⼝" 造成任何影响。通过信号和槽机制,可以将 "按 钮" 和 "窗⼝" 关联起来,实现 "点击按钮会使窗⼝关闭" 的效果。

2、信号的本质

信号的本质就是事件。

如:按钮单击、双击 • 窗⼝刷新 • ⿏标移动、⿏标按下、⿏标释放 • 键盘输⼊

信号的呈现形式就是函数, 也就是说某个事件产⽣了, Qt 框架就会调⽤某个对应的信号函数, 通 知使⽤者。

3、槽的本质

槽(Slot)就是对信号响应的函数 。槽就是⼀个函数,与⼀般的 C++ 函数是⼀样的,可以定义在 类的任何位置( public、protected 或 private ),可以具有任何参数,可以被重载,也可以被直接调 ⽤(但是不能有默认参数)。槽函数与⼀般的函数不同的是:槽函数可以与⼀个信号关联,当信号被 发射时,关联的槽函数被⾃动执⾏

注意:

(1)信号和槽机制底层是通过函数间的相互

调⽤实现的每个信号都可以⽤函数来表⽰,称为信号函数;每个槽也可以⽤函数表⽰,称为槽函数

例如: "按钮被按下" 这个信号可以⽤ clicked() 函数表 ⽰,"窗⼝关闭" 这个槽可以⽤ close() 函数表⽰,假如使⽤信号和槽机制实现:"点击按钮会关闭窗⼝" 的功能,其实就是 clicked() 函数调⽤ close() 函数的效果。

(2)信号函数和槽函数通常位于某个类中,和普通的成员函数相⽐,它们的特别之处在于:

  • 信号函数⽤ signals 关键字修饰,槽函数⽤ public slots、protected slots 或者 private slots 修 饰。signals 和 slots 是 Qt 在 C++ 的基础上扩展的关键字,专⻔⽤来指明信号函数和槽函数;
  • 信号函数只需要声明,不需要定义(实现),⽽槽函数需要定义(实现)。

二、信号和槽的使用

1、 connect()函数

在 Qt 中,QObject 类提供了⼀个静态成员函数 connect() ,该函数专⻔⽤来关联指定的信号函数和槽函数。

connect() 函数原型:

cpp 复制代码
​
connect(const QObject* sender,//信号的发送者(哪个控件)
        const char* signal,//发送信息的函数
        const QObject* receiver,//信号的接受者
        const char* method,//接受信号的槽函数
        Qt::ConnectionType type = Qt::AutoConnection);
//type: ⽤于指定关联⽅式,默认的关联⽅式为 Qt::AutoConnection,通常不需要⼿动设定。

代码⽰例: 在窗⼝中设置⼀个按钮,当点击 "按钮" 时关闭 "窗⼝" .:

大家看到这里可能会有疑惑,为什么我们传递的参数类型没有疑惑是对的, 我们明明是要给第一个参数(信号的发送者)传给QObject类的,怎么就变成了传QPushButton类呢?

这是因为在QT中QObject是所有类继承的基类,通过层层的继承关系,QPushButton也继承了QObject,根据继承关系这里也可以相当与QObject使用,这里我们理解了参数1和参数3.

那为什么参数2和参数4明明传递的是一个函数指针,怎么能和char*类型的指针匹配。

其实最初的Qt为了能给信号参数和槽参数传递参数,是要给这二个参数搭配二个厷

SIGBNAL和SLOT厷。

cpp 复制代码
connect(but,SIGNAL(&QPushButton::licked),this,SLOT(&Widget::close));

但是从Qt5开始就对是上面的内容进行了简单化,给connect提供了重载版本,其实参数2和参数4变成了泛形编程,就可以允许传递任意类型的函数指针。

cpp 复制代码
QMetaObject::Connection QObject::connect(
const QObject *sender,
PointerToMemberFunction signal,
const QObject *receiver, 
PointerToMemberFunction slot,
Qt::ConnectionType type = Qt::AutoConnection);

2、自定义槽函数二种实现方法

基本语法:

1. 包含Q_OBJECT宏

在你的类定义中,必须包含 Q_OBJECT 宏。这是因为 Q_OBJECT 宏允许类使用Qt的元对象系统,包括信号和槽的机制。

2. 类继承

确保你的类继承自 QObject 或其子类(如 QWidgetQMainWindow 等),这样类才能支持信号和槽。

3. 声明槽函数

在类声明中,槽函数可以在 public slots:protected slots:private slots: 部分声明,这取决于你希望槽函数的访问级别。

4. 连接信号和槽

使用 QObject::connect 方法将信号连接到槽。

这里我们要完成在窗⼝中设置⼀个按钮,当点击 "按钮" 时改变窗口的名称"。

方法1:代码实现:

这里我们要在widget.h头文件中,对槽函数进行声明

在widget.cpp中进行对函数的编写,这里我们让如果开关被按下就对窗口进行显示更改为"按钮已经被按下"。

方法2: 通过图形化界面

这里会自动帮我们生成一个函数

函数名的说明

这里我们发送,我们并没有借助connect函数将信号和槽链接起来,但是其实Qt会通过函数名字自动连接。

3、自定义信号

其实Qt中内置的信号,基本就够覆盖用户的所以操作了。我们知道,信号的本质其实就是函数,也就是说如果我能要进行自定义信号,就需要自定义信号函数

在widget.h中,我们完成了对信号的定义

在widget.cpp中调用connect完成对信号和槽的连接

4、信号和槽的带参传递

Qt 的信号和槽也⽀持带有参数, 同时也可以⽀持重载. 此处我们要求, 信号函数的参数列表要和对应连接的槽函数参数列表⼀致. 此时信号触发, 调⽤到槽函数的时候, 信号函数中的实参就能够被传递到槽函数的形参当中。

在 "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();
 signals:
    void MySignal(const QString& text);
 public:
    void MYStols(const QString& text);

private slots:
    void on_pushButton_clicked();

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

在 "Widget.cpp" ⽂件实现重载槽函数以及连接信号和槽。

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

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

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

void Widget::MYStols(const QString & text)
{
    this->setWindowTitle(text);
}


void Widget::on_pushButton_clicked()
{
    //发出自定义的信号
    //这里我们就可以通过信号,把我们想要传递的参数给槽的回调函数
    emit MySignal("信号发送");

}

点击允许程序,在点击按钮输出就可以改变窗口名称

对于这种带参传递的作用最大的好处就是可以进行进行代码复用,因为信号和槽的关系,可以是一对一,一对多的,多对多关系。也就是说,我们可以有多个信号,但我点击不同信号的时候,在同一槽中处理(回调函数),只是不同信号,传递给槽函数的参数不同,从而达到代码复用。

是我们要注意:信号传递给槽函数的参数,可以多但是不能少

多了就会用槽能接受几个就用几个但是少了一定是会报错误的。

三、 信号和槽的其他说明

1、信号与槽的断开

使⽤ disconnect 即可完成断开. disconnect 的⽤法和 connect 基本⼀致

在 "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();
 public:
    void MySltos1();
    void MySltos2();



private slots:
    void on_pushButton_2_clicked();

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

在 "Widget.cpp" ⽂件实现重载槽函数以及连接信号和槽。

cpp 复制代码
#include "widget.h"
#include "ui_widget.h"
#include"QDebug"
#include"QPushButton"
Widget::Widget(QWidget *parent)
    : QWidget(parent)
    , ui(new Ui::Widget)
{
    ui->setupUi(this);
   connect(ui->pushButton,&QPushButton::clicked,this,&Widget::MySltos1);
}

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

void Widget::MySltos1()
{
    this->setWindowTitle("修改窗口1");
    qDebug()<<"MySltos1修改";
}

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




void Widget::on_pushButton_2_clicked()
{
    //首先断开连接
    disconnect(ui->pushButton,&QPushButton::clicked,this,&Widget::MySltos1);
    //然后重新绑定连接
     connect(ui->pushButton,&QPushButton::clicked,this,&Widget::MySltos2);
}

这里我们允许程序,点击修改,首先是窗口名称变成 修改窗口1

当我们切换修改就变为,在点击修改就变为修改窗口2 。

注意如果没有用disconnect 断开连接,在点击切换后,会调用二个槽函数

2、使⽤ Lambda 表达式定义槽函数

但如果想⽅便的编写槽函数,⽐如在编写函数时连函数名都不想定义,则可以通过 Lambda表达式 来 达到这个⽬的。 Lambda表达式 是 C++11 增加的特性。C++11 中的 Lambda表达式 ⽤于定义并创建匿名的函数对 象,以简化编程⼯作。

对于lambda表达式这里进行简单介绍:

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

cpp 复制代码
[ capture ] ( params ) opt -> ret { 
 Function body; 
};
  • capture 捕获列表
  • params 参数表
  • opt 函数选项
  • ret 返回值类型
  • Function body 函数

局部变量引⼊⽅式

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

运用实例:

3、信号与槽的优缺点

优点:松散耦合

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

缺点: 效率较低

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

相关推荐
用户805533698032 天前
不止三件套:QObject 属性系统全关键字与运行时反射!
c++·qt
xcyxiner2 天前
DicomViewer (vcpkg Windows和ubuntu编译)7
qt
Quz7 天前
QML Hello World 入门示例
qt
xcyxiner10 天前
DicomViewer (dcmtk读取dcm文件)5
qt
xcyxiner10 天前
DicomViewer (后台线程处理文件)4
qt
xcyxiner11 天前
DicomViewer (添加模型类)3
qt
xcyxiner12 天前
DicomViewer (目录调整) 2
qt
xcyxiner12 天前
dcmtk vtk vtk-dicom(gdcm) 编译(debug) v2
qt
LDR00613 天前
Type-C 快充全面升级!LDR6601 赋能个人护理便携电机,重塑剃须刀 / 理发器新体验
c语言·开发语言
雪碧聊技术13 天前
Tree.js是什么?一文讲透
开发语言·javascript·ecmascript