Qt核心特性之 —— 「信号(Signal)与槽(Slot)」机制

article/2025/10/1 17:35:27

目录

1、Qt 与 Qt Creator简介:

2、关于引用头文件的一些事儿: 

3、信号(Signal)与槽(Slot)机制:

3.1、一个小例子: 

4、自定义信号与槽: 

4.1、运行效果:

5、信号与槽的特性: 

6、Qt 4 版本以前 connect 用法:

7、使用 Lambda 表达式建立连接:


1、Qt 与 Qt Creator简介:

Qt

        Qt (官方发音 [kju:t],音同 cute)是一个跨平台的 C++ 开发库主要用来开发图形用户界面 (Graphical User Interface,GUI) 程序,当然也可以开发不带界面的命令行 (Command User Interface,CUI) 程序。
        Qt 是纯 C++ 开发的,所以学好 C++ 非常有必要。当然 Qt 还存在 Python、Ruby、Perl 等脚本语言的绑定, 也就是说可以使用脚本语言开发基于 Qt 的程序。
        Qt 支持的操作系统有很多,例如通用操作系统 Windows、Linux、Unix,智能手机系统 Android、iOS、WinPhone,嵌入式系统 QNX、VxWorks 等等。

        (简介来自http://c.biancheng.net/view/1792.html,里面还提供了 Qt 下载安装教程)

Qt Creator

        除此以外,就像使用 Visual Studio 集成开发环境来创建Windows平台下的 Winform / WPF 应用程序一样;我们也需要一个类似的软件来创建基于 Qt 的应用程序。这个软件就是 Qt Creator。

        Qt Creator是跨平台的集成开发环境(IDE),可在Windows、Linux和macOS桌面操作系统上运行,并允许开发人员在桌面、移动和嵌入式平台创建应用程序。

2、关于引用头文件的一些事儿: 

以引用 QDebug 类为例,这里我偷懒贴一段 ChatGPT 的回答: 

3、信号(Signal)与槽(Slot)机制:

  • 信号(Signal):用于反映某件事情发生了,比如 QPushButton 的 clickedpressedreleasedtoggled 等信号。本质是个特殊的成员函数声明
    • QPushButton 就是 Qt 中的按钮控件
  • 槽(Slot):信号处理槽函数,用于响应信号;

        信号与槽之间是一种松散耦合的关系,信号 发送端 和 接收端 本身是没有关联的;但我们可以通过 connect 方法绑定 信号 与对应的 槽函数 : 

connect( 信号发送者sender,发出的信号signal,信号接收者receiver,信号处理slot(槽))

        比如我们点击窗口右上角的 "×" 按钮,窗口就关闭了。在这里面:"×" 按钮即信号发送者;"点击" 即那个具体的信号;"窗口" 即信号接收者;"关闭窗口" 函数即信号处理槽函数

        下面是一个真实使用 connect 方法的 C++ 代码语句,仅供参考:

connect( myTestBtn, &QPushButton::clicked, this, &QWidget::close );

3.1、一个小例子: 

        下面,我将举一个生活中的例子帮助大家更好地理解 信号。不过,其实通过上面的叙述,我相信大部分小伙伴对信号与槽的概念已经非常清晰明朗了;而且,我自己也认为下面这个例子有些小题大做了。所以,这一小节的内容大家酌情跳过!

        在短跑比赛中,我们都知道鸣枪是比赛开始的信号。下面是第一个问题:

                “谁是信号的发送者?裁判还是裁判手上的那把枪?别往下看先思考一下!” 

        以生活常识的角度来看,我认为发送者应该是裁判。因为显然枪是没法主导自己应该什么时候发射的,而且枪也不能自主发射信号弹,是裁判去扣动扳机发射的,信号的发出是由人来主导的。就像我经常在电视上听到那些体育竞赛的解说员说:“裁判发出了比赛结束的信号,本场比赛的胜者是谁谁谁”,可能以常识来说,大部分人会认为裁判是信号的发送者。

        然而计算机是“叛逆”且“纯粹”的。在计算机的世界里,谁能发出信号,谁就是信号的发送者,就这么简单。既然"枪响"是信号,那裁判本身能发出枪响吗?不能。只有枪才能发出枪响嘛,所以信号的发送者是枪而不是手握信号枪扣动扳机的裁判。就像我们点击窗口右上角的 "×" 按钮,窗口就关闭了,信号的发送者是 "×" 按钮而不是手握鼠标点击按钮的我们。

        这一点明了后,关于这个例子中,信号 是“鸣枪”或者说是“枪响”应该能理解就不多做解释了。接下来是第二个问题:

                “上面我们提到信号用于反映某件事情发生了,那这个例子中信号反映了什么事情发生了呢?是反映 "扣动扳机,发射信号弹" 这件事发生了还是反映 "短跑比赛开始了" 这件事发生了?还是先思考一下!

        我认为,应该是反映 "扣动扳机,发射信号弹" 这件事发生了。因为 “鸣枪” 可能只是发生在在一个普通的早晨,发生在观众们还在家中休息、运动员还在场馆训练的时候,裁判只是从仓库里拿出这把信号枪来,朝天扣动扳机 “鸣枪” 以试试信号枪还好不好用,以免正式比赛出岔子。这个时候“鸣枪”显然不能说是在反映 "短跑比赛开始了" 这件事发生了。事实上,我们现在就可以把 "短跑比赛开始了" 理解为槽函数了,因为短跑比赛一开始,运动员就起跑;而运动员起跑,就是在响应 “鸣枪” 信号;而响应处理信号的,那就是槽函数嘛。所以,“鸣枪” 就是纯粹地反映 "开枪了" 这件事儿发生了,而 "短跑比赛开始"/"运动员起跑" 就可以称之为信号处理槽函数,它们之间没有任何关联。这就像 Qt 中 QPushButton 按钮的 clicked 信号就是反映 "点击按钮" 这件事儿发生了,和 "关闭窗口" 没有任何关系。这就是上面提到的 —— 信号与槽之间是一种松散耦合的关系,信号 发送端 和 接收端 本身是没有关联的

        真正将 “鸣枪” 与 “短跑比赛开始” 建立起联系来的,就是正式比赛那天,观众们走出家门、来到场馆,运动员结束训练、站在起跑线,裁判到场就位、整装待发之时,所有人都心知肚明 —— “枪一响,比赛就开始!”,这个时候,它们就建立起联系了。而在 Qt 中,这一切可以简单地用 connect 方法实现。

        最后,我们梳理下思绪,总结下这个例子:

                若以「信号(Signal)与槽(Slot)」的机制来理解本例。那么在本例中,信号的发送者是 裁判手中的那把枪 ;发出的信号是 鸣枪/枪响 ;信号的接收者是 运动员 ;信号处理槽函数是 短跑比赛开始/运动员起跑

4、自定义信号与槽: 

        接下来例举一个简单的自定义信号与槽的 C++ 代码例,该例主要包含三大部分:自定义信号 myprintsignal() (在MySignal类中声明)、自定义槽 myprintslot() (在MySlot类中声明与定义)、发出信号的方法 startprint() (在Widget类中声明与定义) 以及 它们相应的重载。

        这里简单提一下发出信号的方法 startprint() :我们知道对于自带的信号比如 QPushButton 的 clicked 点击信号,已经写好了当我们点击按钮时就会发送信号。而对于自定义的信号与槽,connect 仅仅只是把信号和槽连接起来了,但还没有说啥时候发信号。

        这个时候,我们一般就需要写一个发出信号的方法,调用该方法时,就可以发出信号啦~

信号: 

/* mysignal.h */
#ifndef MYSIGNAL_H
#define MYSIGNAL_H#include <QObject>class MySignal : public QObject
{Q_OBJECT
public:explicit MySignal(QObject *parent = nullptr);signals://自定义信号写在 signals 下//自定义信号无返回值,只需声明,无需在.cpp中定义//自定义信号可以有参数,可以重载//这里自定义一个我的打印信号void myprintsignal();//重载自定义信号void myprintsignal(QString text);
};#endif // MYSIGNAL_H
/* mysignal.cpp */
#include "mysignal.h"MySignal::MySignal(QObject *parent) : QObject(parent)
{}

槽: 

/* myslot.h */
#ifndef MYSLOT_H
#define MYSLOT_H#include <QObject>class MySlot : public QObject
{Q_OBJECT
public:explicit MySlot(QObject *parent = nullptr);//高版本Qt允许自定义槽函数写在public或者全局下//自定义槽函数无返回值,同时需要声明和定义//自定义槽函数可以有参数,可以重载//这里自定义一个我的打印槽函数void myprintslot();//重载槽函数void myprintslot(QString text);
signals:};#endif // MYSLOT_H
/* myslot.cpp */
#include "myslot.h"
#include <QDebug>MySlot::MySlot(QObject *parent) : QObject(parent)
{}void MySlot::myprintslot()
{qDebug() << "这是我自定义的打印槽函数";
}void MySlot::myprintslot(QString text)
{//将 QString 类型转换为 char * 类型,打印出来的文本就没有引号了qDebug() << text.toUtf8().data();    
}

窗口: 

/* widget.h */
#ifndef WIDGET_H
#define WIDGET_H#include <QWidget>
#include "mysignal.h"
#include "myslot.h"QT_BEGIN_NAMESPACE
namespace Ui { class Widget; }
QT_END_NAMESPACEclass Widget : public QWidget
{Q_OBJECTpublic:Widget(QWidget *parent = nullptr);~Widget();private:Ui::Widget *ui;//声明一个私有的MySignal对象MySignal* _mysignal;//声明一个私有的MySlot对象MySlot* _myslot;void startprint();//重载发出信号方法void startprint(QString text);
};
#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);this->setFixedSize(300,300);//实例化一个MySignal对象_mysignal = new MySignal(this);//实例化一个MySlot对象_myslot = new MySlot(this);//定义函数指针(对于无参的可以在参数列表写个void,也可以空白)void (MySignal:: *signalpointer1)() = &MySignal::myprintsignal;void (MySlot:: *slotpointer1)(void) = &MySlot::myprintslot;void (MySignal:: *signalpointer2)(QString) = &MySignal::myprintsignal;void (MySlot:: *slotpointer2)(QString) = &MySlot::myprintslot;//建立连接connect(_mysignal, signalpointer1, _myslot, slotpointer1);connect(_mysignal, signalpointer2, _myslot, slotpointer2);//调用"开始打印"方法,即可发出信号,即可自动调用槽函数startprint();startprint("这是我重载的打印槽函数");
}//开始打印方法
void Widget::startprint()
{//该方法用于发出信号emit _mysignal->myprintsignal();
}void Widget::startprint(QString text)
{//发出带参数的信号emit _mysignal->myprintsignal(text);
}Widget::~Widget()
{delete ui;
}

        通过窗口类 Widget::startprint() 方法可以看到,自定义信号是通过 emit 关键字发出的;而 emit 关键字的存在是为了让程序员在看到 emit 语句时知道这里在做的事情是发出该信号。

