信号槽是Qt的核心通信机制,类似于一个发送天线,向四面八方发送信号,任何人都可能接收到;槽函数类似于一个收音机,只有该收音机将广播调制到特定的频率上才能接收到对应的广播,频率就是信号槽的连接。当某个特定的对象发送信号时,与之关联的某个槽函数就会被执行。

信号槽原理

Qt的信号signals是一个特殊的宏,而槽函数则和普通的函数没有什么区别。qmake会在编译器之前先行执行,将signals宏展开为C++函数,然后将信号和槽的连接生成为一个带有moc_前缀的cpp文件,里面产生C++代码被包含进行项目中提供给编译器编译。

textChanged信号在moc文件中被变形为:

// SIGNAL 0
int Widget::textChanged(const QString & _t1)
{int _t0 = int();void *_a[] = { const_cast<void*>(reinterpret_cast<const void*>(&_t0)), const_cast<void*>(reinterpret_cast<const void*>(&_t1)) };QMetaObject::activate(this, &staticMetaObject, 0, _a);return _t0;
}

其实signals就是一函数,不过在调用QMetaObject::activate(this, &staticMetaObject, 0, _a);之后转到对应的槽函数中去执行罢了。

connect函数在moc文件中被变形为:

void Widget::qt_static_metacall(QObject *_o, QMetaObject::Call _c, int _id, void **_a)
{if (_c == QMetaObject::InvokeMetaMethod) {Widget *_t = static_cast<Widget *>(_o);Q_UNUSED(_t)switch (_id) {case 0: { int _r = _t->textChanged((*reinterpret_cast< const QString(*)>(_a[1])));if (_a[0]) *reinterpret_cast< int*>(_a[0]) = _r; }  break;case 1: { int _r = _t->textChanged((*reinterpret_cast< const myclass(*)>(_a[1])));if (_a[0]) *reinterpret_cast< int*>(_a[0]) = _r; }  break;case 2: _t->on_pushButton_clicked(); break;case 3: _t->onBtnClicked(); break;case 4: _t->onTextChanged((*reinterpret_cast< const QString(*)>(_a[1]))); break;case 5: _t->onTextChanged((*reinterpret_cast< const myclass(*)>(_a[1]))); break;default: ;}} else if (_c == QMetaObject::IndexOfMethod) {int *result = reinterpret_cast<int *>(_a[0]);void **func = reinterpret_cast<void **>(_a[1]);{typedef int (Widget::*_t)(const QString & );if (*reinterpret_cast<_t *>(func) == static_cast<_t>(&Widget::textChanged)) {*result = 0;return;}}{typedef int (Widget::*_t)(const myclass & );if (*reinterpret_cast<_t *>(func) == static_cast<_t>(&Widget::textChanged)) {*result = 1;return;}}}
}

这里已经明确的告诉编译器那个信号要调用什么样的槽函数了。

信号槽的连接形式

信号和槽的连接有多种方式,这里只列举最常见的4种形式:

  1. 信号->槽函数,原型:connect(sender, signal, receiver, slot)
    connect(btn, SIGNAL(clicked(bool)), this, SLOT(onBtnClicked()));
    当对象btn发送clicked信号后,this对象的onBtnClicked()函数被执行。

  2. 信号->信号,原型:connect(sender, signal, receiver, signal)
    connect(edit, SIGNAL(textChanged(QString)), this, SIGNAL(textChanged(QString)));
    当edit对象发送textChanged信号后,this对象的textChanged()函数被执行。

  3. 多个信号->同一个槽函数
    connect(btn, SIGNAL(clicked(bool)), this, SLOT(onBtnClicked()));
    connect(edit, SIGNAL(editingFinished()), this, SLOT(onBtnClicked()));
    connect(this, SIGNAL(windowTitleChanged(QString)), this, SLOT(onBtnClicked()));
    无论是btn, edit还是this的信号发送,都会调用this的onBtnClicked()函数。

  4. 一个信号->多个槽函数
    connect(edit, SIGNAL(textChanged(QString)), this, SLOT(onTextChanged(QString)));
    connect(edit, SIGNAL(textChanged(QString)), this, SLOT(raise()));
    一个信号还可以出发多个槽函数被调用,不过他们的执行先后顺序就不确定了。

信号槽的调用方式

