Loading

Qt笔记 元对象

元对象

一、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;
}

posted @ 2021-09-19 16:18  橘崽崽啊  阅读(282)  评论(0编辑  收藏  举报