C++繼承二之虛擬函式

gaopengtttt發表於2016-08-10
在類方法宣告如果包含了 vritual關鍵字那麼該方法稱為虛行數,繼承類中相同的定義的函式可以使用virtual也可以不使用
虛擬函式一般用於使用相同的原型重新定義基類的函式,實現不同的功能
一般使用virtual更加明確
我們還是應用C++ Primer plus中的例子
#ifndef BRASS_H_
#define Brass_H_
#include
using namespace std;


class Brass
{
        private:
                string fullName;
                long acctNum;
                double balance;
        public:
                Brass(const string& s="Nullbody",long an = -1,double bal=0.0);
                void Deposit(double amt);
                virtual void Withdraw(double amt);
                virtual void ViewAcct() const;
                double Balance() const;
                virtual ~Brass(){}
};


class BraussPlus:public Brass
{
        private:
                double maxLoan;
                double rate;
                double owesBank;
        public:
                BrassPlus(const string& s="Nullbody",long an=-1,double bal=0.0,double ml=500,double r=0.11125);
                BrassPlus(const Brass& ba,double ml=500,double r=0.11125);
                virtual void ViewAcct() const; //這裡的virtual是可選的
                virtual void Withdraw(double amt); //這裡的virtual是可選的
                void ResetMax(double m){maxLoan=m;}
                void ResetRate(double r){rate=r;}
                void ResetOwes(){owesBank = 0;}
                virtual ~BrassPlus(){}
};


#endif


引用:
Brass dom("test",123,123.23);
Brassplus dot("test",123,123.23);
Brass& b1_ref = dom;
Brass& b2_ref = dot;
b1_ref.ViewAcct(); //1
b2_ref.ViewAcct(); //2
指標:
Brass dom("test",123,123.23);
Brassplus dot("test",123,123.23);
Brass* b1_ref = &dom;
Brass* b2_ref = ˙
b1_ref->ViewAcct(); //1
b2_ref->ViewAcct(); //2


第一個b1_ref.ViewAcct(); b1_ref->ViewAcct(); 呼叫Brass::ViewAcct()
第二個b2_ref.ViewAcct(); b2_ref->ViewAcct();  呼叫Brassplus::ViewAcct()
如果不是虛擬函式那麼他們都是呼叫的Brass::ViewAcct()
因為這個引用是Brass的引用


  由此我們可以推斷出虛擬函式的主要目的在於能夠根據引用或者指標指向的物件的型別來
呼叫相應的函式,在前面說的一個基類的引用或者指標可以指向派生類,這種叫做向上
轉換,但是呼叫的介面一定是基類的,一旦有了虛擬函式就可以進行自適應。
  再來看一個例子:
  void t1(Brass& in);
  我們傳入 t1(dom)和t1(dot) 如果in.ViewAcct()顯然兩者不同,
  更可能的我們可以定義一個基類的引用陣列或者指標陣列,將基類和派生類的指標存放到一個陣列中。
  Brass* in[2] 
  或者
  Brass& in[2]
  顯然 in[0] = *dom或者in[0]=dom
  和   in[1] = *dot或者in[1]=dot
  是成立的。
  
  然後我們需要談一下虛擬函式的動態編聯以及虛擬函式原理
  
  一般函式或者方法在編譯的時候是其形參型別是固定的,直接編譯即可,這叫做靜態變聯。
  但是這種方式放到虛擬函式就不適用了比如前面的void t1(Brass& in);顯然in這個形參的含義
  是不固定他肯能指向Brass或者Brassplus型別的引用,所以需要在執行的時候才能確定,這叫做
  動態編聯
  虛擬函式會位每個物件末尾增加一個指標,這個指標指向一個這樣的陣列,陣列中全是指向虛擬函式的
  地址,這個陣列叫做vtbl(virtual function table),如果派生類包含相同的虛擬函式將會覆蓋基類的
  虛擬函式的地址,如果沒有定義相同的虛擬函式就沿用老的地址,如果派生類新建了新的虛擬函式在陣列
  中分配一個地址,如果
  比如前面的Brass dom和Brassplus dot
  dom-->包含地址-->指向vtbl-->vtbl包含地址 a1|a2 分別是Withdraw和ViewAcct
  dot-->包含地址-->指向vtbl-->vtbl包含地址 a3|a4 分別是Withdraw和ViewAcct
  因為Brassplus定義了新的虛擬函式所以a3 a4覆蓋了a1 a2.
  
  說明一下虛擬解構函式,如果解構函式不是虛解構函式,那麼只會呼叫指標或者引用類型別的解構函式
  單數如果解構函式也許虛擬函式,那麼會首先呼叫指標或者引用指向的物件的解構函式然後呼叫基類的解構函式
  比如Brassplus包含了virtual ~BrassPlus()這是預設存在的然後基類定義了virtual ~Brass()
  
  最後我們來看一下關注點
  1、在基礎類中包含了virtual關鍵字,那麼在派生出來的類中相同的函式將成為虛擬函式,派生類中virtual是可選的
  2、解構函式應該是虛擬函式,除非類不會成為基類
  3、新的虛擬函式應該和基類的虛擬函式原型保持一致,
     比如virtual void Withdraw(double amt);
     不要變為
         virtual void Withdraw(void);
  4、虛擬函式實際上一種基於向上轉換特性的,一種根據基類指標自適應的特性
  5、基類指標或者引用可以說是一種可以指向和引用派生類或者派生類的派生類的萬能指標和引用。
     基於這種特性基類指標可以指向任何派生類,虛擬函式能夠自適應指向的類,呼叫正確的方法。
  6、如果不使用虛擬函式的特性,我們只需要定義派生類指標即可,或者不使用指標和引用,因為虛方法
     的自適應特性只有指標和引用才可以
     比如我們可以
     Brassplus dot("test",123,123.23);
     Brassplus& b2_ref = dot; 
     b2_ref.ViewAcct 必然指向Brassplus::ViewAcct()
     因為預設向下轉換是不可行的,在前面已經指出
     或者
     Brassplus dot1=dot
     dot1.ViewAcct 必然使用Brassplus::ViewAcct()
     因為這不是指標也不是引用