4.1、运行效果:

5、信号与槽的特性: 

  1. 一个信号可以绑定连接到多个槽;
  2. 多个信号可以绑定连接到一个槽;
  3. 信号的参数个数可以多于槽函数的参数个数,反之不行;
  4. 信号和槽函数都有的参数,类型必须按照参数表从头一一对应。以上面自定义的信号和槽为例:
    //假设信号声明成这样:
    void myprintsignal(int num, QString text);//那么,这样的槽是允许与该信号 connect 的:
    void myprintslot(int num, QString text);
    void myprintslot(int num);
    void myprintslot();//而这样的槽是不允许的:
    void myprintslot(QString text);
    void myprintslot(QString text, int num);
    void myprintslot(int num, bool flag);
    void myprintslot(int num, QString text, bool flag);
  5. 一个信号可以连接另一个信号:还是以上面自定义的信号与槽为例,假设我们在窗口内新增了一个 QPushButton 命名为 "btn",现在我们想用按钮的"点击"信号去触发另一个不带参数的 myprintsignal() 信号。我们同样用 connect 把这两个信号绑定连接起来就可以了:
    //信号连接信号
    QPushButton* btn = new QPushButton("打印", this);
    connect(btn, &QPushButton::clicked, _mysignal, signalpointer1);/*对于信号之间的连接,同样满足前面的4条特性:
    比如在官方文档提供的信息中,QPushButton 的 clicked 信号声明形式是:
    void clicked(bool checked = false);
    那么 clicked 信号就不能与
    void myprintsignal(QString text);
    信号相绑定连接*/
  6. 运行效果如下图所示:
  7. 信号是可以断开的,信号与槽的连接、信号与信号的连接都是可以断开的。调用 disconnect  方法断开信号即可:
    //断开信号
    disconnect(_mysignal, signalpointer1, _myslot, slotpointer1);
    disconnect(btn, &QPushButton::clicked, _mysignal, signalpointer1);

