• C++类对象反射机制的实现(动态创建类对像并给类的字段赋值)


    1.类的动态创建
    要创建的类要继承一个相同的父类,才能用父类指针创建出相对应的子类

    class QObject
    {
    	public:
    	QObject(){}
    	~QObject(){}
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    1.2.把类名称,类的字符名称,类的字段数据类型都要注册到对应的链表中
    以后从链表中查找对应的类信息

    1.2.1.在要创建的子类添加创建对象的声明宏

    #define REGISTER_CLASS_NAME()//注册类信息
    public:
    static QObject* CreateQObject();
    
    • 1
    • 2
    • 3

    在类外实现

    //注册类字段名称和字段数据类型
    #define BEGIN_REGISTER_FILEDS_NAME(class_name)\
    Qobject* class_name::CreateQObject(){return new class_name;}\
    //在这new一个类对象出来
    
    
    • 1
    • 2
    • 3
    • 4
    • 5

    1.2.2 注册字段信息

    首行要有保存字段信息的结构体

    struct FieldNode
    {
    	TCHAR name[20];//字段名称
    	TCHAR TypeName[20];//字段数据类型名称
    	size_t TypeHashCode;//数据类型的哈希值,用来比较数据类型是否是同一种类型
    	size_t filedOffs;	//字段名的偏移量
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    我们只要拿到类的这个结构就能对字段赋值和获得字段的值
    所以要在类中注册的时候添加获得字段信息结构的函数.

    #define REGISTER_CLASS_NAME()//注册类信息
    public:
    static QObject* CreateQObject();
    static const FieldNode* GetFieldArray(); //获得类字段信息结构数组的地址
    
    • 1
    • 2
    • 3
    • 4

    类外对应的实现添加

    #define BEGIN_REGISTER_FILEDS_NAME(class_name)\
    Qobject* class_name::CreateQObject(){return new class_name;}\
    const FieldNode* class_name::GetFieldArray(){\
    static const FieldNode class_name##FieldNode[]={\
    
    //在实现函数中添加一个静态的字段信息结构数组class_name##FieldNode
    //最后在数组中添加类的字段名称和字段类型就ok
    //就要MFC添加消息和消息响应函数的宏一个道理
    
    //结束宏
    #define END_REGISTER_FILEDS_NAME()\
    {0,0,0,0}}; return &class_name##FieldNode[0];}
    //在最后要添加一个全是0的字段结构信息,用来判断是否到数组尾
    //并返回数组的首地址
    //因为这个宏中的类名称不能识别,所以要在前面重新定义一个别名
    typedef class_name thisClass;
    //添加到类结构数组前
    //前面开始宏修改如下:
    
    #define BEGIN_REGISTER_FILEDS_NAME(class_name)\
    Qobject* class_name::CreateQObject(){return new class_name;}\
    const FieldNode* class_name::GetFieldArray(){\
    typedef class_name thisClass;\
    static const FieldNode thisClass##FieldNode[]={\
    
    //结束宏
    #define END_REGISTER_FILEDS_NAME()\
    {0,0,0,0}}; return &thisClass##FieldNode[0];}\
    
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30

    1.2.3 如何注册类的信息呢
    当然也是用结构体保存类的信息
    刚开始我也是用结构体,但是最后还是用类来保存,两都差别不太大
    主要是类在构造和字段私有上要好些,因这些数据是不能补修改的

    
    typedef QObject* (*CreateObjecFunc)();//定义获得类的创建对象函数指针
    
    class RuntimeClass
    {
    public:
    	Runtimeclass(){}
    	~Runtimeclass(){}
    private:
    	TCHAR Name[20];	//类名称
    	size_t Size;	//类的大小
    	size_t hashCode;//类的哈希值,用来比较是否是同一种类
    	CreateObjecFunc pCreatFunc;		//创建类对象的函数指针
    	const FieldNode* pFieldArray;	//类的字段数组地址
    	const RuntimeClass* pNext;		//保存下一个类信息的地址
    
    public:
    	static const RuntimeClass* g_pRuntimeClassHead;//保存类信息链表头节点
    
    	template<typename T>
    	static const RuntimeClass* GetRuntimeClass()//获得链表头节点
    	{
    		const RuntimeClass* pRunClass=g_pRuntimeClassHead;
    		while(pRunClass)//循环查找对应的类的信息
    		{
    			if(pRunClass->hashCode==typeid(T).hash_code())//通过类的哈希值比较
    				return pRunClass; //找到返回类的运行时信息
    			pRunClass=pRunClass->pNext;
    		{
    	
    		return nullptr; //没找到返回nullptr
    	}
    }
    //在类外把静态变量赋值为nullptr
    const RuntimeClass* RuntimeClass::g_pRuntimeClassHead=nullptr;
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35

    类的运行时信息也有了,但是如何在程序运行时往这类中添加相应的数据呢,
    现在运行时其中肯定没有数据的
    最后就要像MFC的应用程序theApp一样弄个全局的对象,
    也就是说一个类弄一个全局对象,在对象中构造
    下面是类的构造函数:

    Runtimeclass(LPCTSTR clsName,size_t clSize,size_t clsHashCode,
    	CreateObjecFunc pCreateFunc,const FieldNode* pFieldArray){
    	lstrcpy(this->Name,clsName);
    	this->Size=clSize;
    	this->hashCode=clsHashCode;
    	this->pCreatFunc=pCreateFunc;
    	this->pFieldArray=pFieldArray;
    	this->pNext=nullptr;
    	//给链表头赋值
    	//如果链表中有别的类信息,把当前类的下一节点指针指向原来的头节点
    	//最后把当前类作为新的头节点,也就是链表的头插入法
    	
    	if(g_pRuntimeClassHead!=nullptr)
    		this->pNext=g_pRuntimeClassHead;
    
    	g_pRuntimeClassHead=this;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17

    这是往链表中添加类信息数据
    类的字段信息还是没有添加数据,这就要在前面声明宏中创建一个运行时类对象来添加

    声明宏和结束宏不变,

    在BEGIN_REGISTER_FILEDS_NAME中添加一个运行时类对象
    不能添加到最后,因为最后是一个不全的函数,要和结束宏组成一个完整的函数的,
    如果添加到最后就添加到函数体中了,就不是全局对象了,
    所以要添加到前面或是两个函数这间

    这就是构造全局类运行时信息对象的构造
    RuntimeClass class_name##RuntimeClass(TEXT(#class_name),
    sizeof(class_name),typeid(class_name).hash_code(),
    class_name::CreateQObject,class_name::GetFieldArray());\

    参数说明:
    TEXT(#class_name) 表示类名称
    sizeof(class_name) 类大小
    typeid(class_name).hash_code() 获得类的哈希值
    class_name::CreateQObject 要创建类的创建对象函数指针
    class_name::GetFieldArray() 获得字段信息结构数组地址,一定要加上括号才能获得字段数据,
    不加括号就是函数指针,但是没调用,加上括号才调用了函数才能获得字段数据

    #define BEGIN_REGISTER_FILEDS_NAME(class_name)\
    Qobject* class_name::CreateQObject(){return new class_name;}\
    RuntimeClass class_name##RuntimeClass(TEXT(#class_name),\
    sizeof(class_name),typeid(class_name).hash_code(),\
    class_name::CreateQObject,class_name::GetFieldArray());\
    const FieldNode* class_name::GetFieldArray(){\
    typedef class_name thisClass;\
    static const FieldNode thisClass##FieldNode[]={\
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    1.2.4. 添加字段的信息宏

    #define ADD_FIELD_NAME(fName,fType)\
    {TEXT(#fName),TEXT(#fType),typeid(fType).hash_code(),(size_t)&((thisClass*)0)->fName},\
    
    //TEXT(#fName) 字段名称(变量的名称,name,sex)
    //TEXT(#fType) 数据类型名称(int ,bool)
    //(size_t)&((thisClass*)0)->fName 字段的偏移量
    //typeid(fType).hash_code() 获得数据类型的哈希值,用来比较数据类型是否相同
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    1.2.5 宏和类的代码完成后的实例

    class Student :public QObject
    {
    	REGISTER_CLASS_NAME()
    public:
    	int ID;
    	QString mName;
    	QString mSex;
    	UINT mAge;
    	double mSecoe;
    
    
    	Student() { memset(this, 0, sizeof(Student)); }
    	Student(int id, LPCTSTR name, LPCTSTR sex, UINT age, double secoe)
    	{
    		this->ID = id;
    		this->mName = name;
    		this->mSex = sex;
    		this->mAge = age;
    		this->mSecoe = secoe;
    	}
    
    	~Student() {}
    };
    
    
    BEGIN_REGISTER_FILEDS_NAME(Student)
    	ADD_FIELD_NAME(ID, int)
    	ADD_FIELD_NAME(mName, QString)
    	ADD_FIELD_NAME(mSex, QString)
    	ADD_FIELD_NAME(mAge, UINT)
    	ADD_FIELD_NAME(mSecoe, double)
    END_REGISTER_FILEDS_NAME()
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32

    实例应用:

    //先获取类的运行时信息
    const RuntimeClass* pRunClass=RuntimeClass::GetRuntimeClass<Student>();
    assert(pRunclass);
    
    //获得字段信息结构数组的首地址
    const FieldNode* pField=pRunClass->GetFieldArray();
    assert(pField);
    
    //创建对象
    Student* pStu=(Student*)pRunClass->CreateObjec();//通过调用类的创建函数指针创建的对象
    //Student stu(1,TEXT("张三"),TEXT("男"),33,23.6 );//测试对象
    
    //遍历字段信息数组获得数据或设置字段值
    while(pField->TypeHashCode!=0)
    {
    	//获取字段的值
    	//首先获得字段的类型,用哈希值比较
    	if(pField->TypeHashCode==typeid(int).hash_code())
    	{
    		//如果是int类型的值
    		int id= *((int*)((unsigned char*)&stu+ pField->filedOffs));
    	}
    //其他类型一样,只是转换的类型改变
    
    	if(pField->TypeHashCode==typeid(UINT).hash_code())
    	{
    		//如果是UINT类型的值
    		UINT age= *((UINT*)((unsigned char*)&stu+ pField->filedOffs));
    	}
    	
    	if(pField->TypeHashCode==typeid(QString).hash_code())
    	{
    		//如果是QString类型的值
    		Qstring name= *((QString*)((unsigned char*)&stu+ pField->filedOffs));
    	}
    	pField++;
    }
    
    
    //设置字段值和上面相反就行了
    *((UINT*)((unsigned char*)&stu+ pField->filedOffs))=23;
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42

    所以最后在字段结构体中添加
    Get_Field_Values(); 获得字段数据
    Set_Field_Values(); 设置字段数据

    template<typename T>
    void Set_Field_Values(QObject* pObj,T value)
    {
    	if(pField->TypeHashCode==typeid(T).hash_code()) //类型相同才能赋值
    		*((T*)((unsigned char*)&stu+ pField->filedOffs))=value;
    }
    
    template<typename T>
    void Get_Field_Values(QObject* pObj,T & outValue)
    {
    	if(pField->TypeHashCode==typeid(T).hash_code()) //类型相同才能赋值
    		outValue=*((T*)((unsigned char*)&stu+ pField->filedOffs));
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    有了这两个模板函数
    在获得字段数据时修改如下:

    while(pField->TypeHashCode!=0)
    {
    	//获取字段的值
    	if(lstrcmp(pField->Name,TEXT("mName"))==0)//查找字段为mName的字段信息结构
    	{
    		QString name;
    		pField->Get_Field_Values(&stu,name); //获得字段名为mName的值
    	}
    
    	if(lstrcmp(pField->Name,TEXT("mSex"))==0)//查找字段为mSex的字段信息结构
    	{
    		QString sex;
    		pField->Get_Field_Values(&stu,sex); //获得字段名为mSex的值
    	}
    	
    	if(lstrcmp(pField->Name,TEXT("mAge"))==0)//查找字段为mAge的字段信息结构
    	{
    		UINT age;
    		pField->Get_Field_Values(&stu,(UINT)age); //获得字段名为mAge的值,要强转,不然类型不一样
    	}
    	pField++;
    }
    
    
    //当然设置字段值也就一样了
    while(pField->TypeHashCode!=0)
    {
    	//设置字段的值
    	
    	if(lstrcmp(pField->Name,TEXT("mName"))==0)//查找字段为mName的字段信息结构
    	{
    		pField->Set_Field_Values(&stu,QString(TEXT("赵四"))); //获得字段名为mName的值,也要强转为QString
    	}
    //其他类型一样,只是转换的类型改变
    
    	if(lstrcmp(pField->Name,TEXT("mSex"))==0)//查找字段为mSex的字段信息结构
    	{
    		pField->Set_Field_Values(&stu,QString(TEXT("男"))); //获得字段名为mSex的值
    	}
    	
    	if(lstrcmp(pField->Name,TEXT("mAge"))==0)//查找字段为mAge的字段信息结构
    	{
    		pField->Set_Field_Values(&stu,(UINT)45); //获得字段名为mAge的值,要强转,不然类型不一样
    	}
    	pField++;
    }
    
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48

    总结:
    基本上就是这样了,这主要是针对从别的地方读取数据,并添加到对象中应用
    如从数据库中读取数据,并自动添加数据到对象的相应字段中,
    在外面我们只读取对像的数据,并不关心数据库的字段,
    下次再写如何从数据库中自动添加数据到类对象中

  • 相关阅读:
    [附源码]SSM计算机毕业设计郴职图书馆管理系统JAVA
    【数据结构】队列的基本操作——基本实现 | 初始化 | 出入队列
    【vscode】Window11环境下vscode使用Fira Code字体【教程】
    ManualResetEvent
    RabbitMQ消息中间件概述
    CCNP-第十九篇-ISIS(二)
    Matlab:数值的显示格式
    链表【数据结构与算法Java】
    cpp primer plus笔记012-STL模板库
    邮件安全|“AI钓鱼邮件”愈发泛滥,钓鱼邮件如何防“钓”?
  • 原文地址:https://blog.csdn.net/qq_31178679/article/details/133924416