`
was0107
  • 浏览: 7529 次
  • 性别: Icon_minigender_1
  • 来自: 上海
最近访客 更多访客>>
社区版块
存档分类
最新评论

追根究底,MFC六大关键技术之剖析(第二部分)

 
阅读更多
追根究底,MFC六大关键技术之剖析(第二部分)
小李先生
二、运行时类型识别(RTTI)
      运行时类型识别(RTTI)即是程序执行过程中知道某个对象属于某个类,我们平时用C++编程接触的RTTI一般是编译器的RTTI,即是在新版本的VC++编译器里面选用“使能RTTI”,然后载入typeinfo.h文件,就可以使用一个叫typeid()的运算子,它的地位与在C++编程中的sizeof()运算子类似的地方(包含一个头文件,然后就有一个熟悉好用的函数)。typdid()关键的地方是可以接受两个类型的参数:一个是类名称,一个是对象指针。所以我们判别一个对象是否属于某个类就可以象下面那样:
if (typeid (ClassName)== typeid(*ObjectName)){
((ClassName*)ObjectName)->Fun();
}

     象上面所说的那样,一个typeid()运算子就可以轻松地识别一个对象是否属于某一个类,但MFC并不是用typeid()的运算子来进行动态类型识别,而是用一大堆令人费解的宏。很多学员在这里很疑惑,好象MFC在大部分地方都是故作神秘。使们大家编程时很迷惘,只知道在这里加入一组宏,又在那儿加入一个映射,而不知道我们为什么要加入这些东东。
其实,早期的MFC并没有typeid()运算子,所以只能沿用一个老办法。我们甚至可以想象一下,如果MFC早期就有template(模板)的概念,可能更容易解决RTTI问题。
     所以,我们要回到“古老”的年代,想象一下,要完成RTTI要做些什么事情。就好像我们在一个新型(新型到我们还不认识)电器公司里面,我们要识别哪个是电饭锅,哪个是电磁炉等等,我们要查看登记的各电器一系列的信息,我们才可以比较、鉴别,那个东西是什么!
要登记一系列的消息并不是一件简单的事情,大家可能首先想到用数组登记对象。但如果用数组,我们要定义多大的数组才好呢,大了浪费空间,小了更加不行。所以我们要用另一种数据结构——链表。因为链表理论上可大可小,可以无限扩展。
      链表是一种常用的数据结构,简单地说,它是在一个对象里面保存了指向下一个同类型对象的指针。我们大体可以这样设计我们的类:
struct CRuntimeClass
{
  ……类的名称等一切信息……
  CRuntimeClass * m_pNextClass;//指向链表中下一CRuntimeClass对象的指针
};
[/size][size=small]
     链表还应该有一个表头和一个表尾,这样我们在查链表中各对象元素的信息的时候才知道从哪里查起,到哪儿结束。我们还要注明本身是由哪能个类派生。所以我们的链表类要这样设计:
struct CRuntimeClass
{
  ……类的名称等一切信息……
  CRuntimeClass * m_pBaseClass;//指向所属的基类。
  CRuntimeClass * m_pNextClass;//定义表尾的时候只要定义此指针为空就可以 了。
  static CRuntimeClass* pFirstClass;//这里表头指针属于静态变量,因为我们知道static变量在内存中只初始化一次,就可以为各对象所用!保证了各对象只有一个表头。
};

    有了CRuntimeClass结构后,我们就可以定义链表了:
static CRuntimeClass classCObject={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;
}


       说到这里,运行时类型识别(RTTI)算是完成了。写这篇文章的时候,我一直重感冒。我曾一度在想,究竟写这东西是为了什么。因为如果我把这些时间用在别的地方(甚至帮别人打打字),应该有数百元的报酬。
       是什么让“嗜财如命”的我继续写下去?我想,无非是想交几个计算机的朋友而已。计算机是大家公认高科技的东西,但学习它的朋友大多只能默默无闻,外界的朋友也不知道怎么去认识你。程序员更不是“潮流”的东西,更加得不到别人的认可。
        有一件个人认为很典型的事情,有一天,我跟一个朋友到一个单位里面。里面有一个女打字员。朋友看着她熟练的指法,心悦诚服地说:“她的电脑水平比你的又高了一个很高的层次!”,那个女的打字高手亦自豪地说:“我靠电脑为生,电脑水平肯定比你(指笔者)的好一点!换着是你,如果以电脑为生,我也不敢说好过你!”。虽然我想声明我是计算机专业的,但我知道没有理解,所以我只得客气地点头。
     选择电脑“潮流”的东西实际是选择了平凡,而选择做程序员就是选择了孤独!幸好我不是一个专门的程序员,但即使如此,我愿意做你们的朋友,因为我爱你们!

E-mail:liyi268 QQ:14653353
分享到:
评论

相关推荐

    追根究底 助力深度学习——“植树问题”的教学实践与思考.pdf

    追根究底 助力深度学习——“植树问题”的教学实践与思考.pdf

    育儿早教——追根究底

    日常生活休闲中,相信不少小伙伴可能需要育儿早教——追根究底,在这里,为大家整理发布了育儿...该文档为育儿早教——追根究底,是一份很不错的参考资料,具有较高参考价值,感兴趣的可以下载看看

    Scratch3.0趣味编程L3-追根究底深入钻研

    包含了愤怒的小鸟,植物大战僵尸等多个案例,向学生展示Scratch3.0的高级编程技巧及方案。程序设计方法,地图循环,结构化程序设计等高级知识内容。为同学们向代码编程打下坚实基础。

    5S培训教程.docx

    5S定义 整理:SEIRI要/不要 整顿:SEITON定位 清扫:SEISO没有垃圾和脏乱 清洁:SEIKETSU保持光亮和卫生 习惯:SHITSUKE养成纪律的习惯 第二章 为什么要推行5S? 1.5S是无声但最有魄力的推销员;因为可以从客户得到...

    Java reflect 机制详解

    免费共享,很详细的介绍了反射机制的原理,适合追根究底的java学习者

    array.prototype.silce.call 理解分析

    在很多框架中与遇到这个方法,但一时没去研究这个方法,只要前段时间被问到,尴尬无奈收场。所以学习就要追根究底的精神,废话少说,说正题。

    AHK(AutoHotKey)国外强大的按键软件

    热键(Hotkey)、快捷键(Shortcuts key):笼统一点说,这两个东西都是差不多的,我们就不要去追根究底研究它们有什么区别啦。我们只要知道它们都是按下键盘上某个或某些键就是啦。想要学习各种快捷键?往这边走。 ...

    从“经营之神”看大陆企业家的社会责任 (2010年)

    阐述了其事业发展轨迹背后的珍视顾 客、“瘦鹅理论”、“追根究底”、“止于至善”、征战美国市场、心系祖国大陆、回报两岸同胞等一代管理大师的 管理哲学与管理理念,从而阐释了其作为“经营之神”的非凡企业家高度...

    MATLAB-对矩阵进行赋值时的维度问题

    下面附上我追根究底的简单代码: clc; a=[1 2 3 4 5,6,7,8 9 10 11 12]; %a(1,10:13)=2:5; %没错 难道可以这样赋值??惊讶 此时a_len = 13 %a(10:13)=2:5; %也不错 与a(1,10:13)=2:5; 等价 %a(10:13) %若不赋值,...

    七彩记事本汉化美化版

    追根究底之下,发现此软件并非国产,原叫做Metapad,网络流传的是3.51的汉化版。是又有网友制作的一套七彩图标的系列,并非原版就是7个版本。图标做的非常美观,赞一个。但是这个版本的翻译很有些词不达意,所以综合...

    为了研究因果关系,原来科学家在这么多方向上都有尝试【附7篇多方向经典论文】.zip

    在现代科学到来之前,人们从经验出发,通过归纳获得知识。归纳方法受限于观测,无法产生突破性成果。而现代科学则是先从事实归纳出假设、模型,再根据数据去...现代科学的方法论,追根究底就归结于到因果关系的判定。

    JavaScript中的数值范围介绍

    主要介绍了JavaScript中的数值范围介绍,本文是对JavaScript中Number类型的追根究底,读完本文将会对Number类型有一个彻底了解,需要的朋友可以参考下

Global site tag (gtag.js) - Google Analytics