一、信号和槽机制概述

信号槽是 Qt 框架引以为豪的机制之一。所谓信号槽,实际就是观察者模式。当某个事件发生之后,比如,按钮检测到自己被点击了一下,它就会发出一个信号(signal)。这种发出是没有目的的,类似广播。如果有对象对这个信号感兴趣,它就会使用连接(connect)函数,意思是,将想要处理的信号和自己的一个函数(称为槽(slot))绑定来处理这个信号。也就是说,当信号发出时,被连接的槽函数会自动被回调。

槽的本质是类的成员函数,其参数可以是任意类型的。和普通C++成员函数几乎没有区别,它可以是虚函数;也可以被重载;可以是公有的、保护的、私有的、也可以被其他C++成员函数调用。唯一区别的是:槽可以与信号连接在一起,每当和槽连接的信号被发射的时候,就会调用这个槽。

早期,对象间的通信采用回调来实现。回调实际上是利用函数指针来实现,当我们希望某件事发生时处理函数能够获得通知,就需要将回调函数的指针传递给处理函数,这样处理函数就会在合适的时候调用回调函数。回调有两个明显的缺点:

  • 它们不是类型安全的,我们无法保证处理函数传递给回调函数的参数都是正确的。
  • 回调函数和处理函数紧密耦合,源于处理函数必须知道哪一个函数被回调。

二、典型应用示例

下面通过一个简单的例子来进一步讲解信号和槽的相关知识。新建Qt Gui应用,项目名称为mySignalSlot,基类选择QWidget,然后类名保持Widget不变。完成后首先在widget.h文件中声明信号和槽:

#ifndef WIDGET_H
#define WIDGET_H #include <QWidget>
#include <QDebug> namespace Ui {
class Widget;
} class Widget : public QWidget
{
Q_OBJECT //必须包含的宏 public:
explicit Widget(QWidget *parent = nullptr);
~Widget(); public slots:
void testSolts(); //测试槽函数 signals:
void testSignals(); //测试信号 private:
Ui::Widget *ui;
}; #endif // WIDGET_H

使用信号和槽还必须在类声明 的最开始处添加Q_OBJECT宏,在这个程序中,类的声明是自动生成的,已经添加了这个宏。


signal的代码会由 moc 自动生成,开发人员不能在自己的C++代码中实现它。反之,槽应该由开发人员来实现。修改widget.cpp文件如下:

#include "widget.h"
#include "ui_widget.h" Widget::Widget(QWidget *parent) :
QWidget(parent),
ui(new Ui::Widget)
{
ui->setupUi(this); //连接信号槽
connect(this,SIGNAL(testSignals()),this,SLOT(testSolts()));
//发射信号
emit testSignals();
} Widget::~Widget()
{
delete ui;
} //测试槽函数
void Widget::testSolts()
{
qDebug() << "调用了测试槽函数";
}

这个例子实现的效果是:发射信号后,会调用testSolts()槽函数,则“应用程序输出”窗口会打印出“调用了测试槽函数”。

三、使用信号和槽的注意事项

下面列举一下使用信号和槽应该注意的注意事项:

  • 发送者和接收者都需要继承自QObject或其子类;
  • 必须在类声明的最开始添加Q_OBJECT宏;
  • 槽中的参数类型要和信号的参数的类型相对应,且不能比信号的参数多;
  • 使用 signals 标记信号函数,信号是一个函数声明,返回 void,不需要实现函数代码;
  • 使用 emit 在恰当的位置发送信号;
  • 槽函数是普通的成员函数,作为成员函数,会受到 public、private、protected 的影响;
  • 任何成员函数、static 函数、全局函数和 Lambda 表达式都可以作为槽函数。

四、信号和槽的关联

信号和槽进行关联使用的是QObject类的connect()函数,Qt4中这个函数的原型如下:

static QMetaObject::Connection QObject::connect(const QObject *sender,
const char *signal,
const QObject *receiver,
const char *member,
Qt::ConnectionType = Qt::AutoConnection);

