您的位置:首页 > 其它

(转)MFC六大关键技术之二——运行时…

2018-03-25 15:59 211 查看
运行时类型识别(RTTI)即是程序执行过程中知道某个对象属于某个类,我们平时用C++编程接触的RTTI一般是编译器的RTTI,即是在新版本的VC++编译器里面选用“使能RTTI”,然后载入typeinfo.h文件,就可以使用一个叫typeid()的运算子,它的低位与它在C++编程中的s
4000
izeof()运算子类似的地方(包含一个头文件,然后就有一个熟悉好用的函数)。typeid()关键的地方是可以接受两个类型的参数:一个是类名称,一个是对象指针。所以我们判别一个对象是否属于某个类就可以像下面那样:

if(typeid(ClassName)==typeid(*ObjectName)){

((ClassName*)ObjectName->Fun());

}

像上面所说的那样,一个typeid()运算子就可以轻松地识别一个对象是否属于某一个类,但MFC并不是用typeid()的运算子来进行动态类型识别,而是用一大堆令人费解的宏。很多学员在这里很疑惑,好像MFC在大部分都是故弄玄虚。使大家编程时迷茫,只知道在这里加入一组宏,又在那加入一组宏,而不知道我们为什么要加入这些东西。

其实,早起的MFC并没有typeid()运算子,所以只能沿用一个老办法,我们甚至可以想象一下:如果MFC早起就有template概念,就可能更容易解决RTTI问题。

所以我们要回到“古老”的年代,想象一下,要完成RTTI要做哪些事情。就好像我们在一个新型电器公司里面,我们要识别哪个是电饭锅,哪个是电磁炉等,我们要查看登记的各电器一系列的信息。我们才可以比较、鉴别哪个东西是什么!

要登记一些列的消息并不是一件容易的事,大家可能首先想到用数组登记对象。但如果用数组,我们要定义多大的数组才好呢?大了浪费空间,小了更加不幸。所以我们要用另一种数据结构——链表。因为链表理论上可大可小,可以无限扩展。

链表是一种常用的数据结构,简单地说,它是一个对象里面保存了指向下一个同类型对象的指针。我们大体可以这样设计我们的类:

struct CRuntimeClass

{

   
。。。类的名称等一切信息。。。。

CRuntimeClass* m_pNextClass; //指向链表中下一CRuntimeClass对象的指针

};

链表还应该有一个表头和一个表尾,这样我们在查询链表中各对象的信息的时候才知道从哪里查起,到哪里结束。我们还要声明本身是由哪个类派生。所以我们的链表类要这样设计:

struct CRuntimeClass

{  

   。。。类的名称等一切信息。。。

CRuntimeClass* m_pBaseClass; //指向所属的基类

CRuntimeClass* m_pNextClass; //定义表尾的时候只要定义此指针为空就可以了

struct CRuntimeClass* pFirstClass;
//这里表头指针属于静态变量,因为我们知道static变量在内存中只初始化一次,就可以为各个对象所用!保证了各对象只有一个表头!

};

有了CRuntimeClass结构后,我们就可以定义宏了:

static CRuntimeClass classCObjec ={NULL,
NULL};//这里定义了一个CRuntimeClass对象,因为classCObject无基类,所以m_pBaseClass
为NULL。因为目前只有一个元素,所以m_pNextClass为NULL。

至于pFirstClass,大家可能有点想不通,它到什么地方去了。因为我们这里并不像把classCObject作为链表表头,我们还要在前面插入很多的CRuntimeClass对象,并且因为pFirstClass为static指针,即是说它并不属于某个对象,所以我们在用它之前要先初始化:CRuntimeClass*
CRuntimeClass::pFirstClass = NULL;

现在我们可以在前面插入一个CRuntimeClass对象,插入之前我得重要声明一下:如果单纯为了运行时类型之别,我们未必用到m_pNextclass指针(更多是在运行时创建使用),我们关心的是类本身和它的基类。这样,查找一个对象是否属于一个类时,主要关心的是类本身及它的基类。

CRuntimeClass classCCmdTarget={
&classCObject, NULL};

CRuntimeClass classCWnd={ &classCCmdTarget ,NULL
};

