一、QByteArray 类简介
1、QByteArray 类简介
-
该类是一个用于处理字符串的类似于 C++的 string 类型的类,在 Qt 中,对字符串的处理,经常使用的是 QString 类,
-
该类保证字符串以'\0'结尾,并使用隐式共享(copy-on-write)来减少内存用量和不必要的数据复制。
-
QByteArray 适合用于存储纯二进制数据和内存资源比较短缺的情况下。
下面是对 QByteArray 类的简单使用方法:
#include <QByteArray>
#include <iostream>
using namespace std;
int main(int argc, char *argv[])
{
QByteArray by("AAA"); //创建 QByteArray 的方法之一。
const char *pc = "ABC";
QByteArray by1(pc); //创建 QByteArray 的方法之一。
const char *pc1 = by.data(); //返回指向该字符串的 char*类型的指针
cout << pc1 << endl; // 输 出 AAA
by1.append("DDD"); //在末尾追加字符串
cout << by1.data() << endl; //输出 by1 中的字符串 ABCDDD
cout << by1.size() << endl; //输出 6,返回字符串的字符数(不含'\0')
cout << by1[2] << endl; //使用下标访问单个字符,输出 C
cout << by1.at(1) << endl; //at 函数类似于下标算符。输出 B
return 0;
}
二、元对象系统与反射机制
1、reflection 模式(反射模式或反射机制):是指在运行时,能获取任意一个类对象的所有类型信息、属性、成员函数等信息的一种机制。
2、元对象系统与反射机制
①、元对象系统提供的功能之一是为 QObject 派生类对象提供运行时的类型信息及数据成员的当前值等信息,也就是说,在运行阶段,程序可以获取 QObject 派生类对象所属类的名称、父类名称、该对象的成员函数、枚举类型、数据成员等信息,其实这就是反射机制。
②、因为 Qt 的元对象系统必须从 QObject 继承,又从反射机制的主要作用可看到,Qt 的元对象系统主要是为程序提供了 QObject 类对象及其派生类对象的信息,也就是说不是从 QObject 派生的类对象,则无法使用 Qt 的元对象系统来获取这些信息。本文仅讨论各个类提供了哪方面的信息,至于这些类是怎样实现的,本文不作讨论,因为实现这些功能的代码非常多。
3、元对象:是指用于描述另一个对象结构的对象。使用编程语言具体实现时,其实就是一个类的对象,只不过这个对象专门用于描述另一个对象而已,比如 class B{…}; class A{…B mb;…};
假设 mb 是用来描述类 A 创建的对象的,则 mb 就是元对象。
4、Qt 具体实现反射机制的方法
①、Qt 使用了一系列的类来实现反射机制,这些类对对象的各个方面进行了描述,其中,QMetaObject 类描述了 QObject 及其派生类对象的所有元信息,该类是 Qt 元对象系统的核心类,通过该类的成员函数可以获取 QObject 及其派生类对象的所有元信息, 因此可以说 QMetaObject 类的对象是 Qt 中的元对象。注意:要调用 QMetaObject 类中的成员函数需要使用 QMetaObject 类型的对象。
②、对对象的成员进行描述:一个对象包含数据成员、函数成员、构造函数、枚举成员等成员,在 Qt 中,这些成员分别使用了不同的类对其进行描述,比如函数成员使用类QMetaMethod 进行描述, 属性使用 QMetaProperty 类进行描述等, 然后使用QMetaObject 类对整个类对象进行描述,比如要获取成员函数的函数名,其代码如下:
QMetaMethod qm = metaObject->method(1); // 获取索引为 1 的成员函数
qDebug()<<qm.name()<<"\n"; // 输出该成员函数的名称。
5、使用 Qt 反射机制的条件
①、需要继承自 QObject 类,并需要在类之中加入 Q_OBJECT 宏。
②、注册成员函数:若希望普通成员函数能够被反射,需要在函数声明之前加入 QObject::Q_INVOKABLE 宏。
③、注册成员变量:若希望成员变量能被反射,需要使用 Q_PROPERTY 宏。
6、Qt 反射机制实现原理简述
①、Q_OBJECT 宏展开之后有一个虚拟成员函数 meteObject(),该函数会返回一个指向QMetaObject 类型的指针,其原型为virtual const QMetaObject *metaObject() const;
因为启动了元对象系统的类都包含 Q_OBJECT 宏,所以这些类都有含有 metaObject() 虚拟成员函数,通过该函数返回的指针调用 QMetaObject 类中的成员函数,便可查询到QObject 及其派生类对象的各种信息。
②、Qt 的moc 会完成以下工作
-
为 Q_OBJECT 宏展开后所声明的成员函数的成生实现代码
-
识别 Qt 中特殊的关键字及宏,比如识别出 Q_PROPERTY 宏、Q_INVOKABLE 宏、slot、signals 等
三、使用反射机制获取类对象的成员函数的信息
1、QMetaMethon 类
①、作用:用于描述对象的成员函数,可使用该类的成员函数获取对象成员函数的信息。
②、该类拥有如下成员:
enum MethodType{Method, Signal, Slot, Constructor}
此枚举用于描述函数的类型,即:普通成员函数(Method)、信号(Signal)、槽(Slot)、构造函数(Constructor)。
enum Access{Private, Protected, Public}
此枚举主要用于描述函数的访问级别(私有的、受保护的、公有的)
QByteArray methodSignature() const;
返回函数的签名(qt5.0)
MethodType methodType() const;
返回函数的类型(信号、槽、成员函数、构造函数)
QByteArray name() const;
返回函数的名称(qt5.0)
int parameterCount() const ;
返回函数的参数数量(qt5.0)
QList<QByteArray> parameterNames() const;
返回函数参数名称的列表。
int parameterType(int index) const;
返回指定索引处的参数类型。返回值是使用 QMetaType 注册的类型,若类型未注册, 则返回值为 QMetaType::UnknownType。
QList<QByteArray> parameterTypes() const;
返回函数参数类型的列表。
int returnType() const;
返回函数的返回类型。返回值是使用 QMetaType 注册的类型,若类型未注册,则返回值为 QMetaType::UnknownType。
const char * typeName() const;
返回函数的返回类型的名称。
Access access() const;
返回函数的访问级别(私有的、受保护的、公有的)
2、QMetaObject 类中有关获取类对象成员函数的函数有:
①、int indexOfMethod(const char* f) const;
返回名为 f 的函数的索引号,否则返回-1。此处应输入正确的函数签名,比如函数形式为 void f(int i,int j);
则正确的形式为 xx.indexOfMethod("f(int,int");
以下形式都不是正确的形式,"f(int i, int j)
"、"void f(int, int)
"、 "f
"、"void f
"等。
②、int indexOfSignal(const char * s) const;
返回信号 s 的索引号,否则返回-1,若指定的函数存在,但不是信号,仍返回-1。
③、int indexOfConstructor(const char *c) const;
返回构造函数 c 的索引号,否则返回-1
④、int constructorCount() const ;
返回构造函数的数量。
⑤、QMetaMethod constructor(int i)const;
返回指定索引 i 处的构造函数的元数据。
⑥、int methodCount() const;
返回函数的数量,包括基类中的函数、信号、槽和普通成员函数。
⑦、int methodOffset() const;
返回父类中的所有函数的总和,也就是说返回的值是该类中的第一个成员函数的索引位置。
⑧、QMetaMethod method(int i) const;
返回指定索引 i 处的函数的元数据。
//头文件 m.h 的内容(文件读者自行创建)
#ifndef M_H //要使用元对象系统,需在头文件中定义类。#define M_H
#include <QObject> //因为要使用 QObject 类,为此需要包含此头文件
class A : public QObject
{
Q_OBJECT; //启动元对象系统,必须声明此宏
public:
//定义 2 个构造函数、1 个信号、3 个函数。
Q_INVOKABLE
A()
{
} //要想函数被反射,需要指定 Q_INVOKABLE 宏。
Q_INVOKABLE A(int) {}
Q_INVOKABLE void f() {}
Q_INVOKABLE void g(int i, float j) {}
void g1() {} //注意:此函数不会被反射。
signals:
void gb3();
};
class B : public A
{
Q_OBJECT; //要使用元对象系统,应在每个类之中都声明此宏
public:
//定义 1 个函数、2 个信号
Q_INVOKABLE void
f()
{
}
signals:
void gb4();
void gb5();
};
#endif // M_H
//源文件内容(文件读者自行创建)
#include "m.h"
#include <QMetaMethod>
#include <QByteArray>
#include <iostream>
using namespace std;
int main()
{
A ma;
B mb; //创建两个对象
const QMetaObject *pa = ma.metaObject();
const QMetaObject *pb = mb.metaObject();
//以下为通过 QMetaObject 的成员函数获取的信息。
int j = pa->methodCount(); /*返回对象 ma 中的成员函数数量,包括从父类 QObject 继承而来的 5 个成员函数及本对象中的 2 个成员函数(注意,不包括 g1)、1 个信号,所以总数为 8。*/
cout << j << endl; //输出 8
int i = pa->indexOfMethod("g(int,float)"); //获取对象 ma 中的成员函数 g 的索引号。
cout << i << endl; //输出 7
i = pa->constructorCount(); //获取对象 ma 所属类中的构造函数的数量。
cout << i << endl; //输出 2
i = pb->constructorCount(); /*获取对象 mb 所属类 B 中的构造函数的数量,因类 B 无构造函数,所以
返回值为 0,此处也可看到,构造函数数量不包含父类的构造函数*/
cout << i << endl; //输出 0。
i = pa->indexOfConstructor("A(int)"); //获取对象 ma 所属类中的构造函数 A(int)的索引号。
cout << i << endl; //输出 1。
i = pa->indexOfSignal("gb3()"); //获取对象 ma 的信号 gb3()的索引号。
cout << i << endl; //输出 5。
i = pa->indexOfSignal("f()"); /*获取对象 ma 的信号 f()的索引号。因为成员函数 f 存在,但不是信号,所以返回值为-1。*/
cout << i << endl; //输出-1。
i = pb->methodOffset(); /*获取父类的成员函数数量,包括父类A 及QObject 中的成员函数,总共为8。
*/
cout << i << endl; //输出 8,此处相当于是对象 mb 自身成员函数开始处的索引号。
//以下为通过 QMetaMethon 的成员函数获取的信息。
//获取对象 ma 的成员函数 g 的元数据。
QMetaMethod m = pa->method(pa->indexOfMethod("g(int,float)"));
QByteArray s = m.name(); //获取成员函数 g 的函数名。
cout << s.data() << endl; //输出 g
s = m.methodSignature(); //获取函数 g 的签名
cout << s.data() << endl; //输出 g(int,float)
i = m.methodType(); /*获取函数 g 的类型,此处返回的是 QMetaMethod::MethodType 中定义的枚举值, 其中 Method=0,表示类型为成员函数*/
cout << i << endl; //输出 0(表示成员函数)。
//以下信息与函数的返回类型有关
s = m.typeName(); //获取函数 g 的返回值的类型名
cout << s.data() << endl; //输出 void
i = m.returnType(); /*获取函数 g 返回值的类型,此处的类型是 QMetaType 中定义的枚举值,其中枚举值QMetaType::void=43*/
cout << i << endl; //输出 43
//以下信息与函数的参数有关
i = m.parameterType(1); /*获取函数 g 中索引号为 1 的参数类型,此处的类型是 QMetaType 中定义的
枚举值,其中枚举值 QMetaType::float=38*/
cout << i << endl; //输出 38
QList<QByteArray> q = m.parameterNames(); //获取函数 g 的参数名列表
cout << q[0].data() << q[1].data() << endl; //输出 ij
q = m.parameterTypes(); //获取函数 g 的参数类型列表。
cout << q[0].data() << q[1].data() << endl; //输出 intfloat
return 0;
}
四、使用反射机制获取与类相关的信息
1、QMetaObject 类中获取与类相关的信息的成员函数有
const char* className() const;
获取类的名称,注意,若某个 QObject 的子类未启动元对象系统(即未使用 Q_OBJECT 宏),则该函数将获取与该类最接近的启动了元对象系统的父类的名称,而不再返回该类的名称,因此建议所有的 QObject 子类都使用 Q_OBJECT 宏。
const QMetaObject* superClass() const;
返回父类的元对象,若没有这样的对象则返回 0。
bool inherits(const QMetaObject* mo) const; // (Qt5.7)
若该类继承自 mo 描述的类型,则返回 true,否则返回 false。类被认为继承自身。
2、QObject 类中获取与类相关的信息的成员函数有
bool inherits(const char* className) const;
若该类是className 指定的类的子类则返回true,否则返回false。类被认为继承自身。
示例:继承关系的判断
//头文件 m.h 的内容**
#ifndef M_H //要使用元对象系统,需在头文件中定义类。#define M_H
#include <QObject>
class A : public QObject
{
Q_OBJECT
};
class B : public A
{
Q_OBJECT
};
class C : public QObject
{
Q_OBJECT
};
class D : public C
{
};
#endif // M_H
//源文件 m.cpp 的内容
#include "m.h"
#include <QMetaMethod>
#include <iostream>
using namespace std;
int main()
{
A ma;
B mb;
C mc;
D md;
const QMetaObject *pa = ma.metaObject();
const QMetaObject *pb = mb.metaObject();
cout << pa->className() << endl; //输出类名 A
//使用 QMetaObject::inherits()函数判断继承关系。
cout << pa->inherits(pa) << endl; //输出 1,类被认为是自身的子类
cout << pa->inherits(pb) << endl; //输出 0,由 pb 所描述的类 B 不是类 A 的子类。
cout << pb->inherits(pa) << endl; //输出 1,由 pa 所描述的类 A 是类 B 的子类。
//使用 QObject::inherits()函数判断继承关系。
cout << ma.inherits("B") << endl; //输出 0,类 A 不是类 B 的子类。
cout << ma.inherits("A") << endl; //输出 1,类被认为是自身的子类
cout << md.inherits("D") << endl; //输出 0,因为类 D 未启动元对象系统。
cout << md.inherits("C") << endl; /*输出 1,虽然类 D 未启动元对象系统,但类 C 已启动,此种情形下能正确判断继承关系。*/
cout << md.metaObject()->className() << endl; /*输出 C,此处未输出 D,因为类 D 未启动元对象系统,
与类 D 最接近的启动了元对象系统的父类是 C,因此返回 C。*/
return 0;
}
3、qobject_cast 函数,使用语法如下:
DestType* qobject_cast<DestType*>(QObject *p);
该函数类似于 C++中的 dynamic_cast,但执行速度比 dynamic_cast 更快,且不需要C++的 RTTI 的支持,但 qobject_cast 仅适用于 QObject 及其派生类。
主要作用是把源类型 QObject 转换为尖括号中的目标类型 DesType(或者子类型),并返回指向目标类型的指针,若转换失败,则返回 0。或者说源类型 QObject 属于目标类型 DestType(或其子类型),则返回指向目标类型的指针,否则返回 0。
使用 qobject_cast 的条件:目标类型 DestType 必须继承(直接或间接)自 QObject,并使用 Q_OBJECT 宏。
示例:qobject_cast 及其应用
//头文件 m.h 的内容。**
#ifndef M_H //要使用元对象系统,需在头文件中定义类。
#define M_H
#include <QObject>
#include <iostream>
using namespace std;
class A : public QObject
{
Q_OBJECT
public:
void fa() { cout << "FA" << endl; }
};
class B : public A
{
Q_OBJECT
public:
void fb() { cout << "FB" << endl; }
};
class C : public QObject
{
Q_OBJECT
public:
void fc() { cout << "FC" << endl; }
};
class D : public C
{
public:
void fd() { cout << "FD" << endl; }
};
#endif // M_H
//源文件 m.cpp 的内容。
#include "m.h"
#include <QMetaMethod>
#include <iostream>
using namespace std;
//qobject_cast 的简单应用(类型判断)
void g(QObject *p)
{
if (qobject_cast<A *>(p)) //若 p 是类 A 及其派生类类型
{
cout << "GA" << endl;
}
if (qobject_cast<B *>(p)) //若 p 是类 B 及其派生类类型
{
cout << "GB" << endl;
}
else //若 p 不是类 B 及其派生类类型
cout << "XX" << endl;
}
int main()
{
A *pa = new A;
B *pb = new B;
C *pc = new C;
D *pd = new D;
qobject_cast<B *>(pa)->fb(); //输出 FB,转换成功后可调用子类中的函数。
//qobject_cast<D*>(pc); //错误,因为类 D 未使用 Q_OBJECT 宏。
g(pa); //输出 GA、XX。因为 pa 不是 B 及其派生类类型所以会输出 XX。
g(pb); //输出 GA、GB。因为 pb 是A 的派生类类型,所以首先输出 GA,然后输出 GB。
g(pc); //输出 XX,因为 pc 即不是 A 也不是 B 及其派生类的类型,所以输出 XX。
g(pd); //输出 XX,原因同上。
return 0;
}