第一个参数sender为发送信号的对象,第二个参数signal为要发送的信号,第三个参数receiver为接收信号的对象,第4个参数slot为接收对象在接收到信号之后所需要调用的槽函数。connect()函数的最后一个参数表明了关联的方式,默认值是Qt::AutoConnection。对于信号和槽,必须使用SIGNAL()和SLOT()宏,它们可以将其参数转化为const char*类型。connect()函数的返回值为bool类型,当关联成功时返回true。

对于信号和槽的参数问题,基本原则是信号中的参数类型要和槽中的参数类型相对应,而且信号中的参数可以多于槽中的参数,但是不能反过来;如果信号中有多余的参数,那么它们将被忽略。


而Qt5中connect()函数新加入的一种重载形式如下:

static QMetaObject::Connection QObject::connect(const QObject *sender,
PointerToMemberFunction signal,
const QObject *receiver,
PointerToMemberFunction member,
Qt::ConnectionType type = Qt::AutoConnection);

与Qt4最大的不同就是,指定信号和槽两个参数时可以不用再使用SIGNAL()和SLOT()宏,并且槽函数不再必须是使用slots关键字声明的函数,而可以是任意能和信号关联的成员函数。要使一个成员函数可以和信号关联,那么这个函数的参数数目不能超过信号的参数数目,但是并不要求该函数拥有的参数类型与信号中对应的参数类型完全一致,只需要可以进行隐式转换即可。使用这种重载形式,前面程序中的关联可以使用以下代码代替:

connect(this,&Widget::testSignals,this,&Widget::testSolts);

使用这种方式与前一种相比,还有一个好处就是可以在编译时进行检查,信号与槽的拼写错误、参函数参数数目多于信号的参数数目等错误在编译时就能够发现。所以建议在编写Qt5代码时使用这种关联形式。


信号和槽还有一种自动关联方式,例如在设计模式直接生成的按钮的单击信号的槽,就是使用的这种方式: on_pushButton_clicked() 由字符串on、部件的objectName和信号名称这三部分组成,中间用下划线隔开。这样形式命名的槽可以直接和信号关联,不用再使用connect()函数,不过使用这种方式还要进行其他设置。

五、断开信号和槽的关联

可以通过disconnect()函数来断开信号和槽的关联,其原型如下:

static bool QObject::disconnect(const QObject *sender, const char *signal,
const QObject *receiver, const char *member);

(1)断开与一个发送对象所有信号的所有关联

disconnect(myObject, nullptr, nullptr, nullptr);

等价于

myObject->disconnect();

(2)断开与一个指定信号的所有关联

disconnect(myObject, SIGNAL(mySignal()), nullptr, nullptr);

等价于

myObject->disconnect(SIGNAL(mySignal()));

(3)断开与一个指定接受对象的所有关联

disconnect(myObject, nullptr, myReceiver, nullptr);

等价于

myObject->disconnect(myReceiver);

(4)断开一个指定信号和槽的关联

disconnect(myObject, SIGNAL(mySignal()), myReceiver, SLOT(mySlot()));

等价于

myObject->disconnect(SIGNAL(mySignal()), myReceiver, SLOT(mySlot()));

也等价于

disconnect(myConnection); //myConnection是进行关联时connect()的返回值

其用法类似,只是其信号、槽参数需要使用函数指针 &MyObject::mySignal()、&MyReceiver::mySlot() 等形式。这个函数并不能断开信号与一般函数或者lambda表达式之间的关联,如果有这方面需要,则可以使用connect()返回值进行断开。实际上当对象被delete时,其关联的所有链接都会失效,QT会自动移除和这个对象的所有链接。

六、信号和槽的更多用法

(1)一个信号可以连接多个槽

使用QObject::connect可以把一个信号连接到多个槽,而当信号发射时,将按声明联系时的顺序依次调用槽。

MyStr  a;
MyStr b;
MyStr c;
//信号连接到两个槽
QObject::connect(&a,SIGNAL(valueChanged(QString)),&b,SLOT(setValue(QString)));
QObject::connect(&a,SIGNAL(valueChanged(QString)),&c,SLOT(setValue(QString)));
a.setValue("this is A");
//依次调用b.setValue()、c.setValue()

(2)多个信号可以连接同一个槽

