程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> C語言 >> C++ >> C++入門知識 >> Effective C++面向對象與繼承

Effective C++面向對象與繼承

編輯:C++入門知識

1:子類不要覆寫父類的非虛函數。

2:子類不要覆寫從父類繼承過來的默認參數

3:子類與父類之間的賦值問題


1:子類不要覆寫父類的非虛函數。

為了解釋方便,先看一個簡單的例子。

 

[cpp]
class A 

    public:  
        A(int d):data(d){  } 
 
        void print() 
        { 
            cout<<"A print..."<<data<<endl; 
        } 
 
        virtual void test(int i=2) 
        { 
            cout<<"A test..."<<i<<endl; 
        } 
    private:  
        int data; 
}; 
 
class B:public A 

    public :  
          
        B(int d):A(d){  } 
        void print() 
        { 
            cout<<"B print..."<<endl; 
        }  
        virtual void test(int i=4) 
        { 
            cout<<"B test..."<<i<<endl; 
        } 
}; 
   
//測試代碼  
int main() {  
    { 
        B b(5);  
        b.print(); 
        A *a=&b;  
        a->print();   
        cout<<endl; 
        b.test(); 
        a->test(); 
        cout<<endl; 
        A a1=b; 
        a1.test(); 
    } 
      
     getchar(); 
     return 0; 
}  
   

class A
{
 public:
  A(int d):data(d){  }

     void print()
  {
   cout<<"A print..."<<data<<endl;
  }

  virtual void test(int i=2)
  {
   cout<<"A test..."<<i<<endl;
  }
 private:
  int data;
};

class B:public A
{
 public :
  
  B(int d):A(d){  }
  void print()
  {
   cout<<"B print..."<<endl;
  }
  virtual void test(int i=4)
  {
   cout<<"B test..."<<i<<endl;
  }
};
 
//測試代碼
int main() {
 {
     B b(5);
  b.print();
  A *a=&b;
  a->print(); 
  cout<<endl;
  b.test();
  a->test();
  cout<<endl;
  A a1=b;
  a1.test();
 }
 
  getchar();
     return 0;
}
 
 

運行截圖:

 \

例子中指針a是指向對象b的,但是他們調用的print方法卻不是同一個。這裡涉及到靜態綁定和動態綁定的問題。a的靜態類型是A,a的動態的類型卻是B,b的靜態類型和動態類型都是B,因為靜態類型就是申明時的類型,動態類型是其真正指向的類型。還有一點就是非虛方法是靜態綁定,虛擬方法是動態綁定。Print是非虛方法,它是靜態綁定,調用的是自己的對象申明類型的方法,所以a調用的是A的print,b調用的是B的print方法。我想我們更想知道C++是怎麼實現動態綁定。我們都知道含有虛方法的類都有一個虛擬方法表,每個對象的實例都有一個指針指向這個虛擬方法表,子類會繼承父類的virtual方法,也可以覆寫父類的虛擬方法,如果子類覆寫父類的虛擬方法,那麼在虛擬表中對應的指針就指向子類覆寫父類的方法,如果子類不覆寫父類的虛擬方法,則還是指向父類的方法,這樣就形成了動態綁定。不同的子類按照自己的方式覆寫父類的虛擬方法,表現出不同的行為這就是多態。在多重繼承中,每個對象可能有多個虛擬表,那麼它的實例就會有多個指向虛擬表的指針,如果多個父類有一個相同的方法,那麼你就不能直接用這個實例調用這個方法,因為編譯器根本不知道它該調用哪個方法,你要指定是那個父類的方法,當你指明了哪個父類,編譯就可以通過對應的指針調用對應的虛擬表中對應的方法。那麼實例調用虛擬方法的過程是怎麼樣的呢,你有沒有想過?其實上面也提到一點,大致三步:

1:根據對象的vptr指針找到其虛擬方法表vtbl;

2:找到被調用方法在vtbl中對應的指針;

3:調用2中指針指向的方法。


2:子類不要覆寫從父類繼承過來的默認參數

這一條其實還是涉及到靜態綁定和動態綁定的問題,關於這個問題我想上面已經說得比較清楚了,默認值也是靜態綁定,這是毫無疑問的,因為它在編譯期就已經確定了,而虛擬方法確實動態綁定,你把靜態綁定的東西和動態綁定的東西攪在一起沒有問題,但是你還有得寸進尺的在子類中覆寫靜態的東西就會出問題,對不起,父類不管子類中靜態的東西,它只管自己靜態的東西,所以當子類不要覆寫從父類繼承過來的默認參數時,子類就可能出現精神分裂的行為,上面那個列子就是證明。


上面更多提到的都是關於虛擬方法的,那麼非虛擬方法呢,對象實例時怎麼調用非虛擬方法的呢?非虛擬方法是怎麼實現的呢?非虛擬方法就像一般的C函數那樣被實現的,所以他們的調用不需要像虛擬方法一樣先要找到一個指針,然後在通過這個指針調用對應的方法。


3:子類與父類之間的賦值問題

首先將父類轉換成子類的事最好不要做,因為子類的很多特性父類根本沒有,當你把一個從父類轉換過來的子類,當做子類來用的話,很可能出問題。接下來我們重點討論將子類轉換成父類。還是通過上面例子來說明問題。

B b(2);

A a=b;//調用copy constructor

a=b;//調用 operator=

上面兩行代碼,第一行先實例化了一個對象b,第二行將b賦給a,那麼是怎麼將b賦給a的呢,這裡其實調用的不是operator=,而是copy constructor,因為構造一個對象必須調用constructor,或是copy constructor,那麼這裡肯定是調用copy constructor,operator=只是一個賦值動作,一個對象還沒有構造出來怎麼給他賦值呢,在operator=可不是用來幫你構造對象的哦,在第三行的時候a已經被構造出來了,那麼這裡真的就是賦值了調用的就是operator=。總之一句話,一個對象作為左值時,第一次肯定調用的是copy constructor,被初始化後(分配了內存),之後的操作才是賦值。一個對象作為by value形式的參數,那麼每次調用的都是copy constructor,而不是operator=,我們一般都會說將實參賦給形參,其實是用實參構造一個形參。

將b賦給a,就是將b的A部分賦給a,a就是一個完全的A了,它對B一無所知,更不會表現出B的任何行為,所以by value是很暴力並且很耗性能的,也不會出現多態的行為。所以要避免使用by value,盡量用by reference。

 作者:chentaihan
 

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved