目录
一,信号和槽概念
1.元对象系统
Qt中信号和槽不是 C++ 标准代码,在使用这一核心机制时就需要使用Qt的 MOC(元对象编译器) 进行预处理(MOC其实是一个预处理器),在由标准 C++编译器 进行重新编译。
元对象系统基于以下三点组成:
- QObject 类是所有元对象系统的类的。
- 在一个类的private部分声明 Q_OBJECT 宏,使得类可以使用元对象的特性,如信号与槽。
- MOC 为每个的子类提供必要的代码来实现元对象系统的属性。
2.信号和槽
信号和槽是对象间进行通信的机制,也必须要由Qt的元对象系统支持才能实现。
信号和槽之间的关系:
- 信号的参数的类型必须与槽函数的参数的类型相对应。
- 信号的参数个数大于等于槽函数的参数的个数。
- 信号和槽函数之间的 connect 关系是多对多,信号也可以 connect 到另一个信号上。
3.底层实现机制
-
观察者设计模式
二,什么是观察者设计模式
- 观察者模式 是一种对象行为模式。它定义对象间的一种一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都得到通知并被自动更新。
- 在观察者模式中,主体是通知的发布者,它发出通知时并不需要知道谁是它的观察者,可以有任意数目的观察者订阅并接收通知。
- 观察者模式不仅被广泛应用于软件界面元素之间的交互,在业务对象之间的交互、权限管理等方面也有广泛的应用
- 观察者模式(Observer)完美的将观察者和被观察的对象分离开,观察者模式在模块之间划定了清晰的界限,提高了应用程序的可维护性和重用性。
三,观察者设计模式的编程套路
- 设计两者类,一个为观察者类(触发槽函数),一个为被观察者类(信号发布)。
- 观察者类中,定义一个对某个事件感兴趣的处理函数,也就是槽函数。
- 被观察者类中,定义一个数据结构,用来保存观察者对哪一个事件感兴趣,可以使用vector建立对应关系。
- 被观察者类中,实现两个接口函数:
- (接口一)添加观察者与其感兴趣的事件加入容器中。
- (接口二)通知事件函数执行逻辑处理,首先遍历容器中有没有感兴趣的事件,如果有,则代表一系列的观察者对这个事件感兴趣,那么再次遍历观察者列表,让每一个观察者执行相应的槽函数。
四,纯 C++ 实现信号与槽机制
1.槽函数模板类
template <typename TParam>
class SlotBase
{
public:
virtual void slotFunction(TParam) = 0; //占位参数:不用写参数名称
virtual ~SlotBase() = default; //纯析构函数
};
template <class TRecver,typename TParam>
class Slot:public SlotBase<TParam>
{
private:
TRecver* m_pSlotObj; //定义一个接收者(serder)的指针,在构造中对其初始化。
void (TRecver::*m_slotFunc)(TParam); //定义一个接收者类中的成员函数指针,在构造中对其初始化。
public:
Slot(TRecver* pObj,void(TRecver::*recverFunc)(TParam))
{
this->m_pSlotObj = pObj; //使用类外的接收者类的对象指针进行初始化。
this->m_slotFunc = recverFunc; //使用类外的接收者类中的成员函数指针进行初始化。
}
void slotFunction(TParam param)override
{
(m_pSlotObj->*m_slotFunc)(param); //成员对象指针调用类内的成员函数
}
};
2.信号模板类
template <typename TParam>
class Signal
{
private:
vector<SlotBase<TParam>*> signal_vector; //用于触发槽函数
public:
template<class TRecver>
void addSlot(TRecver* pSlotObj,void (TRecver::*slotFunc)(TParam))
{
auto slotObj = new Slot<TRecver,TParam>(pSlotObj,slotFunc);
signal_vector.push_back(slotObj);
}
void operator()(TParam param)
{
for(auto p : signal_vector){
p->slotFunction(param);
}
}
};
3.connect 宏
#define connect(sender,signal,recver,slotFunc) (sender)->signal.addSlot(recver,slotFunc)
4.测试代码
class Recver1
{
public:
void func1(int param)
{
cout << "这是 Recver1 中的方法,参数为:" << param << endl;
}
};
class Recver2
{
public:
void func2(int param)
{
cout << "这是 Recver2 中的方法,参数为:" << param << endl;
}
};
class Sender
{
public:
Signal<int> valueChanged;
public:
void testSignal(int value){
valueChanged(value);
}
};
//以上为三个毫无相关的 Demo 类
int main()
{
Recver1* r1 = new Recver1;
Recver2* r2 = new Recver2;
Sender* sd = new Sender;
connect(sd,valueChanged,r1,&Recver1::func1);
connect(sd,valueChanged,r2,&Recver2::func2);
sd->testSignal(1314520);
return 0;
}
5.运行结果
6.解决 VS Code 终端乱码问题
1.在Windows系统下,VS Code 使用的是Windows的终端,也就是嵌入进去的,而Windows自带的终端编码格式是 GBK(右击终端选择属性,然后按如下图操作可查看 cmd 的编码格式),而 VS Code 默认使用的编码格式是 UTF-8,所以会出现乱码现象。
2.单文件修改方法:按如下图操作,在编码格式项选择 GB2312 或者 GBK,但这只会修改当前源文件的编码格式,在新建一个源文件使用的还是 VS Code 默认的 UTF-8 编码格式,所以唯有源头活水来。
3.多文件修改方法:也就是直接修改 VS Code 的默认编码格式,就不用担心每新建一个源文件就按步骤2修改一次编码格式了,如下图操作,选择 GB2312 或者 GBK 即可。