玉溪网站制作,汽车工厂网站建设,甲蛙网站建设,天津力天装饰有限公司Qt之信号槽原理
一.概述
所谓信号槽#xff0c;实际就是观察者模式。当某个事件发生之后#xff0c;比如#xff0c;按钮检测到自己被点击了一下#xff0c;它就会发出一个信号#xff08;signal#xff09;。这种发出是没有目的的#xff0c;类似广播。如果有对象对这…Qt之信号槽原理
一.概述
所谓信号槽实际就是观察者模式。当某个事件发生之后比如按钮检测到自己被点击了一下它就会发出一个信号signal。这种发出是没有目的的类似广播。如果有对象对这个信号感兴趣它就会使用连接connect函数意思是将想要处理的信号和自己的一个函数称为槽slot绑定来处理这个信号。也就是说当信号发出时被连接的槽函数会自动被回调。这就类似观察者模式当发生了感兴趣的事件某一个操作就会被自动触发。这里提一句Qt 的信号槽使用了额外的处理来实现 并不是 GoF 经典的观察者模式的实现方式。
信号和槽是Qt特有的信息传输机制是Qt设计程序的重要基础它可以让互不干扰的对象建立一种联系 Qt信号槽有如下优点:
1.类型安全。需要关联的信号槽的签名必须是等同的。即信号的参数类型和参数个数同接受该信号的槽的参数类型和参数个数相同。若信号和槽签名不一致编译器会报错。
2.松散耦合。信号和槽机制减弱了Qt对象的耦合度。激发信号的Qt对象无需知道是那个对象的那个信号槽接收它发出的信号它只需在适当的时间发送适当的信号即可而不需要关心是否被接受和那个对象接受了。Qt就保证了适当的槽得到了调用即使关联的对象在运行时被删除。程序也不会奔溃。
3.灵活性。一个信号可以关联多个槽或多个信号关联同一个槽
二.信号槽的实现
众所周知C语言的编译过程为预处理-编译-汇编-链接。
a.驱动程序首先运行C预处理器(cpp),将源程序翻译成一个ASCII码的中间文件main.i,b.驱动程序运行C编译器,将main.i翻译成一个ASCII汇编文件main.sc.驱动程序运行汇编器(as)将main.s翻译成一个可重定位目标文件main.od.运行链接器将main.o及其一些必要的系统目标文件组合在一起创建一个可执行的目标文件但是在qt中首先会有一个Moc预处理器对源代码进行处理经过Moc预处理器处理后的代码才是标准的C代码此后就可以执行正常的C编译流程了。正是因为Moc预处理器Qt才实现了信号槽的功能下面我们通过用纯C代码实现一个简洁的信号槽功能来对信号槽深入了解。
使用C语言模拟信号槽实现
首先看一下类图
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-fqssygsn-1683775621087)(C:\Users\wangning54\AppData\Roaming\Typora\typora-user-images\image-20230418170931282.png)]
QObject有一个静态元对象QMetaObject静态元对象存放着信号槽名称的字符信息以及一个根据信号发送者和信号index调用对应槽的函数
QObject有一个容器connections此容器是一个mapkey是信号indexvalue是一个Connection维护者信号槽的对应关系
QObject有一个静态方法connect此方法将信号的index作为key创建一个value插入到对象维护的连接列表容器中
程序运行时connect借助两个字符串即可将信号与槽的关联建立起来那么它是如果做到的呢C的经验可以告诉我们 类中应该保存有信号和槽的字符串信息字符串和信号槽函数要关联
引入元对象系统
定义信号和槽为了和普通成员进行区分以使得预处理器可以提取信息定义几个关键字。
#define slots
#define signals protected
#define emit 通过预处理器将信息提取取来放置到一个单独的文件中比如moc_QObject.cpp: 规则很简单将信号和槽的名字提取出来放到字符串中。可以有多个信号或槽按顺序sig1/nsig2/n static const char sig_names[] sig1/nsing2/n;static const char slts_names[] slot1/nslot2/n;利用这些信号槽的信息建立连接
定义一个结构体存放信息
struct QMetaObject
{const char * sig_names;const char * slts_names;
};然后将它作为QObject的一个静态对象这个就是Qt中的元对象。
class QObject
{static QMetaObject staticMetaObject;...利用预处理器生成的moc_Object.cpp
#include object.hstatic const char sig_names[] sig1/n;
static const char slts_names[] slot1/n;
QMetaObject QObject::staticMetaObject {sig_names, slts_names};建立信号槽连接
利用moc预处理器保存的信息通过 connect 将信号和槽的对应关系保存到一个 mutlimap中。
struct Connection
{Object * receiver;int method;
};class QObject
{
public:
...static void connect(QObject*, const char*, QObject*, const char*);
...
private:std::multimapint, Connection connections;connect函数
void QObject::connect(QObject* sender, const char* sig, QObject* receiver, const char* slt)
{int sig_idx find_string(sender-meta.sig_names, sig);int slt_idx find_string(receiver-meta.slts_names, slt);if (sig_idx -1 || slt_idx -1) {perror(signal or slot not found!);} else {Connection c {receiver, slt_idx};sender-connections.insert(std::pairint, Connection(sig_idx, c));}
}首先从元对象信息中查找信号和槽的名字是否存在如果存在则将信号的索引和接收者的信息存入信号发送者的一个map中。
信号的激活
在Qt中我们都是使用emit来激活一个信号这是emit的本来面目。
#define emit在Qt中我们必须要在类里增加一个Q_OBJECT的宏发送信号使用emit定义槽和信号使用signals,public slots等。其实这些都是一些宏替换在qobjects.h文件里可以看到这些宏的本来面目。
我们这里使用emit来激活信号。我们在定义信号的时候只写了信号的声明信号的实现并未给出。而槽函数的实现是开发者给出的。其实信号的实现是Moc编译器帮助我们实现的。
void QObject::sig1()
{QMetaObject::active(this, 0);
}信号的调用工作由QMetaObject类来完成
class QObject;
struct QMetaObject
{const char * sig_names;const char * slts_names;static void active(QObject * sender, int idx);};这个函数该怎么写呢思路很简单
从前面的保存连接的map中找出与该信号关联的对象和槽 调用该对象这个槽
typedef std::multimapint, Connection ConnectionMap;
typedef std::multimapint, Connection::iterator ConnectionMapIt;void QMetaObject::active(QObject* sender, int idx)
{ConnectionMapIt it;std::pairConnectionMapIt, ConnectionMapIt ret;ret sender-connections.equal_range(idx);for (itret.first; it!ret.second; it) {Connection c (*it).second;//c.receiver-metacall(c.method);}
}槽的调用:
这个最后一个关键问题了槽函数如何根据一个索引值进行调用。
直接调用槽函数我们都知道了就一个普通函数可现在通过索引调用了那么我们必须定义一个接口函数
class QObject
{void metacall(int idx);
...该函数如何实现呢这个又回到我们的元对象预处理过程中了因为在预处理的过程我们能将槽的索引和槽的调用关联起来。
所以在预处理生成的文件(moc_QObject.cpp)中我们很容易生成其定义
void QObject::qt_static_metacall(int idx)
{switch (idx){case 0:{slot1();break;}case 1:{slot2();break;}};
}总结moc通过元对象系统保存了信号和槽的字符信息然后为每一个信号和槽分配编号。当我们调用connect函数时把信号槽的对应关系保存在对象的一个map容器中。当发送信号时(也就是调用信号函数时)通过刚才保存在map容器中的信号槽对应关系找到对应的接收对象和槽函数。
完整代码:
#include string.h
#include QObject.h
#include iostreamstatic int find_string(const char * str, const char * substr)
{if (strlen(str) strlen(substr))return -1;int idx 0;int len strlen(substr);bool start true;const char * pos str;char cEnd \n;while (*pos) {if (start !strncmp(pos, substr, len) pos[len] \n)return idx;start false;if (*pos cEnd) {idx;start true;}pos;}return -1;
}void QObject::connect(QObject* sender, const char* sig, QObject* receiver, const char* slt)
{int sig_idx find_string(sender-staticMetaObject.sig_names, sig);int slt_idx find_string(receiver-staticMetaObject.slts_names, slt);if (sig_idx -1 || slt_idx -1) {perror(signal or slot not found!);}else {Connection c { receiver, slt_idx };sender-connections.insert(std::pairint, Connection(sig_idx, c));}
}void QObject::slot1()
{std::cout into slot1 std::endl;
}void QObject::slot2()
{std::cout into slot2 std::endl;
}
/*
从前面的保存连接的map中找出与
该信号关联的对象和槽调用该对象这个槽
*/
void QMetaObject::active(QObject* sender, int idx)
{ConnectionMapIt it;std::pairConnectionMapIt, ConnectionMapIt ret;ret sender-connections.equal_range(idx);for (it ret.first; it ! ret.second; it) {Connection c (*it).second;c.receiver-qt_static_metacall(c.method);}
}void QObject::testSignal()
{emit sig2();emit sig1();
}#pragma once#ifndef Q_OBJECT_H
#define Q_OBJECT_H
#include map # define slots
# define signals protected
# define emit # define Q_OBJECT static QMetaObject staticMetaObject;void qt_static_metacall(int idx);class QObject;
/*元对象*/
struct QMetaObject
{const char * sig_names;const char * slts_names;/*信号的发送者和信号的索引*/static void active(QObject * sender, int idx);
};struct Connection
{QObject * receiver;int method;
};typedef std::multimapint, Connection ConnectionMap;
typedef std::multimapint, Connection::iterator ConnectionMapIt;class QObject
{static QMetaObject staticMetaObject; void qt_static_metacall(int idx);public:static void connect(QObject*, const char*, QObject*, const char*);void testSignal();signals:void sig1();void sig2();public slots:void slot1();void slot2();friend struct QMetaObject;private:ConnectionMap connections;
};
#endif /*此文件手动生成其实应该是Moc预编译器生成的*/
#include QObject.h static const char sig_names[] sig1\nsig2\n;static const char slts_names[] slot1\nslot2\n;QMetaObject QObject::staticMetaObject { sig_names, slts_names };void QObject::sig1()
{QMetaObject::active(this, 0);
}void QObject::sig2()
{QMetaObject::active(this, 1);
}void QObject::qt_static_metacall(int idx)
{switch (idx){case 0:{slot1();break;}case 1:{slot2();break;}};
}#include iostream
#include string
#include QObject.hint main()
{QObject obj1, obj2;QObject::connect(obj1, sig1, obj2, slot1);QObject::connect(obj1, sig2, obj2, slot2);obj1.testSignal();return 0;;
}下面是Qt中的宏替换
QObject::connect(countObj1, SIGNAL(valueChanged()), countObj2, SLOT(slotValueChanged()));QObject::connect(countObj1, 2valueChanged(), countObj2, 1slotValueChanged());#define Q_OBJECT \
public: \static const QMetaObject staticMetaObject; \virtual const QMetaObject *metaObject() const; \virtual void *qt_metacast(const char *); \virtual int qt_metacall(QMetaObject::Call, int, void **);
private: \static void qt_static_metacall(QObject *, QMetaObject::Call, int, void **); \struct QPrivateSignal {};struct Q_CORE_EXPORT QArrayData
{QtPrivate::RefCount ref;int size;uint alloc : 31;uint capacityReserved : 1;qptrdiff offset; // in bytes from beginning of header}