同样的,可以让多个信号连接到同一个槽上 ,而且其中的每一个信号的发送,都会调用了那个槽。

MyStr  a;
MyStr b;
MyStr c;
//两个信号连接到同一个槽
QObject::connect(&a,SIGNAL(valueChanged(QString)),&c,SLOT(setValue(QString)));
QObject::connect(&b,SIGNAL(valueChanged(QString)),&c,SLOT(setValue(QString)));
//下面的操作皆会调用到槽c.setValue()
a.setValue("this is A");
b.setValue("this is B");

(3)一个信号可以和另外一个信号相连接

当发射第一个信号的时候,也会把第二个信号一个发送出去。

MyStr  a;
MyStr b;
MyStr c;
//两个信号相连接
QObject::connect(&a,SIGNAL(valueChanged(QString)),&b,SIGNAL(valueChanged(QString)));
//再建立b与c的连接
QObject::connect(&b,SIGNAL(valueChanged(QString)),&c,SLOT(setValue(QString)));
//下面的操作同时发送了信号a.valueChanged与b.valueChanged
a.setValue("this is A");
//从而信号b.valueChanged被槽c.setValue所接收

七、扩展:Qt对象模型概述

标准C+ +对象模型可以在运行时非常有效地支持对象范式(object paradigm), 但是它的静态特性在一些问题领域中不够灵活。图形用户界面编程不仅需要运行时的髙效性,还需要高度的灵活性。为此,Qt在标准C+ +对象模型的基础上添加了一些特性,形成了自己的对象模型。这些特性有:

  • 一个强大的无缝对象通信机制 ---- 信号和槽(signals and slots);
  • 可查询和可设计的对象属性系统(object properties);
  • 强大的事件和事件过滤器(events and event filters);
  • 通过上下文进行国际化的字符串翻译机制(string translation for international­ization) ;
  • 完善的定时器(timers)驱动,使得可以在一个事件驱动的GUI中处理多个任务;
  • 分层结构的、可査询的对象树(object trees),它使用一种很自然的方式来组织对象拥有权(object ownership);
  • 守卫指针即QPointer,它在引用对象被销毁时自动将其设置为0;
  • 动态的对象转换机制(dynamic cast)。

Qt的这些特性都是在遵循标准C+ +规范内实现的,使用这些特性都必须继承自 QObject类。其中对象通信机制和动态属性系统还需要元对象系统(Meta-Object Sys­tem) 的支持。关于对象模型,可以在帮助中查看 Object Model 关键字。

参考:

C++框架_之Qt的信号和槽的详解

QT信号槽机制

