用最有趣的方式解释信号槽通讯机制

  • 1 导语:信号槽的前世今生
  • 2 QT的信号槽
    • 2.1什么是信号槽?
    • 2.2 用严谨的语言解释信号槽
  • 3 信号槽实战
    • 3.1 信号槽实例
  • 4 信号槽plus
    • 4.1 Lambda表达式
  • 5 信号槽有什么优越性
    • 5.1事件通讯与信号槽通讯的对比
  • 6 在其他语言中使用信号槽(以python为例)

1 导语:信号槽的前世今生

信号槽机制创造的意义并非是在没有路的地方修建了路,而更像是增加可选的路径。每每提到QT,我们就不可避免的会谈论信号槽机制。这是一项伟大的发明,在GUI领域,它在对象的通讯方式中独树一帜——自由而安全,分散但灵活,这是我对于它的粗浅认识。

让我们来谈谈它的出身吧。遥想回调函数风光的那些年,总有那么一群程序员被它的指针弄得心烦意乱,他们开始在各大论坛上吐槽回调机制的不安全性、变量传递还有耦合太强的烦恼……

所谓时势造英雄,信号槽的机制很好的解决了高耦合与线程堵塞的问题——等等,高耦合?线程堵塞?这些高深的名词是怎么回事,一点也不有趣啦!不要着急,且听我慢慢道来。

(注:秉持负责的态度,还是要解释一下高耦合和线程堵塞的概念的。)
①高耦合指的软件系统结构中各模块间紧密联系的一种状态。

更学术一点的说法要从耦合性来说起——耦合性:也称块间联系,指软件系统结构中各模块间相互联系紧密程度的一种度量。模块之间联系越紧密,其耦合性就越强,模块的独立性则越差。模块间耦合高低取决于模块间接口的复杂性、调用的方式及传递的信息。

②线程阻塞通常是指一个线程在执行过程中暂停,以等待某个条件的触发。线程堵塞往往会造成线程死锁,在多任务系统下,当一个或多个进程等待系统资源,而资源又被进程本身或其它进程占用时,就形成了死锁。有个变种叫活锁。

用一个形象的例子说明:就拿我们日常生活中的限量版商品生产和消费来说,货物是有限的,商家会限制顾客数目。同理,当多线程的情况下,某个特定时间下,(峰值高并发)出现消费者速度远大于生产者速度,消费者必须阻塞来等待生产者,以保证生产者能够生产出新的数据;当生产者速度远大于消费者速度时,同样也是一个道理。这些情况都要程序员自己控制阻塞,同时又要线程安全和运行效率。

2 QT的信号槽

2.1什么是信号槽?

在信号槽中出现的概念“信号”和我们平时理解的“信号”非常相似。来一个生活中的场景:你坚持不懈地向你的老爸发短信——爸爸,这个月的生活费可以提高吗?老爸收到这个短信后要么果断拒绝,要么想想觉得你这个月比较努力,给出肯定答复。

在这个有点搞笑的例子里,你向老爸提出的“提高生活费”就是一个信号,你就是信号的发出者,而信号的另一端,也就是你的老爸,就是这个信号的接受者,他给出的选择即相当于槽函数的实现。而整个过程的联系是你与老爸的亲情关系和抚养义务,换而言之,即便你群发这个消息到整个世界,能响应你的也就是那些有联系的人罢了,至于其他人,他们并不会给你生活费……

图一 信号槽趣例

其实笔者给出的是一个很狡猾的例子,这里包含了很多东西:

  1. 信号被设计成什么都不做。
  2. 一个信号可以发送给多个槽,同样,多个信号也能发给一个槽。
  3. 信号与槽是独立分散的不同个体,由特定的连接实现通讯。
  4. 信号与槽能链接,是设立在槽函数对于信号是感兴趣的基础上的。

好了,小伙伴,看懂这个例子,恭喜你信号槽机制入门了!至于要不要继续学就看你自己的了^ ^

2.2 用严谨的语言解释信号槽

可能有同学看完这个例子对信号槽有了初步印象,那么乘热打铁,我用更加严谨的语言来解释一下什么是信号槽吧。

信号只是一个特殊的成员函数声明,函数的返回值是void类型,函数只能声明不能定义,信号必须使用signals关键字进行声明,函数的访问属性自动被设置为protected类型,只能通过emit关键字调用函数(发射信号)。

下面是一个信号的简单实例,在注释中我给出了一切需要注意的点:

图二 信号的定义

至于“槽”,我们其实更习惯于称呼它为“槽函数”。槽就是一个可以被调用处理特定信号的函数。需要注意的是:信号与槽的原型应该完全相同,槽函数的返回值必须是void类型,槽函数可以像普通成员函数一样被调用。

3 信号槽实战

3.1 信号槽实例

