一个信号对槽的调用: 会比直接函数调用耗费更多的时间/空间;
但是,信号与槽实现了对象之间的松耦合. 使用灵活,方便。比较符合人的思维方式。
这就是以易用性来换取时间或空间。
下面实例讲解QT signal, slot, connect, emit 的含义。
先上代码: 最简单代码,我不喜欢复杂的。两个类,一个A, 一个B, 声明两个对象,一个a,一个b. a 发信号调用b的槽函数。
$ cat a.h
#include <QObject>
class A : public QObject
{
Q_OBJECT
public:
A(QObject* parent = 0){(void)parent;} // 空构造
void aFunc(); // 在这个函数里发射信号
signals :
void SendString(QString msg);
void SendInt(int i);
};
$ cat a.cpp
#include "a.h"
void A::aFunc()
{
emit SendString("this is a str");
emit SendInt(8);
}
B 类声明和实现
cat b.h
#include <QObject>
class B : public QObject
{
Q_OBJECT
public:
B(QObject* parent = 0){(void)parent;};
public slots:
void ReceiveString(QString msg);
void ReceiveOtherSlot(float f);
void ReceiveInt(int i);
};
$ cat b.cpp
#include <QDebug>
#include "b.h"
void B::ReceiveString(QString msg)
{
qDebug() << msg <<endl;
}
void B::ReceiveOtherSlot(float f)
{
qDebug() << f <<endl;
}
void B::ReceiveInt(int i)
{
qDebug() << i <<endl;
}
测试程序:
$ cat main.cpp
#include <QApplication>
#include "a.h"
#include "b.h"
int main(int argc, char *argv[])
{
QApplication app(argc, argv);
A a;
B b;
QObject::connect(&a, SIGNAL(SendString(QString)), &b, SLOT(ReceiveString(QString)));
QObject::connect(&a, SIGNAL(SendInt(int)), &b, SLOT(ReceiveInt(int)));
a.aFunc(); // 在这个函数中发射信号
return app.exec();
}
解释一下几个概念:
signals 是什么?
signals 是一系列信号,
实际上是一个函数原型声明,但是没有函数实现。
signals 无需声明公有,私有属性.
实际上,函数实现在其它对象中,什么样子不关心。
发射一个信号,就是调用一个或多个其它对象中的函数。
到底哪些函数被调用,是由QObject::connect() 所决定
slots 是什么?
slots 是一系列槽函数. 槽函数与普通函数没有区别,
只是它可以接受信号,可以被其它对象所调用。
slots 具有public,private,protect 属性,决定其可连接属性
connect 是什么?
connect 是QObject 一个函数,用以登记两个对象的连接属性,它使用的SIGNAL 是一个宏
用于将后面的函数变换为字符串, SLOT 也是一个宏,用于将后面函数变换为字符串,
使得QOjbect可以登记 两个函数名称。 将一个对象的信号于另一个对象的槽函数联系起来。
注意: connect 连接的信号和槽是函数原型, 函数参数不用带形参名称,否则连接不上,会提示无此信号或槽。
signal 的使用
----------------------------------------
emit SendInt(8);
----------------------------------------
1. emit 是什么?, emit 是个宏,空的宏, #define emit ""
2. signal 如何调用到slot 函数
用gdb 调试这个程序. 我们在 B::ReceiveInt 设置断点, 断下后:
(gdb) bt
#0 B::ReceiveInt (this=0x7fffffffdc80, i=8) at b.cpp:15
#1 0x0000000000402575 in B::qt_static_metacall (_o=0x7fffffffdc80, _c=QMetaObject::InvokeMetaMethod, _id=2, _a=0x7fffffffdbf0) at moc_b.cpp:53
#2 0x00007ffff6a31fdf in QMetaObject::activate (sender=0x7fffffffdc70, m=0x402840 <A::staticMetaObject>, local_signal_index=1, argv=0x7fffffffdbf0) at kernel/qobject.cpp:3539
#3 0x00000000004023f4 in A::SendInt (this=0x7fffffffdc70, _t1=8) at moc_a.cpp:106
#4 0x00000000004018bc in A::aFunc (this=0x7fffffffdc70) at a.cpp:5
从底向上分析:
1. 在a.cpp:5, 调用了
emit SendInt(8); 对应于:
A::SendInt (this=0x7fffffffdc70, _t1=8) , 这实际上是moc_a.cpp 中的一个函数,属于A 类
2. 在 moc_a.cpp:106, 调用了
QMetaObject::activate(this, &staticMetaObject, 1, _a); 对应于: kernel/qobject.cpp:3539
QMetaObject::activate (sender=0x7fffffffdc70, m=0x402840 <A::staticMetaObject>, local_signal_index=1, argv=0x7fffffffdbf0)
该cpp 文件定义了QObject, QMetaObject 类的几个成员函数。函数原型
void QMetaObject::activate(QObject *sender, const QMetaObject *m, int local_signal_index, void **argv)
可以看到sender 是a, QMetaObject 是A::staticMetaObject, local_signal_index 是1,并带有参数传给了该函数。
3. 在 kernel/qobject.cpp:3539, 调用了
callFunction(receiver, QMetaObject::InvokeMetaMethod, method_relative, argv ? argv : empty_argv);
对应于
B::qt_static_metacall (_o=0x7fffffffdc80, _c=QMetaObject::InvokeMetaMethod, _id=2, _a=0x7fffffffdbf0)
函数原型:void B::qt_static_metacall(QObject *_o, QMetaObject::Call _c, int _id, void **_a)
可见,QMetaObject 类负责寻径,找到receiver 对象为 b, 类型QMetaObject::InvokeMetaMethod, 接受_id 为2, 带参数.
它是怎么找的,当然是QObject::connect 注册过的。
这个被调用函数, 在moc_b.cpp 中。
4. 在moc_b.cpp:53 调用了
case 2: _t->ReceiveInt((*reinterpret_cast< int(*)>(_a[1]))); break;
对应于
B::ReceiveInt (this=0x7fffffffdc80, i=8)
函数原型: void B::ReceiveInt(int i)
可见,该函数通过参数_id=2, 找到了正确的槽函数。并传递参数调用槽函数。
简单梳理一下: 对象a(signal)->moc_a->qobject->moc_b->b(SLOT).对象b(slots)
两边是moc辅助函数, 中间夹着qobject类, qobject 根据注册的connect 函数负责send, receive的钩挂和寻径