查阅connect()函数的帮助文档,发现在最后还有一个参数Qt::ConnectionType,那么这个参数起到什么作用呢?它有很重要的作用,它决定了信号和槽函数在调用时将采取什么样的方式。
它定义了4中连接形式:
1. AutoConnection
自动模式,也是默认的选项。根据Qt的描述,如果信号发送对象和接收对象在同一个线程,那么实际使用的是DirectConnection(直连)模式,如果它们在不同的线程,使用的就是QueuedConnection(队列)模式。这两个模式的意义请继续看下面的讲解。
2. DirectConnection
直连模式,言下之意就是直接调用。当信号发出后,将立即执行槽函数,并且等待槽函数执行完毕后才返回。当发送和接收对象都处于同一个线程内部时使用这种方式。
3. QueuedConnection
队列模式主要用在线程间信号槽传递。当发送和接收处于同一线程时,他们处于一个事件循环中,发送和接收函数的执行都是按照事件循环的顺序执行;而当他们处于不同线程时,发送对象并不知晓接收方所在的线程处于事件循环的什么状态,因此信号被传递进对方线程的信号队列,等待对方线程处理,同时发送信号立即结束返回。他们的执行是异步进行的。
4. BlockingQueuedConnection
有时候虽然他们不在同一个线程,但是发送方也希望等槽函数执行完毕后才返回,因此这个参数就是为了这个目的。值得注意的时使用这个参数必须是发送和接收对象不在一个线程,否则可能会引起死锁。
5. UniqueConnection
这个是一个组合规则,一般很少使用。

当我们使用QueuedConnection模式进行信号槽连接时,必须要确保信号槽的传递参数是 Meta-Object 系统所能识别的类型,如果要传递我们自己定义的类型呢?

信号槽的参数传递

我们知道槽其实就是一个函数,它可以像普通函数一样有返回值,有各种各样的参数。在Qt中,使用 Meta-Object 来表示connect传递的参数。Meta-Object 是什么东西?它是一种能被Qt所识别的类型,比如说int,float,bool等,还有QString,QSize,QRect,QFont等Qt所定义的类型。我们自己创造的class类型是不被Meta-Object系统识别的,那如果要传递我们自己定义的类型时该如何办呢?不用担心,Qt为我们提供了一种注册机制,可以将我们自己的类型注册为Meta-Object类型。

下面是我的自定义类:

class myclass
{
public:~myclass(){}myclass(){}myclass(const myclass& other){if (&other != this) {x = other.x;y = other.y;}}int x;int y;
};

要让Qt识别自己的类型,class必须要提供拷贝构造函数,因为在线程间拷贝的时候会用到拷贝构造函数,如果你不能保证使用系统提供的默认拷贝构造函数能得到正确结果,就必须实现自己的拷贝构造函数。

现在有这样一个信号定义:textChanged(const myclass& data);
还有这样一个槽函数定义:void onTextChanged(const myclass& data);
他们都使用自定义的class类型作为信号槽的传递参数,在使用connect函数之前,我们使用Qt提供的qRegisterMetaType(“myclass”);函数注册该class为Meta-Object类型。然后使用connect连接信号槽,这样我们就可以得到正确的调用。

或许你会发现一个问题,即使不使用qRegisterMetaType函数注册,一样能得到正确结果。那是因为在前面已经说过的问题,在同一个线程中,使用信号槽的默认调用方式是DirectConnection模式,在这种情况下无须注册都可以使用,但当你使用QueuedConnection模式连接时,你就会得到一个错误提示:

QObject::connect: Cannot queue arguments of type 'myclass'
(Make sure 'myclass' is registered using qRegisterMetaType().)

编译器告诉你在使用队列模式时必须要注册Meta-System类型。

参数传递的效率

我们知道一般参数传递分值传递和引用传递,当然也可以说还有指针传递。那么在信号槽中使用不同的参数传递效率会如何呢?
我现在将myclass类修改一下,使其可以在构造和析构的时候打印些字符,这样我就能知道该对象呗创建和拷贝了多少次。

修改后的myclass大致是这样:

class myclass
{
public:~myclass(){ qDebug() << "~myclass()"; }myclass(){ qDebug() << "myclass()"; }myclass(const myclass& other){if (&other != this) {x = other.x;y = other.y;}qDebug() << "myclass(const myclass& other)";}int x;int y;
};

分别调用不同的参数传递方式打印出来的结果如下:

// 值传递
myclass()
myclass(const myclass& other)
myclass(const myclass& other)
~myclass()
~myclass()
myclass(const myclass& other)
x= 10 , y= 5
~myclass()
~myclass()
// 引用传递
myclass()
myclass(const myclass& other)
~myclass()
x= 10 , y= 5
~myclass()
// 指针传递
myclass()
x= 10 , y= 5
~myclass()
参数传递方式 默认构造函数调用次数 拷贝构造函数调用次数
值传递 1次 3次
引用传递 1次 1次
指针传递 1次

!!
其结果不言而喻,指针的效率是最高的,值传递时拷贝构造函数竟然被调用了3次,分别在emit发送信号时,发送到线程循环等待时,进入槽函数作形式参数时。而引用只在进入线程循环等待时拷贝了一次,这也就说明了为什么使用引用时对象在emit之后被释放了,但槽函数却还是能得到正确的结果,因为线程循环等待时即使是引用传递也会进行对象拷贝的。