6、Qt 4 版本以前 connect 用法:

        本文使用的 Qt 版本是 5.14.2,在稍早一些的 Qt 版本比如 Qt 4 版本以前,connect 方法的使用方式会有些许不同。还是以上面自定义的信号和槽为例,现我们绑定链接无参的 myprintsignal() 信号和无参的 myprintslot() 槽,形式应当如下:

connect(_mysignal, SIGNAL(myprintsignal()), _myslot, SLOT(myprintslot()));

        这个形式在目前的 Qt 5 和 Qt 6 版本仍然是适用的。不过需要注意的是:若要使用这种形式,就必须把槽函数声明在 public slots: 下!若声明在 public全局下则会弹出找不到槽的提示:

        这个形式优点是参数直观;缺点是类型不安全。所以现在不推荐使用了

7、使用 Lambda 表达式建立连接:

        Lambda 表达式(匿名函数)是 C++ 11 中的新特性。对于本文第 5 章提到的信号和槽函数的参数特性(第3条和第4条特性),我们可以使用 Lambda 表达式来解决参数限制的问题。比如现在我们来修改下第5条特性(信号连接信号)的那段代码:

connect(btn, &QPushButton::clicked, _mysignal, [=](){emit _mysignal->myprintsignal("这是使用 Lambda 表达式的连接");   //在匿名函数中发送带参信号btn->setText("test");                                          //在匿名函数中调用普通成员函数_myslot->myprintslot("测试下呢");                               //在匿名函数中调用槽
});

