zoukankan      html  css  js  c++  java
  • 追根究底,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对象的指针
    };
    链表还应该有一个表头和一个表尾,这样我们在查链表中各对象元素的信息的时候才知道从哪里查起,到哪儿结束。我们还要注明本身是由哪能个类派生。所以我们的链表类要这样设计:
    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;

    }

  • 相关阅读:
    (转载)SAPI 包含sphelper.h编译错误解决方案
    C++11标准的智能指针、野指针、内存泄露的理解(日后还会补充,先浅谈自己的理解)
    504. Base 7(LeetCode)
    242. Valid Anagram(LeetCode)
    169. Majority Element(LeetCode)
    100. Same Tree(LeetCode)
    171. Excel Sheet Column Number(LeetCode)
    168. Excel Sheet Column Title(LeetCode)
    122.Best Time to Buy and Sell Stock II(LeetCode)
    404. Sum of Left Leaves(LeetCode)
  • 原文地址:https://www.cnblogs.com/lzjsky/p/1886498.html
Copyright © 2011-2022 走看看