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