友情提示:如果本网页打开太慢或显示不完整,请尝试鼠标右键“刷新”本网页!
第三电子书 返回本书目录 加入书签 我的书架 我的书签 TXT全本下载 『收藏到我的浏览器』

深入浅出MFC第2版(PDF格式)-第21部分

快捷操作: 按键盘上方向键 ← 或 → 可快速上下翻页 按键盘上的 Enter 键可回到本书目录页 按键盘上方向键 ↑ 可回到本页顶部! 如果本书没有阅读完,想下次继续接着阅读,可使用上方 "收藏到我的浏览器" 功能 和 "加入书签" 功能!




        { 



            return m_salary; // 经理以「固定周薪」计薪。 



        } 



       float CWage::putePay () 



        { 



            return  (m_wage * m_hours); // 时薪职员以「钟点费* 每周工时」计薪。 



        } 



       float CSales::putePay () 



        { 



       // 销售员以「钟点费* 每周工时」再加上「佣金* 销售额」计薪。 



            return  (m_wage * m_hours + m_m * m_sale); // 语法错误。 



        } 



        CSales                  CWage   m_wage   m_hours           pr ivate 

    但是        对象不能够直接取用               的        和        ,因为它们是 



    成员变量。所以是不是应该改为这样: 



       float CSales::putePay () 



        { 



            return putePay () + m_m * m_sale; 



        } 



                                       put ePay       … 

    这也不好,我们应该指明函数中所调用的                            究归谁属  编译器没有厉害到能 



    够自行判断而保证不出错。正确写法应该是: 



       float CSales::putePay () 



        { 



            return CWage::putePay () + m_m * m_sale; 



        } 



    这就合乎逻辑了:销售员是一般职员的一种,他的薪水应该是以时薪职员的计薪方式作 



    为底薪,再加上额外的销售佣金。我们看看实际情况,如果有一个销售员: 



    CSales aSales(〃侯俊杰〃); 



                                                                                      66 


…………………………………………………………Page 129……………………………………………………………

那么侯俊杰的底薪应该是: 



   aSales。CWage::putePay (); // 这是销售员的底薪。注意语法。 



而侯俊杰的全薪应该是: 



   aSales。putePay (); // 这是销售员的全薪 



                                scope resolution operator :: 

结论是:要调用父类别的函数,你必须使用                               明白指出。 



接下来我要触及对象类型的转换,这关系到指针的运用,更直接关系到为什么需要虚拟 



函数。了解它,对于application framework 如MFC 者的运用十分十分重要。 



假设我们有两个对象: 



   CWage aWager; 



   CSales aSales(〃侯俊杰〃); 



销售员是时薪职员之一,因此这样做是合理的: 



   aWager = aSales; // 合理,销售员必定是时薪职员。 



这样就不合理: 



   aSales = aWager; // 错误,时薪职员未必是销售员。 



如果你一定要转换,必须使用指针,并且明显地做型别转换(cast )动作: 



   CWage* pWager; 



   CSales* pSales; 



   CSales aSales(〃侯俊杰〃); 



   pWager = &aSales; // 把一个「基础类别指针」指向衍生类别之对象,合理且自然。 



   pSales =  (CSales *)pWager; // 强迫转型。语法上可以,但不符合现实生活。 



真实世界中某些时候我们会以「一种动物」来总称猫啊、狗啊、兔子猴子等等。为了某 



种便利(这个便利稍后即可看到),我们也会想以「一个通用的指针」表示所有可能的 



职员类型。无论如何,销售员、时薪职员、经理,都是职员,所以下面动作合情合理: 



                                                                       67 


…………………………………………………………Page 130……………………………………………………………

        CEmployee* pEmployee; 



        CWage aWager (〃曾铭源〃); 



        CSales aSales(〃侯俊杰〃); 



        CManager aManager (〃陈美静〃); 



        pEmpolyee = &aWager; // 合理,因为时薪职员必是职员 



        pEmpolyee = &aSales; // 合理,因为销售员必是职员 



        pEmpolyee = &aManager; // 合理,因为经理必是职员 



    也就是说,你可以把一个「职员指针」指向任何一种职员。这带来的好处是程序设计的 



    巨大弹性,譬如说你设计一个串行(linked list ),各个元素都是职员(哪一种职员都可 



             add  

    以),你的       函数可能因此希望有一个「职员指针」作为参数: 



        add (CEmployee* pEmp); // pEmp 可以指向任何一种职员 