运行效果如下图:


http://chatgpt.dhexx.cn/article/dXH00lH6.shtml

相关文章

三张图搞定TCP 握手、HTTPS、TLS加密过程

1. 抓包内容&#xff08;WireShark&#xff09; 2. 搞定握手、挥手、SSL加密过程 3. 消息内容&#xff08;Charles&#xff09; 之前看到写的比较好的文章&#xff0c;有文字详细叙述&#xff1a; TLS版本差异 https://zhuanlan.zhihu.com/p/27524995?utm_source协议解析 htt…

TCP三次握手学习心得

客户端A若要连接服务端B&#xff0c;首先A会向B发送一个连接请求&#xff0c;其中SYN1&#xff0c;ACK0&#xff0c;B为了告诉A成功收到了消息&#xff0c;则向A发送一个确认包&#xff0c;其中SYN1,ACK1,这时A收到之后又会向B发送一个确认收到确认包的确认包&#xff0c;SYN0,…

使用wireshark抓取Tcp三次握手

文章目录 wireshark的下载安装TCP协议段格式简单介绍确认应答机制介绍使用wireshark抓取TCP的三次握手 wireshark的下载安装 软件的下载可以直接去官网下载 wireshark&#xff0c;选择自己电脑适合的版本就行。 但是不咋推荐&#xff0c;原因是国外网站访问速度太慢&#xff…

TCP的三次握手、四次挥手

一、TCP的三次握手 第一次握手&#xff1a;你能和我建立连接吗&#xff0c;可以接受到我的数据吗。 SYN 1 &#xff0c;seq x 第二次握手&#xff1a;可以建立连接&#xff0c;我接受到你的请求了&#xff0c;能接受到我的数据吗&#xff0c;你的数据是这个吗 SYN 1 &#…

TCP三次握手原理

在众多的网络协议中&#xff0c;TCP协议占据着举足轻重的地位&#xff0c;你知道什么是TCP协议吗&#xff1f; 一、TCP协议 TCP(Transmission Control Protoco)协议属于计算机网络体系中的运输层。运输层的任务是负责向主机中应用层进程之间的通信提供通用的数据传输服务。所以…

TCP三次握手及四次挥手过程中的异常处理

1. 消息丢失的情况&#xff1a; 总的原则&#xff1a; ACK不会重传&#xff0c;SYN和FIN报文段有最大重传次数。无论是SYN还是FIN&#xff0c;达到最大重传次数后对端若仍无响应则直接进入CLOSED状态。 1.1 三次握手过程的消息丢失&#xff1a; 正常的三次握手的流程&#x…

HTTPS 中 TLS 和 TCP 能同时握手吗?

HTTPS 中 TLS 和 TCP 能同时握手吗&#xff1f; 大家好&#xff0c;我是小林。 有位读者在面试的时候&#xff0c;碰到这么个问题&#xff1a; 面试官跟他说 HTTPS 中的 TLS 握手过程可以同时进行三次握手&#xff0c;然后读者之前看我的文章是说「先进行 TCP 三次握手&#…

TCP优化一:TCP 三次握手的优化

TCP 三次握手的性能提升 TCP 是面向连接的、可靠的、双向传输的传输层通信协议&#xff0c;所以在传输数据之前需要经过三次握手才能建立连接。 那么&#xff0c;三次握手的过程在一个 HTTP 请求的平均时间占比 10% 以上&#xff0c;在网络状态不佳、高并发或者遭遇 SYN 攻击…

TCP握手过程和挥手过程