Qt对象模型之一:信号和槽的更多相关文章

  1. Qt学习之信号与槽(一)

    Qt学习之信号与槽(一) 目录 QT的信号与槽机制 在窗口的UI设计中操作添加信号和槽     QT的信号与槽机制   QT的两种机制 在Qt和PyQt中有两种通信机制: 低级事件处理机制(low-l ...

  2. Qt入门之信号与槽机制

    一. 简介 就我个人来理解,信号槽机制与Windows下消息机制类似,消息机制是基于回调函数,Qt中用信号与槽来代替函数指针,使程序更安全简洁. 信号和槽机制是 Qt 的核心机制,可以让编程人员将互不 ...

  3. Qt中使用信号和槽的一点心得

    信号(Signal)与槽(Slot)-Qt中的典型机制 这一篇文章中都说得很详细了,这里不再重复,只说一点在实际使用中可能会遇到的问题. 1.一个信号不要同时连接几个槽函数,不然执行的顺序是随机的,最 ...

  4. Qt编程之信号与槽-------unresolved external symbol "public: virtual struct QMetaObject const * __thiscall XX::metaObject(void)const

    原因是加入Q_OBJECT这个macro的类,被编译的时候就要用到moc这个命令,所以在VS2010中,没有加入此命令的应用,当然会出错了.所以解决办法是加,或者如果你不使用信号槽可以直接删除. 当要 ...

  5. qt学习:信号,槽

    [C.GUI.Qt.4编程(第二版)](加)布兰切特,(英)萨默菲尔德.扫描版[学习库www.xuexi111.com].pdf 信号和槽是Qt编程的基础,它可以让各种对象联系在一起. 1. 使用co ...

  6. Qt 5中信号和槽的新语法

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

  7. 【QT学习】信号和槽机制

    QT中发射信号使用 emit 来完成. 类要使用信号和槽机制,必须从QObject或者QObject子类继承.必须在类的定义中加上 Q_OBJECT 宏. 信号和槽连接时,参数类型必须相同. 关键字大 ...

  8. Qt学习笔记----信号与槽实现的四种方式

    1.以按钮为例,首先添加一个按钮,对象名为pushButton,在按钮是右键单击选择“转到槽”即可,在转到槽选择面板(右图)里面选择需要进行的操作,比如单击clicked() 2.选择菜单“编辑”,找 ...

  9. Qt之自定义信号和槽函数

    自定义信号和槽函数: 1.类的声明和实现分别放在.h和.cpp文件中: 2.类声明包含Q_OBJECT宏: 3.信号只要声明不要设计其的实现函数 4.发射信号用emit关键字 5.自定义槽的实现与普通 ...

  10. Qt跨线程信号和槽的连接(默认方式是直连和队列的折中)

    Qt支持三种类型的信号-槽连接:1,直接连接,当signal发射时,slot立即调用.此slot在发射signal的那个线程中被执行(不一定是接收对象生存的那个线程)2,队列连接,当控制权回到对象属于 ...

随机推荐

  1. Maven查看依赖树

    1.命令行,但是只能查看一层的引用 mvn dependency:tree 如图所示: 2.使用Eclipse的Dependency Hierarchy查看,这个就比较深层次的查看,如图所示: 3.使 ...

  2. android手机rootROM下载地址

    https://download.mokeedev.com/ https://download.lineageos.org/

  3. Office WORD如何为每一页设置不同的页眉页脚

    如下图所示,我想要为封面和目录,摘要等等设置不同的页眉页脚(一般封面和目录不需要页脚)   而从正文开始,套用相同的页眉和以页数作为页脚(注意"第一章 绪论"不是这个文档的第一页) ...

  4. Input系统—ANR原理分析(转)

    一. 概述 当input事件处理得慢就会触发ANR,那ANR内部原理是什么,哪些场景会产生ANR呢. “工欲善其事必先利其器”,为了理解input ANR原理,前面几篇文章疏通了整个input框架的处 ...

  5. PHPCMS中GET标签概述、 get 标签语法、get 标签创建工具、get 调用本系统演示样例、get 调用其它系统演示样例

    一.get 标签概述 通俗来讲,get 标签是Phpcms定义的能直接调用数据库里面内容的简单化.友好化代码,她可调用本系统和外部数据,仅仅有你对SQL有一定的了解,她就是你的绝世好剑!也就是适合熟悉 ...

  6. 排队理论之性能分析 - Little Law &amp; Utilization Law

    了解一个系统的性能一般是參考一些度量值(Metric),而怎样计算出这些Metric就是我们要讨论的.Little Law(排队理论:利特儿法则)和Utilization Law是Performanc ...

  7. mac WebStorm 破解

    摘要:因为想要学习HTML所以需要一个工具,同事推荐了webstorm.下载以后再网上搜破解方法.搜索到一个很简单的. 一.下载链接https://www.jetbrains.com/webstorm ...

  8. VMnet1和VMnet8 未识别的网络的解决方法

    我的系统是win7 64位,它居然不能识别VMnet1和VMnet8,在网上找了些资料,发现所有资料都是一样的.不过事实证明是正确的. 解决办法: 1,在运行中输入regedit 2,进入注册表[HK ...

  9. Android Studio集成Genymotion 及Genymotion 配置ADB

    1.打开 Android Studio,依次[File]-[Settings],快捷键  Ctrl + Alt + S 2.在打开的 settings 界面里找到 plugins 设置项,点击右侧的“ ...

  10. forword 与 redirect

    直接转发方式(Forward) 客户端和浏览器只发出一次请求,Servlet.HTML.JSP或其它信息资源,由第二个信息资源响应该请求,在请求对象request中,保存的对象对于每个信息资源是共享的 ...