QT | 信号与槽(超详解)

06-01 851阅读

 前言

对qt信号和槽的详细解释

💓 个人主页:普通young man-CSDN博客

⏩ 文章专栏:C++_普通young man的博客-CSDN博客

⏩ 本人giee:   普通小青年 (pu-tong-young-man) - Gitee.com

      若有问题 评论区见📝

🎉欢迎大家点赞👍收藏⭐文章

————————————————

QT | 信号与槽(超详解)


目录

信号和槽的概述

信号的本质

槽的本质

信号和槽的使用

FunctionPointer ::Object *sender 参数解释

代码实现:

可视化实现

自定义信号和槽

信号函数实现的要求

通过按键发送信号

信号和槽带参数

代码展示:

违反规则举例:

信号和槽的关系

一对一

⼀对多

多对一

信号与槽的断开

Lambda的利用

信号与槽的优缺点

优点:松散耦合

缺点:效率较低

低耦合高聚合

低耦合

高聚合


信号和槽的概述

在 Qt 中,用户与控件的每次交互都构成一个事件,如 “用户点击按钮”“用户关闭窗口” 等。每个事件都会发出相应信号,像点击按钮会发出 “按钮被点击” 信号,关闭窗口会发出 “窗口被关闭” 信号。

Qt 里的所有控件都具备接收信号的能力,且一个控件可接收多个不同信号。对于接收到的信号,控件会做出相应响应动作,这种响应动作在 Qt 中被称为槽。例如,按钮所在窗口接收到 “按钮被点击” 信号后,可能做出 “关闭自己” 的响应;输入框接收到 “输入框被点击” 信号后,会 “显示闪烁的光标,等待用户输入数据”。

信号和槽是 Qt 独有的消息传输机制,其作用是将相互独立的控件关联起来。以 “按钮” 和 “窗口” 为例,它们原本是独立控件,点击按钮不会对窗口产生影响,但通过信号和槽机制,就能实现 “点击按钮使窗口关闭” 的效果。

QT | 信号与槽(超详解)

信号的本质

信号的本质就是事件,比如用户的点击就可以是一个点击事件,窗口的刷新,鼠标的悬停,按下,释放,键盘的输入等等。

qt中其实信号就是一个函数,当事件产生qt的框架就会通知这个函数,然后这个函数就会发出当前的这个信号(发出这个信号也可以想成一种函数返回)

槽的本质

槽的本质就是处理信号发出的信号并完成对应的动作 , 槽也就是响应信号的一个函数,可以这么想,信号发出,槽函数接收到之后就执行相关操作。槽函数和一般函数的区别:槽函数可以与一个信号关联,当信号发出的时候,关联的槽函数自动执行。

注:

在 Qt 的信号和槽机制中,其底层是通过函数间的相互调用来实现的。每个信号都可以用函数表示,这类函数被称为信号函数;每个槽也能用函数来表示,被叫做槽函数。

 

例如,“按钮被按下” 这一信号可以用 clicked() 函数来表示,“窗口关闭” 这个槽则可以用 close() 函数表示。若要使用信号和槽机制实现 “点击按钮会关闭窗口” 的功能,本质上就是 clicked() 函数调用 close() 函数所达成的效果。

 

