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

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

class QObject
{
	public:
	QObject(){}
	~QObject(){}
}

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

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

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

在类外实现

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

1.2.2 注册字段信息

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

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

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

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

类外对应的实现添加

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


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;

类的运行时信息也有了,但是如何在程序运行时往这类中添加相应的数据呢,
现在运行时其中肯定没有数据的
最后就要像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;
}

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

声明宏和结束宏不变,

在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.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.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()

实例应用:

//先获取类的运行时信息
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;

所以最后在字段结构体中添加
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));
}

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

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++;
}


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

你可能感兴趣的:(c++,c++,java,开发语言)