Qt并不是一定要用来做界面开发,其实Qt内部的很多非界面模块也十分优秀,本文紧接着上一篇文章,继续介绍使用QObject做基类来开发模块。
信号与槽(Signals and slots)
信号驱动是面向对象编程的精髓,对象与对象之间通过各种信号来传递信息,大部分的面向对象开发的信号驱动都是通过回调函数方式串联在一起。而Qt下实现信号驱动的机制是其信号与槽(Signals and slots)体系。
要实现信号和槽之间的关联,需要三个部分:1-信号函数构造;2-槽函数构造;3-信号和槽关联。信号就像广播一样,可以在对象内,对象间四处传播;只有接口匹配的槽函数才能接受信号。那么如何确定某个信号被某个槽函数(或者某几个槽函数)接受呢? 这就需要建立信号与槽之间的关联关系。

信号驱动是面向对象编程的必不可少的技术。比如新建了4个任务,收到开始指令后,需要将这四个任务分配到四个CPU中并行计算。 此时如果使用传统的函数驱动编程方式,应该很难实现。 那么如果事先把四个任务分配到四个CPU,再发出一个信号,四个CPU中的任务收到信号后,同时并行开始就会十分轻松。
不再空拽理论,紧接着上一次的项目继续,我们还是结合实操来看。上次的代码已经定义了一个信号。如下图:

当修改的名称和原来保存的名称不一样时,就会发出一个信号,发出信号的方式如下,需要使用Qt关键字 emit:3

下面构建槽函数,槽函数和普通函数在定义和实现上没有任何区别,只是需要放在slots修饰的字段后面,下面构建一个public槽函数,实现将新的name写入列表m_nameVector之中:
源代码中对槽函数进行实现:
下面实进行信号与槽之间关联,信号与槽之间的关联方法是QObject类的基本方法,方法名称为connect。一共有5种方式来实现信号与槽之间的关联:
connect(const QObject *sender, const char *signal, const QObject *receiver, const char *method, Qt::ConnectionType type)
connect(const QObject *sender, const char *signal, const char *method, Qt::ConnectionType type) const
connect(const QObject *sender, PointerToMemberFunction signal, const QObject *receiver, PointerToMemberFunction method, Qt::ConnectionType type)
connect(const QObject *sender, PointerToMemberFunction signal, Functor functor)
connect(const QObject *sender, PointerToMemberFunction signal, const QObject *context, Functor functor, Qt::ConnectionType type)
其中第一种和第二种是比较传统的基于字符串关联的方式,如下为例,将刚才开发的void nameChanged() 和void addOneName()关联在一起。第一种方式和第二种方式没有区别,此处只介绍第一种实现方式,第一个this表示信号发出对象指针,第二个this表示槽函数对象指针,如果信号和槽对象一样,那么第二个指针可以省略,也就是第二种关联方式。信号和槽函数使用宏SIGANL和SLOT关联。
第三种信号与槽函数的关联方式定义是connect(const QObject *sender, PointerToMemberFunction signal, const QObject *receiver, PointerToMemberFunction method, Qt::ConnectionType type = Qt::AutoConnection),此方法是将函数字符串变化为成员函数指针,对应的关联方式如下:

成员函数(信号或者槽)指针的实现方式是 “&类名称::信号名称”和“&类名称::槽函数名称”,注意是类名称加函数名称。
第五种信号与槽函数的关联方式是connect(const QObject *sender, PointerToMemberFunction signal, const QObject *context, Functor functor, Qt::ConnectionType type),第四种信号与槽函数的关联方式和第五种基本一样,当信号和槽是同一个对象时,可以忽略第二个对象指针,也就是第四种关联方式。这两种实现方式是将槽函数以仿函数的方式来实现,Qt的仿函数实现方式是“{}”,中括号只能是这两个值:“&”或者“=”,“()”中输入形参,“{}”里对仿函数进行实现。

如果是值传递,可以在[]中写入&,因为传递的是指针this,只能在[]中写入“=”。
编译运行项目,对name进行修改,输出m_nameVector中的值到控制台:

本项目的头文件.h:
#ifndef CUSTOMOBJECT_H
#define CUSTOMOBJECT_H
#include <QObject> //头文件包含
#include <QVector> //QVector头文件包含
class customObject : public QObject
{
Q_OBJECT //必须私有包含的宏
public:
explicit customObject(QObject *parent = nullptr); //默认定义的构造函数
~customObject(); //析构函数
enum Type //公共枚举类定义
{
type1 = 0,
type2 =1
};
QString name() const;
void setName(const QString &name);
protected: //受保护的方法定义,一般是事件驱动的虚函数重定义
virtual void timerEvent(QTimerEvent *event) override; //重定义计时器方法
public slots: //公共槽函数定义区域
void addOneName(); //将修改新名称加到QVector列表中
signals: //信号定义
void nameChanged();
private: //除枚举类的大部分属性定义区域
QString m_name{}; //定义一个基本私有属性
QVector<QString> *m_nameVector = nullptr; //新建一个指针指向一个存放字符串的Vector序列
};
#endif // CUSTOMOBJECT_H
本项目的源文件.cpp:
#include "customobject.h"
#include <QDebug>
customObject::customObject(QObject *parent)
: QObject(parent),
m_nameVector{new QVector<QString> {}} //在堆区变量m_nameVector开辟内存空间
{
//connect(this,SIGNAL(nameChanged()), this, SLOT(addOneName())); //第一种信号与槽关联方式
connect(this, &customObject::nameChanged, this, &customObject::addOneName); //第三种信号与槽关联方式
// connect(this,&customObject::nameChanged,this,[=]()
// {
// m_nameVector->push_back(m_name);
// }); //第五种信号与槽关联方式
}
customObject::~customObject()
{
if(m_nameVector){delete m_nameVector; m_nameVector = nullptr;}
}
void customObject::timerEvent(QTimerEvent *event)
{
}
void customObject::addOneName()
{
m_nameVector->push_back(m_name); //将修改后name添加到列表
qDebug()<<*m_nameVector;
}
QString customObject::name() const
{
return m_name;
}
void customObject::setName(const QString &name)
{
if(name==m_name)
{
return;
}
else
{
m_name = name;
qDebug()<<"name changed...";
emit nameChanged(); //发出name值修改信号
}
}
后期将继续介绍QObject的事件体系和多线程体系。
欢迎同好沟通交流,批评指正,欢迎关注我的公号:不如起而行之
本文详细介绍了Qt的信号与槽机制,作为面向对象编程的重要技术,信号驱动使得对象间的通信更加便捷。通过实例展示了信号的发射、槽函数的创建、以及多种信号与槽的关联方式,帮助读者理解并掌握这一核心特性。
702

被折叠的 条评论
为什么被折叠?