信号函数和槽函数通常存在于某个类中,与普通成员函数相比,它们具有以下特别之处:

 
  • 关键字修饰:信号函数使用 signals 关键字修饰,槽函数则使用 public slots、protected slots 或者 private slots 修饰。signals 和 slots 是 Qt 在 C++ 基础上扩展的关键字,专门用于指明信号函数和槽函数。
  • 定义要求:信号函数只需进行声明,无需进行定义(实现);而槽函数则需要进行定义(实现)。

    信号和槽的使用

    再QT中,QObject类提供了一个静态成员函数connect(),用来连接信号函数与槽函数。

    /**
     * @brief 该函数用于在 Qt 中建立信号和槽之间的连接,实现对象间的通信机制。
     *
     * 在 Qt 的事件驱动编程模型里,信号和槽是核心机制,它使得对象之间可以以一种松耦合的方式进行交互。
     * 当信号发出时,与之相连的槽函数会被调用执行。
     *
     * @param sender 指向发出信号的对象的指针,该对象必须是 QObject 或其子类的实例。
     *               信号就是从这个对象产生的,例如一个按钮对象可以发出被点击的信号。
     *
     * @param signal 要连接的信号,以字符串形式表示。
     *               通常使用 SIGNAL() 宏来生成这个字符串,例如 SIGNAL(clicked()) 表示按钮被点击的信号。
     *               此字符串准确描述了要监听的信号名称。
     *
     * @param receiver 指向接收信号并执行相应槽函数的对象的指针,同样要求是 QObject 或其子类的实例。
     *                 当 sender 发出 signal 信号时,receiver 对象中的相应槽函数会被触发。
     *
     * @param method 要连接的槽函数,以字符串形式表示。
     *               一般使用 SLOT() 宏来生成,例如 SLOT(close()) 表示关闭窗口的槽函数。
     *               它指定了当信号发出时,receiver 对象中要执行的具体函数。
     *
     * @param type 连接类型,指定了信号和槽之间的连接方式,默认值为 Qt::AutoConnection。
     *             不同的连接类型决定了槽函数何时以及如何被调用,具体如下:
     *             - Qt::AutoConnection:根据 sender 和 receiver 是否在同一线程自动选择连接方式。
     *                                   如果在同一线程,采用直接连接;否则采用队列连接。
     *             - Qt::DirectConnection:信号发出时直接调用槽函数,不考虑线程问题,槽函数会立即执行。
     *             - Qt::QueuedConnection:将槽函数调用放入 receiver 所在线程的事件队列中,
     *                                   槽函数会在 receiver 所在线程的事件循环中被处理,确保线程安全。
     *             - Qt::BlockingQueuedConnection:与 Qt::QueuedConnection 类似,但会阻塞 sender 线程,
     *                                   直到槽函数执行完毕,适用于需要等待槽函数执行结果的情况。
     *             - Qt::UniqueConnection:确保连接是唯一的,避免重复连接。若已经存在相同的连接,不会再次建立。
     *
     * @return 如果成功建立连接,返回 true;若连接失败(如信号或槽不存在等原因),返回 false。
     */
    bool connect (const QObject *sender, const char *signal, const QObject *receiver, const char *method, Qt::ConnectionType type = Qt::AutoConnection);

    注:

    connect函数在连接信号和槽的时候,比如我是一个QPushButton的控件,那connect函数的第二个参数必须是QPushButton的信号函数(父类的信号),不能是其他类的函数

    QT | 信号与槽(超详解)

    对connect函数的第二,四个参数做解答:

    QT | 信号与槽(超详解)

    你会发现这两个参数居然传的是char*,我们再来看看我传入的函数是什么返回类型:

    QT | 信号与槽(超详解)QT | 信号与槽(超详解)

    返回类型void(*)()函数指针,难道函数指针 == char* ,这当然是不可能的。

    这里上面有一个解决办法:

    就是在传参数的时候加上两个宏

    QT | 信号与槽(超详解)

    SIGNAL和SLOT(将函数指针进行转换,就可以传入)

    SIGNAL 宏

    把信号函数名转换为字符串。在使用 connect 函数连接信号和槽时,要以字符串形式指定信号,SIGNAL 宏就负责完成这个转换,方便将信号和对应的对象关联起来。

    SLOT 宏

    将槽函数名转换为字符串。同样在 connect 函数中,需要以字符串形式指定槽函数,SLOT 宏能把槽函数转换为符合要求的字符串,从而建立信号和槽之间的连接。

    其实,这个connect并不是用的声明上的版本,当我们ctrl+鼠标左键点击connect函数的时候,你会发现是一个qt5之后的版本,这个新的版本还是泛型编程,使得更加灵活

    QT | 信号与槽(超详解)

    FunctionPointer::Object *sender 参数解释

    萃取器(Traits)的概念

    萃取器是 C++ 模板编程中的一种技术,它的核心作用是从某种类型中提取相关的属性或信息。萃取器通常以模板类的形式呈现,通过模板特化来针对不同的类型提供不同的实现。借助萃取器,我们能够在编译时获取类型的相关信息,从而实现更具通用性和灵活性的代码。

    FunctionPointer::Object 作为萃取器的解释

    FunctionPointer 是 Qt 内部的一个模板类,其主要目的是在编译时对函数指针类型进行解析,进而提取出与该函数指针相关的各种信息,像函数的返回类型、参数类型以及函数所属的对象类型等。FunctionPointer::Object 专门用于从信号或槽的函数指针类型 Func1 中提取出函数所属的对象类型。

    简单说就是一个模板的特化

    代码实现:

    我简单的创建一个按键:

    如果你想查看自带的信号:可以点到哪个函数之后按键盘上的F1,就可以打开相关文档

    QT | 信号与槽(超详解)

    这里讲解一个东西,就是你在敲代码的时候会发现:

    QT | 信号与槽(超详解)

    当你调用自带的信号函数的时候,出现了click和一个clicked,这两个一个就是点击了一下(锯齿图标),一个是点击之后会发出信号(信号发射图标),这个你可以自己试一试效果,当你把clicked改成click的时候就会发现点击按钮的时候,什么效果都没有,原因就是没发出信号。

    QT | 信号与槽(超详解)

    还有这个close()是Widget内置的槽函数,作用就是关闭这个窗口并且释放。


    可视化实现

    首先双击这个ui文件,进入可视化的面板

    QT | 信号与槽(超详解)

    然后托一个按钮,当然你可以自己调整大小,文字大小,位置等等

    QT | 信号与槽(超详解)

    clicked()就是点击的意思,这个大家如果英语不好的话可以自己翻译一下,双击

    QT | 信号与槽(超详解)

    双击之后就会在widget.cpp生成出这个函数

    QT | 信号与槽(超详解)

    1. 槽函数名称以 on 开头,并且通过下划线将各部分连接起来。
    2. XXX 表示对象名,即控件的 objectName 属性值。该属性可以在 Qt 设计器中设置,用于唯一标识一个控件对象。QT | 信号与槽(超详解)
    3. SSS 表示对应的信号名称。例如,按钮被点击这一动作对应的信号为 clicked

    自定义信号和槽

    自定义信号不是很常见,因为在GUI qt这么个框架可以是可以穷举这些信号,因此在qt中可以应付大多数场景。

    信号实现起来很简单,因为本质就是一个函数,和槽函数一样比较特殊,但是在qt5中已经和普通的成员函数差不多,因为都继承自最高的那个父类。

    信号函数实现的要求

    1. 类定义
      • 继承自 QObject
      • 添加 Q_OBJECT 宏
      • 信号声明
        • 放在 signals 部分
        • 返回类型为 void
        • 无需实现体
        • 信号发射
          • 使用 emit 关键字(现在在qt5中也可以不用这个关键字,但是为了代码的易读性,建议添加)
          • 信号连接
            • 用 QObject::connect 函数
            • 信号与槽参数类型兼容

    通过一段代码具体讲解

    QT | 信号与槽(超详解)

    通过emit发送信号(如果不使用emit就像直接调用函数一样去调用这个信号函数,在编译的时候就会自定发出信号),但是现在就是一个简单的信号,他现在什么操作都没有

    QT | 信号与槽(超详解)

    调用自己的信号函数

    QT | 信号与槽(超详解)

    我们通过connect来连接一个槽函数:

    QT | 信号与槽(超详解)

    代码编译结果:

    发现我改变了窗口的标题

    QT | 信号与槽(超详解)

    如果我们不调用信号函数,就什么都不会发生:

    QT | 信号与槽(超详解)

    通过按键发送信号

    QT | 信号与槽(超详解)

    QT | 信号与槽(超详解)

    编译结果:

    QT | 信号与槽(超详解)

    信号和槽带参数

    信号和槽函数也是可以带参数的,但是嘞有要求:

    1.信号的槽函数的参数必须一 一对应

    2.信号的参数个数 >= 槽的参数个数

    代码展示:

    首先给一个两个按钮

    QT | 信号与槽(超详解)

    QT | 信号与槽(超详解)

    通过按钮点击发送信号,但是这个信号函数是带参数的,也就是说他会带上这个参数一起发送出去

    QT | 信号与槽(超详解)

    槽函数会接收这个信号以及参数

    QT | 信号与槽(超详解)

    你可能会问他们怎么就能传参?其实大家不要忘了connect函数可以连接这两个函数,所以要求大家信号函数和槽函数的参数一定要保持一致

    违反规则举例:

    当槽函数的参数比信号多的时候,就会报错不匹配

    从这个代码可以看出槽函数的参数是两个,当我们编译的时候就会被检查到信号和插槽参数不兼容

    QT | 信号与槽(超详解)

    那为什么槽函数就不能比信号的参数多,而信号的参数就可以比槽函数的参数少嘞?

    其实简单的说就是一种传递机制,信号函数作为发送端,槽函数作为接收端,这里我们必须解释一下在qt中信号和槽函数是一个多对多的关系,下面我专门来一个标题

    信号和槽的关系

     

    一对一

    一个信号连接一个槽。一个信号连接一个信号。

    QT | 信号与槽(超详解)

    QT | 信号与槽(超详解)

    ⼀对多

    ⼀个信号连接多个槽

    QT | 信号与槽(超详解)

    QT | 信号与槽(超详解)

    QT | 信号与槽(超详解)

    多对一

    多个信号连接⼀个槽函数

    QT | 信号与槽(超详解)

    QT | 信号与槽(超详解)

    QT | 信号与槽(超详解)

    • 参数传递机制
      • Qt 的信号与槽连接机制在实现时,会根据信号和槽的参数信息进行参数传递。当信号参数多于槽函数参数时,Qt 可以很方便地忽略多余的参数,只传递槽函数需要的参数。
      • 但如果槽函数参数多于信号参数,就无法实现参数的正确传递,因为没有足够的信息来填充槽函数的所有参数。

        信号与槽的断开

        断开连接我们到的是disconnect 函数,他的参数列表:

        • 第一个参数 ui->pushButton 是信号的发送者,也就是按钮对象。
        • 第二个参数 &QPushButton::clicked 是要断开连接的信号。
        • 第三个参数 this 是信号的接收者,也就是 Widget 对象。
        • 第四个参数 &Widget::handlefunc 是要断开连接的槽函数。

          下面我直接通过一个代码直观的看出他的作用:

          QT | 信号与槽(超详解)

          QT | 信号与槽(超详解)

          QT | 信号与槽(超详解)

          通过这个代码,我可以实现通过第一个按钮进行打印标题一

          QT | 信号与槽(超详解)

          当点击槽函数切换的按钮是,它会断开之前的连接,重新连接新的槽函数,再次点击按钮一打印标题二

          QT | 信号与槽(超详解)

          QT | 信号与槽(超详解)

          可以打印日志来看是否切换


          Lambda的利用

          其实qt中使用的是c++11:

          QT | 信号与槽(超详解)

          直接上代码:

          QT | 信号与槽(超详解)

          通过这段代码,解释一下,我创建了一个按钮,然后给他设置了一个文字,lambada实现一个我点击之后他会到目标xy坐标位置

          connect本来就是一个泛型的,lambada可以又std::function()包装器接收

          使用lambada时候需要注意:

          QT | 信号与槽(超详解)

          lambada由于作用域的原因需要捕获外部变量,这个是c++11的基础,所以这里访问不到tmp,

          注意qt不需要传引用,因为qt中很多api的参数都是指针,所以直接传值和传引用是一样的

          捕获方式语法示例解释特点示例代码
          值捕获[var1, var2, ...]明确指定要捕获的外部变量,Lambda 内部会复制这些变量的值。- 外部变量的修改不会影响 Lambda 内部的值。

          - Lambda 内部对捕获变量的修改不会影响外部变量。

          cpp
          int x = 10;
          auto lambda = [x]() {
          return x * 2;
          };
          x = 20;
          std::cout
免责声明:我们致力于保护作者版权,注重分享,被刊用文章因无法核实真实出处,未能及时与作者取得联系,或有版权异议的,请联系管理员,我们会立即处理! 部分文章是来自自研大数据AI进行生成,内容摘自(百度百科,百度知道,头条百科,中国民法典,刑法,牛津词典,新华词典,汉语词典,国家院校,科普平台)等数据,内容仅供学习参考,不准确地方联系删除处理! 图片声明:本站部分配图来自人工智能系统AI生成,觅知网授权图片,PxHere摄影无版权图库和百度,360,搜狗等多加搜索引擎自动关键词搜索配图,如有侵权的图片,请第一时间联系我们。

相关阅读

目录[+]

取消
微信二维码
微信二维码
支付宝二维码