TCP报文首部 源端口和目的端口&#xff0c;各占2个字节&#xff0c;分别写入源端口和目的端口&#xff1b;序号&#xff0c;占4个字节&#xff0c;TCP连接中传送的字节流中的每个字节都按顺序编号。例如&#xff0c;一段报文的序号字段值是 301 &#xff0c;而携带的数据共有1…

TCP三次握手

TCP协议是传输层协议&#xff0c;是一种面向连接的传输控制协议&#xff0c;可以控制流量的传输。是一种可靠的传输&#xff0c;能够保证数据的完整性&#xff0c;有效性和有序性。 1.TCP建立连接&#xff08;三次握手&#xff09; 第一次握手&#xff1a;PC1发送SYN请求&…

TCP握手与挥手详解(附有图)

为什么不是4次握手 首先我们知道TCP是3次握手与4次挥手&#xff0c;为什么不是4次握手呢&#xff0c;因为其中握手请求同步过程中并不需要数据传输因此将两次合并为一次了。 我们需要掌握哪些标志量 SYN&#xff1a;请求同步标志&#xff0c;为1的时候为有效 ACK&#xff1…

为什么TCP需要握手

一、TCP握手流程 二、为什么不是4次握手 TCP的每次请求都是成对的&#xff0c;原则上应该是四次 【Client to Server】第一次SYN&#xff0c;seqx【Server to Client】第二次ACK&#xff0c;seqy&#xff0c;ackx1&#xff08;没有携带数据的ACK不消耗序列号&#xff09;【Se…

tcp_tw_recycle引起的TCP握手失败

背景 测试环境的一台Nginx服务器&#xff0c;最近一直被前端同事吐槽网络有问题&#xff0c;经常出现访问HTTP请求时超时&#xff0c;哪怕是静态文件也经常超时。 刚开始以为是公司网络抽风了&#xff0c;也就没放在心上&#xff0c;但持续了一个星期&#xff0c;而且复现率很…

TCP握手过程(正解版)

参考文章 Why do we need a 3-way handshake? Why not just 2-way https://blog.csdn.net/qq_36903042/article/details/102656641 https://blog.csdn.net/qq_36903042/article/details/102513465 大部分网络博客的错误解读 首先需要声明的是&#xff0c; 百度搜索到的大…

网络协议 (五) TCP握手建立连接

一、握手策略 为了可以准确的将数据准确无误地送达目标主机&#xff0c;所有基于 TCP 实现的协议&#xff0c;都需要先完成 TCP 协议的三次握手策略。 1. 首先我们需要了解一下图中提到的几个标志符&#xff1a; 1.序号seq seq 是TCP通信过程中&#xff0c;某一个传输方向上字…

TCP 握手没成功怎么办?

大家好&#xff0c;我是小林。 之前收到个读者的问题&#xff0c;对于 TCP 三次握手和四次挥手的一些疑问&#xff1a; 第一次握手&#xff0c;如果客户端发送的SYN一直都传不到被服务器&#xff0c;那么客户端是一直重发SYN到永久吗&#xff1f;客户端停止重发SYN的时机是什么…

深入理解TCP三次握手

一、TCP 包头格式 首先&#xff0c;TCP报文是TCP层传输的数据单元&#xff0c;也称为报文段&#xff0c;下面就是TCP包头格式&#xff1a; 接下来我们来看看每个字段的含义&#xff1a; 源端口和端口字号&#xff1a; TCP源端口&#xff1a;源计算机上应用程序端的端口号&…

TCP的三次握手及四次挥手详解

三次握手 三次握手过程&#xff1a; &#xff08;1&#xff09;第一次握手&#xff1a;Client将标志位SYN置为1&#xff08;表示要发起一个连接&#xff09;&#xff0c;随机产生一个值seqJ&#xff0c;并将该数据包发送给Server&#xff0c;Client进入SYN_SENT状态&#xff0c…

C语言中main函数参数使用

在C99标准中定义main函数两种正确的写法 int main(void); int main(int argc, char* argv[]);常见的不标准写法 void main() main()这里主要说明带参数的main函数如何使用 int main(int argc, char* argv[]) {int i;for (i0; i<argc; i)printf("%d: %s\r\n", i…

C语言main函数参数、返回值

C语言main函数返回值&#xff1a; main函数的返回值&#xff0c;用于说明程序的退出状态。如果返回0&#xff0c;则代表程序正常退出&#xff1b;返回其他数字的含义则由系统决定&#xff0c;通常&#xff0c;返回非零代表程序异常退出&#xff0c;即使程序运行结果正确也仍需修…