7、如上虛擬函式自適應基於基類的指標或者引用和向上轉換特性完成。
8、虛擬函式使用動態編聯,實現是透過vtbl(virtual function table)實現的。

來看一段程式碼,本程式碼用於簡單的檢查一個使用者是否是普通使用者還是VIP使用者還是註冊使用者

點選(此處)摺疊或開啟

  1. 標頭檔案
  2. /*************************************************************************
  3.   > File Name: class.h
  4.   > Author: gaopeng
  5.   > Mail: gaopp_200217@163.com
  6.   > Created Time: Wed 31 Aug 2016 04:52:14 AM CST
  7.  ************************************************************************/

  8. #include<iostream>
  9. #include<string.h>
  10. #include <stdlib.h>
  11. using namespace std;


  12. class normalu
  13. {
  14.         private:
  15.                 string name;
  16.                 unsigned int id;
  17.                 char* password;
  18.         public:
  19.                 unsigned int pri;
  20.                 normalu(string username,unsigned int uid,unsigned int upri,const char* upass):name(username),id(uid),pri(upri)
  21.         {
  22.                 password = new char[strlen(upass)+1];
  23.                 strcpy(password,upass);
  24.         }
  25.                 normalu(const normalu& innor);
  26.                 virtual ~normalu();
  27.                 void show()
  28.                 {
  29.                         cout<<"name:"<<name<<endl;
  30.                         cout<<"id:"<<id<<endl;
  31.                 }
  32.                 virtual void checkpri(void)
  33.                 {
  34.                         if(pri>5)
  35.                         {
  36.                                 cout<<"User Is Normal User!\n";
  37.                                 show();
  38.                                 exit(0);
  39.                         }
  40.                         else
  41.                         {
  42.                                 cout<<"User Is Not Normal User!\n";
  43.                                 show();
  44.                                 cout<<"User Is Only register user!\n";
  45.                         }
  46.                 }
  47. };

  48. class vipu:public normalu
  49. {
  50.         public:
  51.                 vipu(string username,unsigned int uid,unsigned int upri,const char* upass):normalu(username,uid,upri,upass){}
  52.                 vipu(const normalu& innor):normalu(innor){}
  53.                 virtual void checkpri(void)
  54.                 {
  55.                         if (pri>10)
  56.                         {
  57.                                 cout<<"User Is Vip User\n";
  58.                                 show();
  59.                                 exit(0);
  60.                         }
  61.                         else
  62.                         {
  63.                                 cout<<"This User Is Not A Vip user!\n";
  64.                                 cout<<"Will Check This User Or Normaluser!\n";
  65.                         }
  66.                 }
  67.      virtual ~vipu(){};
  68. };

  69. normalu::normalu(const normalu& innor)
  70. {
  71.         int len = strlen(innor.password);
  72.         password = new char[len+1];
  73.         strcpy(password,innor.password);
  74.         id=innor.id;
  75.         name=innor.name;
  76.         pri=innor.pri;
  77. }
  78. normalu::~normalu()
  79. {
  80.          delete [] password;
  81. }
  82. 主函式
  83. /*************************************************************************
  84.   > File Name: main.cpp
  85.   > Author: gaopeng
  86.   > Mail: gaopp_200217@163.com
  87.   > Created Time: Wed 31 Aug 2016 04:52:20 AM CST
  88.  ************************************************************************/

  89. #include<iostream>
  90. #include"class.h"
  91. using namespace std;

  92. int main(void)
  93. {
  94.         normalu u1("gaopeng",1213,3,"test123");
  95.         vipu u2(u1);
  96.         normalu* pu;
  97.         pu = &u2;
  98.         pu->checkpri();
  99.         pu = &u1;
  100.         pu->checkpri();
  101. }

輸出:
This User Is Not A Vip user!
Will Check This User Or Normaluser!
User Is Not Normal User!
name:gaopeng
id:1213
User Is Only register user!

這裡演示上面說的大部分問題和實現,包括了解構函式

來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/7728585/viewspace-2123279/,如需轉載,請註明出處,否則將追究法律責任。

相關文章