晴天霹雳 



    我们渐渐接触问题的核心。上述C++ 性质使真实生活经验的确在计算机语言中仿真了出 



    来,但是万里无云的日子里却出现了一个晴天霹雳:如果你以一个「基础类别之指针」 



    指向一个「衍生类别之对象」,那么经由此指针,你就只能够调用基础类别(而不是衍 



    生类别)所定义的函数。因此: 



        CSales aSales(〃侯俊杰〃); 



        CSales* pSales; 



        CWage* pWager; 



        pSales = &aSales; 



        pWager = &aSales; // 以「基础类别之指针」指向「衍生类别之对象」 



        pWager…》setSales(800。0); //                  ) 

                                   错误(编译器会检测出来 , 



        // 因为CWage 并没有定义setSales 函数。 



        pSales…》setSales(800。0); // 正确,调用CSales::setSales 函数。 



       pSal es  pWage r  

    虽然       和       指向同一个对象,但却因指针的原始类型而使两者之间有了差异。 



    延续此例,我们看另一种情况: 



        pWager…》putePay (); // 调用CWage::putePay () 



        pSales…》putePay (); // 调用CSales::putePay () 



       pSal es  pWage r         CSales                  put ePay  

    虽然       和       实际上都指向          对象,但是两者调用的                  却不 



                                                                                68 


…………………………………………………………Page 131……………………………………………………………

    相同。到底调用到哪个函数,必须视指针的原始类型而定,与指针实际所指之对象无关。 



三个结论 



    我们得到了三个结论: 



    1。 如果你以一个「基础类别之指针」指向「衍生类别之对象」,那么经由该指针 



      你只能够调用基础类别所定义的函数。 



        class CBase                      CBase* pBase; 



        BaseFunc() 



                                 虽然我们可以令pBase 实际指向CD erived 对象, 



                                 却因为pBase 的类型(〃一个CBase* 指针〃) 

      class CDerived 

                                 使它只能够调用BaseFunc(),不能够调用DeriFunc()。 

        DeriFunc() 

     



    2。 如果你以一个「衍生类别之指针」指向一个「基础类别之对象」,你必须先做 



      明显的转型动作(explicit cast )。这种作法很危险,不符合真实生活经验,在 



      程序设计上也会带给程序员困惑。 



        class CBase       CDerived *pDeri; 

                          CBase aBase(〃Jason〃); 

        BaseFunc() 

                                             这种作法很危险,不符合真实生活经验, 

                          pDeri = &aBase;  //  

                                           //  

                                              在程序设计上也会带给程序员困惑。 



      class CDerived                  CDerived* pDeri; 



        DeriFunc() 

     



    3。 如果基础类别和衍生类别都定义了「相同名称之成员函数」,那么透过对象指 



      针调用成员函数时,到底调用到哪一个函数,必须视该指针的原始型别而定, 



      而不是视指针实际所指之对象的型别而定。这与第  点其实意义相通。 

                                              1  



                                                                                   69 


…………………………………………………………Page 132……………………………………………………………

                       class CBase 

                                         CBase* pBase; 

                        BaseFunc() 

                                         CDerived* pDeri; 

                        mFunc() 



                                           不论你把这两个指针指向何方,由于它们的原始类型, 

                      class CDerived       使它们在调用同名的 m Func() 时有着无可改变的宿命: 



                                           o pBase…》mFunc() 永远是指CBase::mFunc 

                        DeriFunc() 

                        mFunc()         o pDeri…》mFunc() 永远是指CDerived::mFunc 



                 得到这些结论后,看看什么事情会困扰我们。前面我曾提到一个由职员组成的串行,如 



                             pr intNames  

                  果我想写一个              函数走访串行中的每一个元素并印出职员的名字,我们可以在 



                  CEmpl oyee                     getName                   while  

                           (最基础类别)中多加一个                  函数,然后再设计一个             循环如下: 

                      int count = 0; 

                     CEmployee* pEmp; 

                      。。。 

                     while (pEmp = anIter。getNext()) 

                      { 

                        count++; 

                        cout  put ePay while  

    因此如果上述       循环中调用的是                   ,那么     循环所执行的将 



    总是相同的运算,也就是CEmpl oyee::put ePay ,这就糟了(销售员领到经理的薪水还 



    不糟吗)。更糟的是,我们根本没有定义CEmpl oyee::put ePay ,因为CEmpl oyee  只 



    是个抽象概念(一个抽象类别)。指针必须落实到具象类型上如CWage 或CManager 或 



    CSales ,才有薪资计算公式。 



虚拟函数与一般化 



    我想你可以体会,上述的while 循环其实就是把动作「一般化」。「一般化」之所以重 



    要,在于它可以把现在的、未来的情况统统纳入考量。将来即使有另一种名曰「顾问」 



    的职员,上述计薪循环应该仍然能够正常运作。当然啦,「顾问」的put ePay 必须设 



    计好。 



     「一般化」是如此重要,解决上述问题因此也就迫切起来。我们需要的是什么呢?是能 



    够「依旧以CEmpoly ee 指针代表每一种职员」,而又能够在「实际指向不同种类之职员」 



    时,「调用到不同版本(不同类别中)之put ePay 」这种能力。 



    这种性质就是多态(polymorphism ),靠虚拟函数来完成。 



    再次看看那张计薪循环图: 



       pE mp             pE mp…》 put ePay  

    ■ 当     指向经理,我希望                   是经理的薪水计算式,也就是 



         CManager::put ePay 。 



       pE mp               pE mp …》put ePay  

    ■ 当     指向销售员,我希望                    是销售员的薪水计算式,也就 



       是C
返回目录 上一页 下一页 回到顶部 0 0
快捷操作: 按键盘上方向键 ← 或 → 可快速上下翻页 按键盘上的 Enter 键可回到本书目录页 按键盘上方向键 ↑ 可回到本页顶部!
温馨提示: 温看小说的同时发表评论,说出自己的看法和其它小伙伴们分享也不错哦!发表书评还可以获得积分和经验奖励,认真写原创书评 被采纳为精评可以获得大量金币、积分和经验奖励哦!