Qt编程入门(1) : 信号和槽相关推荐

  1. Qt编程中的信号和槽机制

    Qt编程中的信号和槽机制 在使用自定义类创建一个按钮之后,只能看到一个按钮的图形,但是使用鼠标点击并无任何反应,下面想要实现一个"点击按钮可以关闭窗口"的功能. 关闭窗口的功能可以 ...

  2. Qt图形界面编程入门(信号和槽通信机制)

    信号和槽机制是Qt的核心机制,可以让编程人员将互不相关的对象绑定在一起,实现对象之间的通信. 声明了信号的对象,当其状态改变时,信号就由该对象发送出去,而且该对象只负责发送信号,它不知道另一端是谁在接 ...

  3. 【QT编程】QT对象间通讯——信号与槽

    01.目录 文章目录 01.目录 02.信号与槽介绍 03.信号与槽机制 04.信号(Signal) 05.槽函数(Slot) 06.一个示例 07.一个真实的示例 08.信号和槽使用默认参数 09. ...

  4. Qt图形界面编程入门(标签与槽机制习题分享)

    标签对象初始显示0,每次单击标签对象后,其显示内容就加1,依次变为1.2.3等. #ifndef DIALOG_H #define DIALOG_H#include <QDialog> # ...

  5. Python Qt GUI设计:信号与槽的使用方法(基础篇—7)

    目录 1.信号与槽的概念 2.信号与槽的基础函数 2.1.创建信号函数 2.2.连接信号函数 2.3.断开信号函数 2.4.发射信号函数 3.信号和槽的使用方法 3.1.内置信号与槽的使用 3.2.自 ...

  6. Qt多线程中的信号与槽

    文章目录 1 多线程中的信号与槽 2 对象的依附性 2.1 对象的依附性 2.2 开启线程事件循环 2.3 线程事件循环的结束 2.4 设计实例 3 信号与槽的连接方式 3.1 Qt::DirectC ...

  7. QT学习小结之信号与槽

    信号与槽函数是我们学习QT必备的基础知识,今天跟大家分享我学习QT的一些总结吧. 信号槽机制是Qt编程的基础.通过信号槽,能够使Qt各组件在不知道对方的情形下能够相互通讯. 槽函数和普通的C++成员函 ...

  8. Qt静态函数中的信号和槽问题

    目录 介绍 Qt中的信号和槽 一般形式 当发送信号的地方为静态函数时 存在问题 解决方案 介绍 信号和插槽用于对象之间的通信.信号和插槽机制是Qt的一个核心特性,可能是与其他框架提供的特性最为不同的部 ...

  9. Qt编程入门(2) : 窗口和布局(2)

    窗口和布局是Qt主要的组成部分,Qt为此封装了至少几百个Class,要全部掌握这些Class是不现实的,也是不明智的.本章重点介绍几个常用的窗口和布局管理器的基本用法,只帮助起到一个引领入门的作用.对 ...

  10. PyQt入门(2)-信号和槽

    信号和槽用于对象之间的通信. 信号和槽机制是 Qt 的核心特性,可能也是与其他框架最不同的地方.Qt 的元对象系统使信号和槽成为可能. 在 GUI 编程中,当我们更改一个 widget 时,我们通常希 ...

最新文章

  1. 小牛生产小牛的问题解决集粹
  2. 【转】java线程系列---Runnable和Thread的区别
  3. 阿里巴巴Json工具:Fastjson教程
  4. 理论+实操: linux中firewalld防火墙基础讲解(转载)
  5. php中的关联数组,PHP中的多种关联数组
  6. linux shell 命令记录,linux常用shell命令记录
  7. 如何做一份能忽悠投资人的PPT
  8. php常用库函数(二)
  9. c 打印二叉树_二叉树遍历(非递归和递归实现)
  10. php rsa加密demo,php实现RSA加密类实例_PHP
  11. JS - javascript容错处理代码
  12. c语言中头文件iostream,程序中为什么要包含头文件iostream.h?
  13. 数据结构~~二叉树和BSTs(三)(转)
  14. django 文档参考模型
  15. u盘安装红旗linux操作系统,如何用u盘安装红旗linux?
  16. ChromeFK插件推荐系列十九:网站图片批量下载插件推荐
  17. 【力扣】77. 组合
  18. yolov5 tensorrt 精度对齐总结
  19. 蜘蛛池泛目录seo必备站长源码
  20. iconfont图标无法显示的问题

热门文章

  1. Faster RCNN (pytorch)(转载)
  2. acegis连接使用方法_arcgis工具使用方法
  3. TcpSocket的Qt串口实现与QtSocket接收数据不完整处理方法
  4. node创建项目步骤
  5. 鸿蒙系统下载地址_华为鸿蒙系统下载
  6. duilib在同一个界面中(xml)加载两种自定义的list
  7. SQL Server 2008空间数据应用系列十一:提取MapInfo地图数据中的空间数据解决方案...
  8. 不同行业的软件都爱用什么编程语言开发?
  9. 微信小程序游戏——飞机大战
  10. 163编辑器学习笔记