【Qt专栏】Qt 中信号与槽的概念和实现机制

目录

一,信号和槽概念

1.元对象系统 

2.信号和槽

3.底层实现机制

二,什么是观察者设计模式

三,观察者设计模式的编程套路

四,纯 C++ 实现信号与槽机制

1.槽函数模板类

2.信号模板类

3.connect 宏

4.测试代码

5.运行结果

6.解决 VS Code 终端乱码问题 


一,信号和槽概念

1.元对象系统 

Qt中信号和槽不是 C++ 标准代码,在使用这一核心机制时就需要使用Qt的 MOC(元对象编译器) 进行预处理(MOC其实是一个预处理器),在由标准 C++编译器 进行重新编译。

元对象系统基于以下三点组成:

  1. QObject 类是所有元对象系统的类的。
  2. 在一个类的private部分声明 Q_OBJECT 宏,使得类可以使用元对象的特性,如信号与槽。
  3. MOC 为每个的子类提供必要的代码来实现元对象系统的属性。

2.信号和槽

信号和槽是对象间进行通信的机制,也必须要由Qt的元对象系统支持才能实现。

信号和槽之间的关系:

  1. 信号的参数的类型必须与槽函数的参数的类型相对应。
  2. 信号的参数个数大于等于槽函数的参数的个数。
  3. 信号和槽函数之间的 connect 关系是多对多,信号也可以 connect 到另一个信号上。

3.底层实现机制

  • 观察者设计模式

二,什么是观察者设计模式

  • 观察者模式 是一种对象行为模式。它定义对象间的一种一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都得到通知并被自动更新。
  • 在观察者模式中,主体是通知的发布者,它发出通知时并不需要知道谁是它的观察者,可以有任意数目的观察者订阅并接收通知。
  • 观察者模式不仅被广泛应用于软件界面元素之间的交互,在业务对象之间的交互、权限管理等方面也有广泛的应用
  • 观察者模式(Observer)完美的将观察者和被观察的对象分离开,观察者模式在模块之间划定了清晰的界限,提高了应用程序的可维护性和重用性。

三,观察者设计模式的编程套路

  1. 设计两者类,一个为观察者类(触发槽函数),一个为被观察者类(信号发布)
  2. 观察者类中,定义一个对某个事件感兴趣的处理函数,也就是槽函数。
  3. 被观察者类中,定义一个数据结构,用来保存观察者对哪一个事件感兴趣,可以使用vector建立对应关系。
  4. 被观察者类中,实现两个接口函数:
    1. (接口一)添加观察者与其感兴趣的事件加入容器中。
    2. (接口二)通知事件函数执行逻辑处理,首先遍历容器中有没有感兴趣的事件,如果有,则代表一系列的观察者对这个事件感兴趣,那么再次遍历观察者列表,让每一个观察者执行相应的槽函数。

四,纯 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 即可。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

拉伊卜

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值