哈哈,没想到吧,我把我向老爸要生活费的例子写了出来!我几乎把所有要注意的点都融入进去了。

它的结果是这样的(请暂时忽略别的东西,这些在代码中用于提示注意要点)


点击ask for money 的按钮,会出现如下窗口:

点击give my daughter money,打印出这一段话:

这就完美的完成了从要求给生活费到打钱的响应过程,如果你想看源代码,下面就是。

//Mainwidget.h
#include <QWidget>
#include<QPushButton>
#include"SubWidget.h"class MainWidget : public QWidget
{Q_OBJECTpublic:MainWidget(QWidget *parent = 0);~MainWidget();public slots:void MySlot();void SlotEmployees();void DealSub();void DealSlot(int, QString);private:QPushButton b1;QPushButton *b2;QPushButton b3;QPushButton *b4;SubWidget w2;};#endif // MAINWIDGET_H//Subwiget.h
#ifndef SUBWIDGET_H
#define SUBWIDGET_H#include <QWidget>
#include <QPushButton>class SubWidget : public QWidget
{Q_OBJECT
public:explicit SubWidget(QWidget *parent = 0);private:QPushButton b;signals:void mysignal();void mysignal(int, QString);public slots:void SlotReturn();
};#endif // SUBWIDGET_H//Mainwidget.cpp
#include "mainwidget.h"
#include<QPushButton>
#include<QDebug>MainWidget::MainWidget(QWidget *parent): QWidget(parent)
{b1.setParent(this);b1.setText("close");    //点的左边为实体b1.move(100,100);b2 = new QPushButton(this);b2->setText("^-^");   //->左边为指针b2->move(100,200);connect(&b1,SIGNAL(pressed()), this, SLOT(close()));connect(b2,SIGNAL(released()),this,SLOT(MySlot()));this->setWindowTitle("I am son");b3.setParent(this);b3.setText("send request to father");b3.move(100,300);b4 = new QPushButton(this);b4->setText("^o^");   //->左边为指针b4->move(200,200);connect(b4,SIGNAL(released()),this,SLOT(MySlot()));//一个信号可以关联多个槽//w2.show();connect(&b3,SIGNAL(released()),this,SLOT(SlotEmployees()));connect(&w2,SIGNAL(mysignal()),this,SLOT(DealSub()));//SIGNAL,SLOT宏把函数转换为字符串,要是函数写错,它是不会报错的connect(&w2,SIGNAL(mysignal(int,QString)),this,SLOT(DealSlot(int,QString)));resize(500,500);}MainWidget::~MainWidget()
{}void MainWidget::MySlot()
{b2->setText("123");}void MainWidget::SlotEmployees()
{w2.show();this->hide();
}void MainWidget::DealSub()
{w2.hide();this->show();
}void MainWidget::DealSlot(int a, QString str)
{qDebug()<<a<<str.toUtf8();   //小写大写括号,你的qDebug()
}//Subwidget.cpp
#include "SubWidget.h"SubWidget::SubWidget(QWidget *parent) : QWidget(parent)
{this->setWindowTitle(" your father");b.setParent(this);b.setText("give money");b.move(200,200);connect(&b,SIGNAL(released()),this,SLOT(SlotReturn()));resize(500,500);
}void SubWidget::SlotReturn()
{emit mysignal();emit mysignal(200, "na qu hui huo");//带参数的槽函数
}

4 信号槽plus

4.1 Lambda表达式

相比于QT4,QT5在信号槽上更进一步,它支持的ISO C++ 11 标准的一大亮点是引入Lambda表达式。而lambda表达式使用起来,至少是在形式上更简单方便。

“Lambda 表达式”(lambda expression)是一个匿名函数,

Lambda表达式基于数学中的λ演算得名,直接对应于其中的lambda抽象(lambda abstraction),是一个匿名函数,即没有函数名的函数。Lambda表达式可以表示闭包(注意和数学传统意义上的不同)

话不多说,直接用一个非常经典的实例来解释这一现象。

#include "widget.h"
#include "ui_widget.h"
#include <QDebug>
Widget::Widget(QWidget *parent) :QWidget(parent),ui(new Ui::Widget)
{ui->setupUi(this);// 传统Qt是连接方式// 传统Qt4连接方式为 信号发送者,信号,信号接受者,处理函数QObject::connect(ui->pushButton,SIGNAL(clicked(bool)),this,SLOT(qT4_slot()));//Qt5连接方式//看起来和QT4的方式没有太大差别,只是在Qt4 中引用了信号槽,在简单的使用时没有问题,但是在庞大的工程中,信号和槽仅仅是宏替换,在编译的时候没有安全监测//Qt5在编译的时候就会有监测 QObject::connect(ui->pushButton_2,&QPushButton::clicked,this,&Widget::qT5_slot);//Qt5 Lambda表达式//这里需要注意 Lambda表达式是C++ 11 的内容,所以,需要再Pro项目文件中加入 CONFIG += C++ 11QObject::connect(ui->pushButton_3,&QPushButton::clicked,[=](){qDebug()<<"lambda 表达式";}); }Widget::~Widget()
{delete ui;
}void Widget::qT4_slot()
{qDebug()<< "This is Qt 4 Connect method";
}void Widget::qT5_slot()
{qDebug()<< "This is Qt 5 Connect method";}

5 信号槽有什么优越性

5.1事件通讯与信号槽通讯的对比

终于到了对比产生彼此的优越的时候了,笔者结合了许多网络资料,进行了事件与信号槽机制的对比总结。

1.信号与槽,在同一线程,类型为直连时,信号发生,槽被调用,相当于直接的函数调用,是同步的;而事件,使用sendEvent时是同步的,使用postEvent则是异步,要等到下一轮事件循环才会被处理。

2.跨线程时,采用队列连接的信号与槽,是通过事件实现的。从这点讲,信号与槽等同于事件,只是信号与槽用起来更方便,事件稍微繁琐。

3.事件一方面是处理用户输入,比如按键,鼠标;一方面可以自定义事件用于对象间通讯。无论哪种,都会经过Qt的事件循环。而信号与槽则不一定会经过事件循环。

当然一切的方法只有相对的优越性,信号槽并非是完美的通讯方式plus,他打乱了程序的结构,看到为信号槽服务的源码时,总是有种使不上劲的感受,这是因为这些工作往往是分散的,让许多初学者(比如说我)糟心的是,即使信号槽关联出现问题,QT4往往不会报错,因为在它眼里,SIGNAL,SLOT内的仅仅是字符串而已……

注意,我无意将信号槽与其他通讯方式进行对比,一切解决方案只有在特定的环境下才能说优劣,总体而言,只有最适合的语言和解决方案,没有最完美的方案。

6 在其他语言中使用信号槽(以python为例)

信号槽功能强大,怎么不会引起其他语言的注意呢,笔者在学习QT之前学习了一段时间python GUI编程,了解到万能的python有个PYQT库可用。我们只需导入PyQt库就能在python中与信号槽进行愉快的玩耍啦!(P.S.现有PyQt4和PyQt5版本,个人感觉4版本的信号槽模式更接近于QT环境的操作,5版本的更符合python原生习惯。)

请看一个非常简单的例子,其中包含了关联信号槽,解除关联,再次连接等基本操作,其结果演示如下:

(1)对滑块进行拖动,下面即时显示数字。点击保存,保存状态显示为saved,点击运行,运行状态显示为running。

(2)解除关联之后,即使把滑块拖到极致,也没有改变,而一旦重新关联,移动滑块可以正常运行。

看完演示之后,咱们再来看看这个例子上的核心代码。

# 用connect进行关联
self.buttonRun.clicked.connect(self.buttonSave.clicked)
self.slider.valueChanged.connect(self.pBar.setValue)
self.slider.valueChanged.connect(self.lcdNumber.display)
self.buttonSave.clicked.connect(self.showMessage)
self.buttonRun.clicked.connect(self.showMessage)
self.buttonDisconnect.clicked.connect(self.unbindConnection)
self.buttonConnect.clicked.connect(self.bindConnection)
self.buttonStop.clicked.connect(self.stop)# 用disconnect取消关联
def unbindConnection(self):self.slider.valueChanged.disconnect()# 用bindconnect再次关联信号槽
def bindConnection(self):self.slider.valueChanged.connect(self.pBar.setValue)self.slider.valueChanged.connect(self.lcdNumber.display)# 用stop清空状态
def stop(self):self.saveLabel.setText("")self.runLabel.setText("")

我们可以发现,在其它语言中使用信号槽实现对象间的通讯和在QT环境下实现还是略有不同的,以上提供的是更接近于python语言习惯的信号槽机制。

虽然逻辑相近,但笔者所使用的这种方式确实在很多细节上和QT的展现形式不同。实际上,python的PYQT4库中也提供了用QT的方式进行对象之间的通讯,以上面滑块和数字的连接为例,改过来就是这样的:

# QT式信号槽响应机制
self.connect(slider, QtCore.SIGNAL('valueChanged(int)'), lcdNumber, QtCore.SLOT('display(int)'))

这种写法完全复刻了QT4信号槽的标准写作方式,笔者认为,这种处理大抵是为了降低跨语言学习者在学习中的隔阂感。

PYQT5还提供了许多信号槽的高级玩法,笔者才疏学浅恐有遗漏之处,原创不易,欢迎指正和交流。

本文于2020/1/20 撰写,2020/7/30修改发布。
本文python用例有参考其他博主。

【QT|趣谈】最详细的信号槽机制介绍!相关推荐

  1. Qt的信号槽机制介绍(含Qt5与Qt4的差异对比)

    转载地址: https://blog.csdn.net/nicai888/article/details/51169520 一 闲谈: 熟悉Window下编程的小伙伴们,对其消息机制并不陌生, 话说: ...

  2. Qt的信号槽机制介绍

    Qt 是一个跨平台的 C++ GUI 应用构架,它提供了丰富的窗口部件集,具有面向对象.易于扩展.真正的组件编程等特点,更为引人注目的是目前 Linux 上最为流行的 KDE 桌面环境就是建立在 Qt ...

  3. 【QT开发笔记-基础篇】| 第一章 QT入门 | 1.7 如何连接信号槽

    本节对应的视频讲解:B_站_链_接 https://www.bilibili.com/video/BV1mN4y137H6 信号和槽要建立连接,本质上是通过 `connect` 函数来连接实现的. 但 ...

  4. QT 的信号与槽机制介绍

    QT 是一个跨平台的 C++ GUI 应用构架,它提供了丰富的窗口部件集,具有面向对象.易于扩展.真正的组件编程等特点,更为引人注目的是目前 Linux 上最为流行的 KDE 桌面环境就是建立在 QT ...

  5. 【转载】QT 的信号与槽机制介绍

    QT 是一个跨平台的 C++ GUI 应用构架,它提供了丰富的窗口部件集,具有面向对象.易于扩展.真正的组件编程等特点,更为引人注目的是目前 Linux 上最为流行的 KDE 桌面环境就是建立在 QT ...

  6. Qt信号槽机制-传递自定义数据类型(qRegisterMetaType)

    Qt信号槽机制-传递自定义数据类型qRegisterMetaType 前言 前言 通过Qt内置的数据类型进行信号与槽参数传递很方便:如果是自己定义的类型如果想使用signal/slot来传递的话,则没 ...

  7. 学习QT之信号槽机制详解

    学习QT之信号槽机制详解 一.Qt信号槽机制 概念:信号槽是Qt框架引以为豪的机制之一.所谓信号槽,实际就是观察者模式.当某个事件发生之后,比如:按钮检测到自己被点击了一下,它就会发出一个信号(sig ...

  8. Hello Qt——Qt信号槽机制源码解析

    基于Qt4.8.6版本 一.信号槽机制的原理 1.信号槽简介 信号槽是观察者模式的一种实现,特性如下: A.一个信号就是一个能够被观察的事件,或者至少是事件已经发生的一种通知: B.一个槽就是一个观察 ...

  9. c++实现Qt信号槽机制

    信号槽机制的原理 信号槽是观察者模式的一种实现,或者说是一种升华: 一个信号就是一个能够被观察的事件,或者至少是事件已经发生的一种通知: 一个槽就是一个观察者,通常就是在被观察的对象发生改变的时候-- ...

最新文章

  1. Kruskal算法构造最小生成树
  2. Maven引入外部jar的几种方法
  3. c51单片机led奇数偶数亮_两STM32单片机串口通讯实验
  4. php安装sqlserver2008,php53 mssql2008_sqlserver2008安装环境_php mssql库
  5. python输入变量输出常量_Python输入input、输出print
  6. MySQL 运维及开发规范
  7. Collection与Arrays
  8. BLS数字签名算法介绍及拓展
  9. Eplan破解文件名称说明:
  10. 实在抵不住张老师的诱惑,又跳坑了
  11. Vue的内容抽离过程
  12. mysql a foreign key constraint fails_外键记录有存在,插入数据却报错a foreign key constraint fails...
  13. 晒弟弟考取的教资证写的朋友圈文案
  14. 8.19华为笔试题目c++
  15. vivoX80Pro和华为P50Pro哪个值得入手参数对比
  16. 3dmark压力测试 linux,3DMark压力测试发布:彻底榨干你电脑!
  17. Mandelbrot 并行实现
  18. 不同文件(扩展名)的打开方式
  19. 网络通信基础知识(一)
  20. 小程序--腾讯地图导航

热门文章

  1. 论文笔记 EMNLP 2020|Event Extraction by Answering (Almost) Natural Questions
  2. Oracle获取本年,本月,下月,上月 第一天或最后一天日期
  3. 程序员是否可以在家办公——在家办公的利与弊
  4. 计算机音乐数字乐谱童话,童话钢琴简谱-数字双手-光良
  5. SAP年末余额结转步骤(转)
  6. [HNOI2004] 敲砖块(dp + 前缀和)
  7. RNA 1. SCI 文章中读取 GEO 数据
  8. 合肥工业大学——java(最新版)——第二次作业
  9. redisson-spring-boot-starter
  10. 有多少人等着看百度区块链的笑话?