CRuntimeClass classCView={ &classCWnd , NULL
};
好了,上面只是仅仅为一个指针m_pBaseClass赋值(MFC中真正CRuntimeClass有多个成员变量和方法),就连接成了链表。假设我们现在已全部构造完成自己需要的CRuntimeClass对象,那么,这时候应该定义表头。即要用pFirstClass指针指向我们最后构造的CRuntimeClass对象——classCView。
CRuntimeClass::pFirstClass=&classCView;
现在链表有了,表头表尾都完善了,问题又出现了,我们应该怎样访问每一个CRuntimeClass对象?要判断一个对象属于某类,我们要从表头开始,一直向表尾查找到表尾,然后才能比较得出结果吗。肯定不是这样!
大家可以这样想一下,我们构造这个链表的目的,就是构造完之后,能够按主观地拿一个CRuntimeClass对象和链表中的元素作比较,看看其中一个对象中否属于你指定的类。这样,我们需要有一个函数,一个能返回自身类型名的函数GetRuntimeClass()。
上面简单地说一下链表的过程,但单纯有这个链表是没有任何意义。回到MFC中来,我们要实现的是在每个需要有RTTI能力的类中构造一个CRuntimeClass对象,比较一个类是否属于某个对象的时候,实际上只是比较CRuntimeClass对象。
如何在各个类之中插入CRuntimeClass对象,并且指定CRuntimeClass对象的内容及CRuntimeClass对象的链接,这里起码有十行的代码才能完成。在每个需要有RTTI能力的类设计中都要重复那十多行代码是一件乏味的事情,也容易出错,所以MFC用了两个宏代替这些工作,即DECLARE_DYNAMIC(类名)和IMPLEMENT_DYNAMIC(类名,基类名)。从这两个宏我们可以看出在MFC名类中的CRuntimeClass对象构造连接只有类名及基类名的不同!
到此,可能会有朋友问:为什么要用两个宏,用一个宏不可以代换CRuntimeClass对象构造连接吗?个人认为肯定可以,因为宏只是文字代换的游戏而已。但我们在编程之中,头文件与源文件是分开的,我们要在头文件头声明变量及方法,在源文件里实具体实现。即是说我们要在头文件中声明:
public:

static CRuntimeClass classXXX 
//XXX为类名
virtual CRuntime* GetRuntimeClass()
const;
然后在源文件里实现:
CRuntimeClass* XXX::classXXX={……};
CRuntime* GetRuntimeClass() const;

{ return &XXX::
classXXX;}//这里不能直接返回&classXXX,因为static变量是类拥有而不是对象拥有。
我们一眼可以看出MFC中的DECLARE_DYNAMIC(类名)宏应该这样定义:
#define DECLARE_DYNAMIC(class_name) public: static
CRuntimeClass class##class_name; virtual CRuntimeClass*
GetRuntimeClass() const;
其中##为连接符,可以让我们传入的类名前面加上class,否则跟原类同名,大家会知道产生什么后果。
有了上面的DECLARE_DYNAMIC(类名)宏之后,我们在头文件里写上一句
DECLARE_DYNAMIC(XXX)
宏展开后就有了我们想要的:
public:

static CRuntimeClass classXXX 
//XXX为类名
virtual CRuntime* GetRuntimeClass()
const;
对于IMPLEMENT_DYNAMIC(类名,基类名),看来也不值得在这里代换文字了,大家知道它是知道回事,宏展开后为我们做了什么,再深究真是一点意义都没有!
有了此链表之后,就像有了一张存放各类型的网,我们可以轻而易举地RTTI。CObject有一个函数BOOL IsKindOf(const CRuntimeClass*
pClass) const;,被它以下所有派生员继承。
此函数实现如下:
BOOL CObject::IsKindOf(const CRuntimeClass* pClass)
const

{

  CRuntimeClass*
pClassThis=GetRuntimeClass();//获得自己的CRuntimeClass对象指针。
  while(pClassThis!=NULL)

  {

   if(pClassThis==pClass) return
TRUE;

  
pClassThis=pClassThis->m_pBaseClass;//这句最关键,指向自己基类,再回头比较,一直到尽头m_pBaseClass为NULL结束。
  }

   return FALSE;

}

98b0
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: