通关旅

softgamer的痕迹
posts - 16, comments - 13, trackbacks - 0, articles - 0

C++体会 -- 多态性

Posted on 2008-07-16 21:15 softgamer 阅读(399) 评论(0)  编辑 收藏 引用
     
   我不想在这里讲整个关于C++中的多态性机制,因为我自己描述完后,90%会误人子弟,其实那些资料你可以在
   网
上搜索到一大堆。 我只想着重讲一下我对于多态性中的动态多态性的一些使用总结。

   先简单的说说多态性:不是说千手观音。

   所谓多态性,简单地说就是一个名称,但是具有多种语义。多态性的发明有什么意义呢?考虑一下,现在你想      玩球。但是有很多种
   球,什么足球,网球,篮球,具体有多少种球类,请你登录奥运网站查阅。程序必须去根   据不
同的球的类别给
   你选择。于是你用 switch
   来根据   不同的情况来选择不同的球。但是这样做已经过时了,意   识到没
有,它一点扩展性都没有,一但需要   加入新的球的类别,你就
   不得不去修   改代码----而不是扩展代码。
   采用多态
性就不同了,利用多态性,你可以把一大堆的case语句替换为一个Ball函数。这就是它的神奇功能。

   多态性分为静态多态性和动态多态性。
   函数重载可以实现静态多态性,而要实现动态多态性就可以用----虚函数(virtual function),这也牵扯到继
   承。

   1.如果以一个基础类别的指针指向一个衍生类别的物件,那么经由此指针,你就只能呼叫基础类别(而不是衍
        生类别)所定义的函数。

   2.如果你以一个衍生类别的指针,指向一个基础类别的物件,你必须先做明显的转型动作(explicit cast), 
          这种作法很危险,不符合真实生活经验,在程序设计上也会带给程序员困惑。
 
   3.如果基础类别和衍生类别都定义了相同名称的成员函数,那么透过物件指针呼叫成员函数时,到底呼叫哪一  
        个函数,必须视该指针的原始类型而定,而不是视指针之实际所指之物件的类型而定。
         
   摘自《深入浅出MFC》

   好了,这些死板的遣词造句就到这里了,下面着重看一下虚函数在多态性中的是怎么表现的。

   以下继承论述的都是public继承。

          #include <iostream>

          using namespace std;

          class CBallBase

          {

                public:

                void Ball()

                {

                   cout << "CBallBase::Ball was selected" << endl;

                }

 

            };

           class CBallDerive:public CBallBase

           {

                public:

                void Ball()

                {

                   cout << "CBallDerive::Ball was selected"<< endl;

                }

                void Ball2()
  
                {

                   cout << "CBallDerive::Ball2 was selected" << endl;

                }

            };

 

            int main()

            {

                CBallDerive  bd;

                CBall  b;

                CBall   *p=&bd;

                p->Ball();     //result:CBall::Ball was  selected if no virtual function affix

                //p->Ball2();  // error C2039: 'Ball2' : is not a member of 'CBall'

                //CBallDerive  *p2=&b;         // error C2440: 'initializing' :.....不能自动转换

                CBallDerive*p2=(CBallDerive*)&b;

                p2->Ball();         //result:CBallDerive ::Ball was selected

                p2->Ball2();        //correct

                b=bd;           //correct 足球是球

                //b.Ball2();    //error C2039: 'Ball2' : is not a member of 'CBallBase'

                //bd=b;         //error   球不是足球

                return 0;

           }

           以上代码执行结果:
 
           CBall::Ball  was selected

           CBallDerive::Ball was selected

           CBallDerive::Ball2 was selected

    代码基本上反映了上面三点,那么为什么基类指针能够名正言顺地指向其派生类对象呢(甚至可以把一个
    派生类对象赋值给一个基类对象)?原因:派生类以public继承基类时,由于包含了基类所有的元素(private
    除外),所有的派生类对象同时也是基类对象。另一方面,基类对象就不能赋值给派生类对象,但是如果是指    针的话,我们可以强制    转换。那为什么说“不符合真实生活经验,在程式设计上也会带给程式员困惑。”呢?
    正如侯
先生论述的,物件导向观念是描绘现实世界
    用的。水果类经过添加各种特征(属性或方法)派生出苹果类。我们
可以说苹果是水果,但是却不能说水果就
    是苹果的。


    看这一句://b.Ball2();    //error C2039: Ball2' : is not a member of 'CBallBase'
 
    这里就涉及到切割(object slicing),

    看这段话你就会明白为什么它会出错:“衍生物件通常都比基类物件大(记忆体空间),因为衍生物件不但继承
    其基础类别的成员,又有自己的成员。那么所谓的upcasting(向上强制转型)将会造成物件的内容被切割(object
    slicing)。”

    在这里我要提取出第三点,先放在这里:如果基础类别和衍生类别都定义了相同名称之成员函数,那么透过物
    件
指标呼叫成员函数时,到底呼叫哪一个函数,必须视该指标的原始型别而定,而不是视指标之实际所指之物
    件的
型别而定。

    //////////////////////////////////////////////////////////////////////////////////////////////////////

    接下来让我们看,当虚拟函数参合进来时,那些指针啊对象啊之类会出现什么情况。
    再在Cbase类的void Ball()前加上virtual, 然后结果就变成了

       CBall   *p=&bd;

       p->Ball();            //result:CBall::Ball was  selected if  without virtual function affix

       =====》

       CBallDerive::Ball  was called with virtual affix

       原来的结果是

       CBall::Ball  was selected  without virtual affix


       CBallDerive*p2=(CBallDerive*)&b;
 
       p2->Ball();         //result:CBallDerive ::Ball was selected

       =====》

       CBase::Ball was called  with virtual affix

       原来的结果是

       CBallDerive::Ball was selected without virtual affix


       p2->Ball2();              //correct

       =====》

       CBallDerive::Ball2 was called

       原来的结果是

       CBallDerive::Ball2 was selected

       当在基类中不使用virtual声明会在其派生类中出现同名的Ball()函数时,如果基础类别和衍生类别都定义了相同
       名称Ball(),那么透过物件指针呼叫成员函数时,到底呼叫哪一个函数,必须视该指针的原始类
       型而定,这里的原始类型指的是CBallDerive*p2,而不是视指针之实际所指之物件(CBallDerive*)&b 的类型而定;

       当在基类中使用virtual声明会在其派生类中出现同名的Ball()函数时,如果基础类别和衍生类别都定义了相
       同名称Ball()成员函数(且基类中的该函数被定义为virtual),那么透过物件指针呼叫成员函数时,到底呼
       叫哪一个函数,必须视该指针之实际所指之物件CBallDerive &bd的类型而定,而不是视指标的原始型CBall
  
     *p别而定!

 



只有注册用户登录后才能发表评论。
网站导航: 博客园   IT新闻   BlogJava   博问   Chat2DB   管理