元对象系统学习(一)
一、元对象系统
元对象是指用于描述另一个对象结构的对象。在Qt中,当我们创建一个QObject子类,并在类的私有区域调用了Q_OBJECT宏时,会由QMetaobject类自动新建一个对象staticMetaObject,这个对象就是用来描述这个子类的元对象。
- Qt的元对象系统
元对象系统提供了内部对象之间通信的信号与槽机制,运行时信息、属性系统、动态属性系统等。元对象系统基于以下三件事。
- QObject为所有要使用元对象系统的对象提供了一个基类;
- 必须在类声明的私有区域添加Q_OBJECT宏,该宏用于使能元对象系统,使能后才可以使用信号与槽功、动态属性等功能;
- 元对象编译器(MOC)为每个QObject子类提供了实现元对象所必须的特性。
- MOC
moc是 Meta-Object Compiler(元对象编译器)的缩写。因为Qt的元对象系统是对C++的扩展,使用通用的编译器去编译带有启用了元对象系统的Qt程序,是编译不了的,因此Qt自己实现了一个编译器MOC。
该编译器读取C++源文件,如果它发现其中包含一个或多个类的声明中含有Q_OBJECT宏,它就会给含有Q_OBJECT宏的类生成另一个含有元对象代码的C++源文件。这个生成的源文件可以被包含到类的源文件,或者和这个类的实现一起编译和连接。除了提供对象间通讯的信号和槽机制之 外(介绍这个系统的主要原因),QObject中的元对象代码还提供以下特征:
- metaObject(),返回该类所关联的元对象。
- className(),在运行的时候以 字符串返回类的名称,不需要C++编译器中的本地运行类型信息(RTTI)的支持。
- inherits(),返回这个对象是否 是一个继承于QObject继承树中一个特定类的类的实例。
- tr()和trUtf8() ,这两个函数是用于国际化中的字符串翻译。
- setProperty()和property(),两个函数是用来通过名称动态设置和获得对象的动态属性。
- QMetaObject::newInstance(),为类构造一个新的实例。
- 反射机制
reflection 模式(反射模式或反射机制):是指在运行时,能获取任意一个类对象的所有类型信息、属性、成员函数等信息的一种机制。
在运行阶段,程序可以通过Qt提供的一些类来获取 QObject 派生类对象所属类的名称、父类名称、该对象的成员函数、枚举类型、数据成员等信息,其实这就是反射机制;元对象系统提供的功能之一就是为 QObject 派生类对象提供运行时的类型信息及数据成员的当前值等信息。
因为继承自QObject的类,才能启动元对象系统;所以,要通过反射机制来获取元对象信息,该类必须是QObject派生而来,且QObject应该位于基类继承列表的第一位。
Qt 使用了一系列的类来实现反射机制,这些类对对象的各个方面进行了描述,其中QMetaObject 类描述了 QObject 及其派生类对象的所有元信息,该类是 Qt 元对象系
统的核心类,通过该类的成员函数可以获取 QObject 及其派生类对象的所有元信息,包括:类名、父类、数据成员、函数成员、构造函数、枚举成员、属性等成员;在 Qt 中,这些成员分别使用了不同的类对其进行描述,比如函数成员使用类QMetaMethod 进行描述,属性使用 QMetaProperty 类进行描述,枚举使用QMetaMenu进行描述等。
被Qt 中特殊的关键字及宏声明的函数或者变量才能被反射,比如Q_PROPERTY 宏、Q_INVOKABLE宏、slot、signals 等,因此普通函数想要被反射就需要用Q_INVOKABLE宏声明;变量想要被反射,需要用Q_PROPERTY 宏声明。 - 属性系统
类属性是描述类的所有对象共同特征的一个数据项,,例如对象的高度、宽度、文本、颜色等;属性是一个类最重要的部分,了解了属性,也就了解了这个类能提供那些功能;Qt可以通过QObject::property()函数获取对象的属性,也可以通过QObject::setProperty()函数设置属性值;也可以在派生类(该类继承自QObject)时用QObject::Q_PROPERTY宏去声明一个自定义的属性,定义格式如下:
Q_PROPERTY(type name
(READ getFunction [WRITE setFunction] |
MEMBER memberName [(READ getFunction | WRITE setFunction)])
[RESET resetFunction]
[NOTIFY notifySignal]
[REVISION int]
[DESIGNABLE bool]
[SCRIPTABLE bool]
[STORED bool]
[USER bool]
[CONSTANT]
[FINAL])
其中,方括号中的是可选项,各选项之间用空格隔开,变量及关键字释义:
- type:指定属性的类型,可以是 QVariant 支持的类型或用户自定义类型,若是枚举类型,还需使用 Q_ENUMS 宏对枚举进行注册,若是自定义类型,需要使Q_DECLARE_METATYPE( Type )宏进行注册;
- name:指定属性的名称。
- READ getFunction:用于指定读取属性值的读取函数,其中 READ关键字表示读取,不可更改,getFunction用于指定函数名称;若没有指定 MEMBER 变量,则必须指定 READ 函数。通常, READ 函数是const 的, READ 函数必须返回属性的类型或对该类型的引用。
- WRITE setFunction:用于指定设置属性值得函数,其中 关键字WRITE 表示写入,不可更改, setFunction用于指定函数名称。此函数必须只有一个参数,且返回值类型必须为 void。若为只读属性,则不需要指定 WRITE 函数。
- MEMBER memberName:用于把指定的成员变量 memberName 设置为具有可读和可写性质, 而无需创建READ 和 WRITE 函数,其中 MEMBER 关键字表示成员,不可更改,memberName 表示类中的成员变量。若没有指定 READ 函数,则必须指定 MEMBER 变量。
- RESET resetFunction: 用于把属性重置为默认值,该函数不能有参数,且返回值必
须为 void。其中 RESET 表示重置,不可更改,resetFunction 用于指定函数名称。 - NOTIFY notifySignal: 表示给属性关联一个信号。 如果设置该项,则应指定该类中
已经存在的信号,每当属性值发生变化时就会发出该信号。若使用 MEMBER 变量,则NOTIFY 信号必须为零或一个参数, 且参数必须与属性的类型相同, 该参数将接受该属性的新值。 - REVISION:设置版本号,默认为 0。
- DESIGNABLE: 用于设置属性在 GUI 设计工具的属性编辑器(例如 Qt Designer)中是否可见,多数属性是可见的,默认值为 ture,即可见。 该变量也可以指定一个返回布尔值的成员函数替代 true 或 false。
- SCRIPTABLE:设置属性是否可被脚本引擎访问,默认为 true。
- STORED:指定了该属性是否是独立的或者是否依赖于别的属性。它也指定了当保存对象属性时是否会保存该属性。大多数的属性的STORED为真。但是,QWidget::minmunWidth()的STROED为false,因为它的值是从QQWidget::minimumSize()中取得的,它的类型是QSize。
- USER:设置属性是否为可编辑的属性,每一个类只有一个 USER 属性(默认值为false)。 比如 QAbstractButton::checked 是用户可编辑的属性
- CONSTANT:表明属性的值是常量,常量属性不能有WRITE函数和NOTIFY信号。对于同一个对象实例,每一次使用常量属性的 READ 函数都必须得到相同的值,但对于类的不同实例,这个值可以不同。
FINAL:表示属性不能被派生类重写。
5.属性与动态属性
属性是指该属性用Q_PROPERTY宏声明过,而且可以通过QMetaObject成员函数来获取属性信息;而动态属性是指该属性没有用Q_PROPERTY宏声明,动态属性是基于某个类的实例的,也就是说动态属性是添加到某个类的对象中的,而不是添加到QMetaObject 中的,这意味着,无法使用 QMetaObject 的成员函数获取动态属性的信息,但是依然可以通过property()函数获取动态属性的信息。
当使用setProperty()函数设置属性值时,若该属性使用 Q_PROPERTY 进行了声明, 且值与属性 name 的类型兼容, 则把值 v存储在属性 name 中,并返回 true,若值与属性的类型不兼容则属性不会更改,并返回 false;若属性 name 未使用 Q_PROPERTY 进行声明,则把该属性和值作为新属性添加到对象中,并返回 false,这就是动态属性。动态属性仍可使用 property 进行查询。
二、程序
#ifndef MAINWINDOW_H
#define MAINWINDOW_H
#include <QMainWindow>
namespace Ui {
class MainWindow;
}
class MainWindow : public QMainWindow
{
Q_OBJECT
public:
explicit MainWindow(QWidget *parent = 0);
~MainWindow();
//设置类信息
Q_CLASSINFO("Author","CUI")
Q_CLASSINFO("Version","V1.0.0")
//定义年龄属性
Q_PROPERTY(int age READ age WRITE setAge NOTIFY ageChanged RESET resetAge REVISION 1)
int age();
void setAge(int age);
void resetAge();
int m_age;
signals:
void ageChanged(int age);
public slots:
void propertyTestSlot(int age);
private:
Ui::MainWindow *ui;
//被Q_INVOKABLE定义的函数能被反射,可通过元对象系统获取
Q_INVOKABLE void printPropertys(const QMetaObject *metObj);
};
#endif // MAINWINDOW_H
#include "mainwindow.h"
#include "ui_mainwindow.h"
#include <QDebug>
#include <QMetaMethod>
#include <QMetaObject>
#include <QMetaClassInfo>
MainWindow::MainWindow(QWidget *parent) :
QMainWindow(parent),
ui(new Ui::MainWindow)
{
ui->setupUi(this);
m_age = 18;
connect(this,SIGNAL(ageChanged(int)),this,SLOT(propertyTestSlot(int)));
// 1)设置已通过Q_PROPERTY声明的属性,测试该属性reset函数
this->setProperty("age",QVariant(28));
qDebug()<<this->property("age").toInt();
this->resetAge();
qDebug()<<this->property("age").toInt();
// 2)设置动态属性并查询动态属性
this->setProperty("sex",QVariant("男"));
qDebug()<<this->property("sex").toString();
// 3)打印自定义类信息
const QMetaObject *metaObj = this->metaObject();
for(int i = 0; i < metaObj->classInfoCount(); i++)
{
qDebug()<<metaObj->classInfo(i).name()<<metaObj->classInfo(i).value();
}
// 4)打印QMainWindow函数
metaObj = this->metaObject();
for(int i = 0; i < metaObj->methodCount(); i++)
{
qDebug()<<"QMainWindow函数"<<metaObj->method(i).typeName()<<metaObj->method(i).name();
}
// 5)打印MainWindow属性,及其父类的属性,通过QMetaObject::superClass获取父类的metaObject
printPropertys(this->metaObject());
}
MainWindow::~MainWindow()
{
delete ui;
}
void MainWindow::printPropertys(const QMetaObject *metObj)
{
QMetaProperty property;
int propertyCount = 0;
int propertyOffset = 0;
const QMetaObject *met = nullptr;
met = metObj;
if(met != nullptr)
{
propertyCount = metObj->propertyCount();
propertyOffset = metObj->propertyOffset();
for(int i = propertyOffset; i < propertyCount; i++)
{
property = metObj->property(i);
qDebug()<<metObj->className()<<"属性"<<QString(property.typeName())+" "+QString(property.name());
}
}
else
{
return;
}
met = nullptr;
met = metObj->superClass();
if(met != nullptr)
{
printPropertys(metObj->superClass());
}
}
void MainWindow::propertyTestSlot(int age)
{
qDebug()<<"age changed,new age is "<<age;
}
int MainWindow::age()
{
return m_age;
}
void MainWindow::setAge(int age)
{
m_age = age;
emit ageChanged(age);
}
void MainWindow::resetAge()
{
m_age = 18;
emit ageChanged(m_age);
}