程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> C語言 >> C++ >> C++入門知識 >> C++一些注意點之繼承和派生

C++一些注意點之繼承和派生

編輯:C++入門知識

1.繼承權限:

       public:公有派生,基類中所有成員在公有派生類中保持各個成員的訪問權限。基類public成員,在派生類也為public成員;基類protected成員,在派生類可以訪問,在派生類之外不能直接訪問;基類private成員,除了基類的成員函數能訪問之外,其他都不能訪問。基類對基類對象和派生類對象的訪問權限時一樣的。

       protected:與私有繼承的情況相同。兩者的區別在於基類成員對其對象的可見性與一般類及其對象可見性相同。公有成員可見,其他成員不可見。基類的公有成員和保護成員都作為派生類的保護成員,並且不能被這個派生類的子類所訪問。基類對派生類對象所有成員都不可見。保護繼承無法再往下繼承。

       private: 私有派生,基類中公有成員和保護成員在派生類中均變為私有的,在派生類中仍然可以直接訪問這些成員,但是在派生類之外均不可直接使用基類的公有或私有成員。基類對基類對象保持原有權限,基類對派生類對派生類對象來說所有都是私有成員。私有繼承無法再往下繼承。

2.抽象類:定義一個類,只用作基類派生新類,而不能用作定義對象,該類稱為抽象類。

      當把一個類的構造函數或析構函數的訪問權限定義為保護時,這種類稱為抽象類。因為在定義對象的時候要調用構造函數和析構函數,所以構造函數或者析構函數定義為保護的權限時都行。但是派生類在構造時要調用基類的構造函數和析構函數,所以也不能將構造函數或者析構函數聲明為私有的。

      另一種定義:當類中含有純虛函數時,為抽象類。

3.初始化基類成員:派生類一方面要調用派生類的構造函數來初始化在派生類新增的成員,另一方面要調用基類的構造函數來初始化派生類中的基類成員。調用順序是這樣的:

      (1)調用基類的構造函數;(調用順序取決於繼承的一個順序)

      (2)調用對象成員的構造函數;(調用順序取決於在類對象在派生類中的聲明順序)

      (3)調用自己的構造函數。(初始化成員時,跟變量的定義順序有關)

例子:


[html] 
#include<iostream> 
 
 
using namespace std; 
 
 
class Base1{ 
    int x; 
public: 
    Base1(int a) 
    { 
        x = a; 
        cout<<"調用基類1的構造函數"<<endl; 
    } 
    ~Base1() 
    {cout<<"調用基類1的析構函數"<<endl;} 
}; 
 
 
class Base2{ 
    int y; 
public: 
    Base2(int b) 
    { 
        y = b; 
        cout<<"調用基類2的構造函數"<<endl; 
    } 
    ~Base2() 
    {cout<<"調用基類2的析構函數"<<endl;} 
}; 
 
 
class Der:public Base1,public Base2{ 
private: 
    int z; 
    Base1 b1,b2; 
public: 
    Der(int a,int b):Base2(a),b1(200),b2(a+b),Base1(20) 
    { 
        z = b; 
        cout<<"調用派生類的構造函數"<<endl; 
    } 
    ~Der() 
    {cout<<"調用派生類的析構函數"<<endl;} 
}; 
 
 
 
 
int main() 

    if(1){ 
       Der d(100,200); 
    } 
    system("pause"); 
    return 0; 
}<SPAN style="FONT-SIZE: 18px"> 
</SPAN> 

#include<iostream>


using namespace std;


class Base1{
    int x;
public:
 Base1(int a)
 {
  x = a;
  cout<<"調用基類1的構造函數"<<endl;
 }
 ~Base1()
 {cout<<"調用基類1的析構函數"<<endl;}
};


class Base2{
 int y;
public:
 Base2(int b)
 {
  y = b;
  cout<<"調用基類2的構造函數"<<endl;
 }
 ~Base2()
 {cout<<"調用基類2的析構函數"<<endl;}
};


class Der:public Base1,public Base2{
private:
 int z;
 Base1 b1,b2;
public:
 Der(int a,int b):Base2(a),b1(200),b2(a+b),Base1(20)
 {
  z = b;
  cout<<"調用派生類的構造函數"<<endl;
 }
 ~Der()
 {cout<<"調用派生類的析構函數"<<endl;}
};

 


int main()
{
 if(1){
    Der d(100,200);
 }
 system("pause");
 return 0;
}

運行結果:

 

3.沖突與支配原則和賦值兼容性

(1)沖突

        一個派生類從多個基類派生而來,當基類中成員的訪問權限設為public,且不同的基類成員就有相同的名字時,就會出現沖突。(這個一定同時繼承的兩個基類中有相同的名字才會沖突,派生類和基類有相同的名字不會沖突)例如:


[html] 
#include<iostream> 
 
using namespace std; 
 
class Base1{ 
    int x; 
public: 
    Base1(int a) 
    {x = a;} 
    void show(){cout<<"x="<<x<<endl;} 
}; 
 
class Base2{ 
    int x; 
public: 
    Base2(int b) 
    {x = b;} 
    void show(){cout<<"x="<<x<<endl;} 
}; 
 
class Der:public Base1,public Base2{ 
private: 
    int z; 
public: 
    void setx(int a){x=a;}  //D 
}; 
 
 
int main() 

    Der d;  
    d.show();               //E 
    return 0; 

#include<iostream>

using namespace std;

class Base1{
    int x;
public:
 Base1(int a)
 {x = a;}
 void show(){cout<<"x="<<x<<endl;}
};

class Base2{
 int x;
public:
 Base2(int b)
 {x = b;}
 void show(){cout<<"x="<<x<<endl;}
};

class Der:public Base1,public Base2{
private:
 int z;
public:
 void setx(int a){x=a;}  //D
};


int main()
{
 Der d;
 d.show();               //E
 return 0;
}
        這時,D行訪問基類中的成員,這是編譯器無法確定是要訪問屬於哪一個類的x,即出現編譯錯誤;同樣的x也會錯誤,編譯器不知道調用哪個基類的show函數。

        解決這種沖突辦法:使各基類中的成員不同;各基類將成員設為私有的,並設置相應的訪問函數,訪問函數不能重名;使用作用域來說明。


[html] 
#include<iostream> 
 
using namespace std; 
 
class Base1{ 
    int x; 
public: 
    Base1(int a) 
    {x = a;} 
    void show(){cout<<"x="<<x<<endl;} 
}; 
 
class Base2{ 
    int x; 
public: 
    Base2(int b) 
    {x = b;} 
    void show(){cout<<"x="<<x<<endl;} 
}; 
 
class Der:public Base1,public Base2{ 
private: 
    int z; 
public: 
    void setAx(int a){Base1:x=a;}  //D 
    void setBx(int a){Base2:x=a;}  //D 
}; 
 
 
int main() 

    Der d;  
    d.Base1::show(); 
    d.Base2::show(); 
    return 0; 

#include<iostream>

using namespace std;

class Base1{
    int x;
public:
 Base1(int a)
 {x = a;}
 void show(){cout<<"x="<<x<<endl;}
};

class Base2{
 int x;
public:
 Base2(int b)
 {x = b;}
 void show(){cout<<"x="<<x<<endl;}
};

class Der:public Base1,public Base2{
private:
 int z;
public:
 void setAx(int a){Base1:x=a;}  //D
 void setBx(int a){Base2:x=a;}  //D
};


int main()
{
 Der d;
 d.Base1::show();
 d.Base2::show();
 return 0;
}

 

(2)作用域不能連續使用。例如,Der又派生一個類,則不能像這樣使用:

[html]
#include<iostream> 
 
using namespace std; 
 
class Base1{ 
    int x; 
public: 
    Base1(int a) 
    {x = a;} 
    void show(){cout<<"x="<<x<<endl;} 
}; 
 
class Base2{ 
    int x; 
public: 
    Base2(int b) 
    {x = b;} 
    void show(){cout<<"x="<<x<<endl;} 
}; 
 
class Der:public Base1,public Base2{ 
private: 
    int z; 
public: 
    void setAx(int a){Base1:x=a;}   
    void setBx(int a){Base2:x=a;}   
}; 
 
class Der1:public Der{ 
private: 
public: 
}; 
 
int main() 

    Der1 d;  
    d.Der1::Base1::show();//這樣的寫法是錯的 
    d.Der1::Base2::show();//這樣的寫法是錯的 
    return 0; 

#include<iostream>

using namespace std;

class Base1{
    int x;
public:
 Base1(int a)
 {x = a;}
 void show(){cout<<"x="<<x<<endl;}
};

class Base2{
 int x;
public:
 Base2(int b)
 {x = b;}
 void show(){cout<<"x="<<x<<endl;}
};

class Der:public Base1,public Base2{
private:
 int z;
public:
 void setAx(int a){Base1:x=a;} 
 void setBx(int a){Base2:x=a;} 
};

class Der1:public Der{
private:
public:
};

int main()
{
 Der1 d;
 d.Der1::Base1::show();//這樣的寫法是錯的
 d.Der1::Base2::show();//這樣的寫法是錯的
 return 0;
}
解決方法:在Der增加成員:

      void showA(){cout<<"x="<<Base1::x<<endl;}
      void showB(){cout<<"x="<<Base2::x<<endl;}


(3)允許派生類的成員和基類的成員重名,但是訪問的時候如果不帶有作用域,在表示訪問派生類中的成員,帶有作用域可以訪問基類的成員。

(4)賦值兼容規則:派生類的對象可以賦值給基類對象,但是丟生了派生了的信息,基類的對象不能賦值給派生類對象;派生類對象的地址可以賦值給基類的指針變量(派生類對象可以賦值給基類的引用)。

 


5.虛基類

(1)同一個基類引起的多份copy

        這樣一個繼承關系,A派生B和C,C又派生出D,這樣D就有A中的兩份copy。如下:


[html]
#include<iostream> 
 
using namespace std; 
 
class A{ 
public: 
    int x; 
    A(int a){x = a;} 
}; 
 
class B:public A{ 
    int y; 
public: 
    B(int a=0,int b=0):A(a){y = b;} 
    void PB(){cout<<"x="<<x<<'\t'<<"y="<<y<<endl;} 
}; 
 
class C:public A{ 
    int z; 
public: 
    C(int a=0,int b=0):A(a){z = b;} 
    void PC(){cout<<"x="<<x<<'\t'<<"z="<<z<<endl;} 
}; 
 
class D:public B,public C{ 
    int m; 
public: 
    D(int a,int b,int d,int e,int f):B(a,b),C(d,e) 
    {m = f;} 
    void PD() 
    { 
        PB();//D   不能換成{cout<<"x="<<x<<'\t'<<"y="<<y<<endl;} 
        PC();//E   不能換成{cout<<"x="<<x<<'\t'<<"z="<<z<<endl;} 
        cout<<"m="<<m<<endl; 
    } 
}; 
 
int main() 

    D d(100,200,300,400,500); 
    d.PD(); 
    return 0; 
}<SPAN style="FONT-SIZE: 18px"> 
</SPAN> 

#include<iostream>

using namespace std;

class A{
public:
    int x;
 A(int a){x = a;}
};

class B:public A{
 int y;
public:
 B(int a=0,int b=0):A(a){y = b;}
 void PB(){cout<<"x="<<x<<'\t'<<"y="<<y<<endl;}
};

class C:public A{
 int z;
public:
 C(int a=0,int b=0):A(a){z = b;}
 void PC(){cout<<"x="<<x<<'\t'<<"z="<<z<<endl;}
};

class D:public B,public C{
 int m;
public:
 D(int a,int b,int d,int e,int f):B(a,b),C(d,e)
 {m = f;}
 void PD()
 {
  PB();//D   不能換成{cout<<"x="<<x<<'\t'<<"y="<<y<<endl;}
  PC();//E   不能換成{cout<<"x="<<x<<'\t'<<"z="<<z<<endl;}
  cout<<"m="<<m<<endl;
 }
};

int main()
{
 D d(100,200,300,400,500);
 d.PD();
 return 0;
}
運行結果:


注:D行和E行不能換成相應的語句,這樣會引起沖突,必須采用前面作用域的解決方法。

 


(2)虛繼承

       在多重派生的過程中,若欲使公共的基類在派生類中只有一份copy,可以將這種基類說明為虛基類。在派生的定義中,在基類的名字前面加上virtual關鍵字。

      在類的對象中都一個虛表指針,指向虛表(每個類含有一個虛表,續表中存放特定類的虛函數地址),虛表裡存放了虛函數的地址。虛函數表裡順序存放虛函數地址的,不是鏈表存儲的。在每個帶虛函數的類中,編譯器秘密設置一指針,稱為vpoionter,指向這個對象的虛表,通過基類指針調用的時候靜態插入取得這個指針,並在表中查找出函數地址的代碼。《寶典p131》


[html]
#include<iostream> 
 
using namespace std; 
 
class A{ 
public: 
    int x; 
    A(int a=0){x = a;} 
}; 
 
class B:virtual public A{ 
    int y; 
public: 
    B(int a=0,int b=0):A(a){y = b;} 
    void PB(){cout<<"x="<<x<<'\t'<<"y="<<y<<endl;} 
}; 
 
class C:public virtual A{ 
    int z; 
public: 
    C(int a=0,int b=0):A(a){z = b;} 
    void PC(){cout<<"x="<<x<<'\t'<<"z="<<z<<endl;} 
}; 
 
class D:public B,public C{ 
    int m; 
public: 
    D(int a,int b,int d,int e,int f):B(a,b),C(d,e) 
    { 
        m = f; 
    } 
    void PD() 
    { 
        PB(); 
        PC(); 
        cout<<"m="<<m<<endl; 
    } 
}; 
 
int main() 

    D d(100,200,300,400,500);//D 
    d.PD(); 
    d.x=400;                 //E 
    d.PD(); 
    return 0; 

#include<iostream>

using namespace std;

class A{
public:
    int x;
 A(int a=0){x = a;}
};

class B:virtual public A{
 int y;
public:
 B(int a=0,int b=0):A(a){y = b;}
 void PB(){cout<<"x="<<x<<'\t'<<"y="<<y<<endl;}
};

class C:public virtual A{
 int z;
public:
 C(int a=0,int b=0):A(a){z = b;}
 void PC(){cout<<"x="<<x<<'\t'<<"z="<<z<<endl;}
};

class D:public B,public C{
 int m;
public:
 D(int a,int b,int d,int e,int f):B(a,b),C(d,e)
 {
  m = f;
 }
 void PD()
 {
  PB();
  PC();
  cout<<"m="<<m<<endl;
 }
};

int main()
{
 D d(100,200,300,400,500);//D
 d.PD();
 d.x=400;                 //E
 d.PD();
 return 0;
} 執行結果:

 


        首先在派生類D的對象d中只有基類的一個拷貝,因為改變x的值,輸出時相同的;

       其次,x的初值為0,雖然在執行D行時調用了類D的構造函數,而x的初值仍然為0,為什麼呢?因為調用虛基類的構造函數方法與一般的構造函數不同。由於虛基類經過一次或多次派生出來的派生類,每一個派生類中的構造函數中都調用了基類的構造函數。在本例中,B和C都調用了A的構造函數對A的成員進行初始化,這樣編譯器就無法確定是用B的構函數還是C的構造函數來初始化A中的變量。於是,編譯器調用A的默認構造函數來初始化A中的變量,也就是說A中必須有默認的構造函數,或者編譯器將報錯。

        再次強調:用虛基類進行多重派生時,若虛基類沒有缺省的構造函數,則派生類的每一個派生類的構造函數的初始化成員列表中都必須有對虛基類構造函數的調用。

 


6.一些例子《面試寶典P123》

(1)范例1


[cpp] 
#include<iostream>  
 
using namespace std; 
 
class A{ 
protected: 
    int x; 
public: 
    A(int a=0):x(a){}; 
    int getData()const 
    { 
        return doGetData(); 
    } 
    virtual int doGetData() const  
    { 
        return x; 
    } 
}; 
 
class B:public A{ 
protected: 
    int x; 
public: 
    B(int a=1):x(a){}; 
    int doGetData()const 
    { 
        return x; 
    } 
}; 
 
class C:public B{ 
protected: 
    int x; 
public: 
    C(int a=2):x(a){}; 
}; 
 
 
int main() 

    C c(10);                        //1  
    cout<<c.getData()<<endl;        //2  
    cout<<c.A::getData()<<endl;     //3  
    cout<<c.B::getData()<<endl;     //4  
    cout<<c.C::getData()<<endl;     //5  
    cout<<c.doGetData()<<endl;      //6  
    cout<<c.A::doGetData()<<endl;   //7  
    cout<<c.B::doGetData()<<endl;   //8  
    cout<<c.C::doGetData()<<endl;   //9  
    system("pause"); 
    return 0; 

#include<iostream>

using namespace std;

class A{
protected:
 int x;
public:
 A(int a=0):x(a){};
 int getData()const
 {
  return doGetData();
 }
 virtual int doGetData() const
 {
  return x;
 }
};

class B:public A{
protected:
 int x;
public:
    B(int a=1):x(a){};
 int doGetData()const
 {
  return x;
 }
};

class C:public B{
protected:
 int x;
public:
 C(int a=2):x(a){};
};


int main()
{
    C c(10);                        //1
    cout<<c.getData()<<endl;        //2
    cout<<c.A::getData()<<endl;     //3
    cout<<c.B::getData()<<endl;     //4
    cout<<c.C::getData()<<endl;     //5
    cout<<c.doGetData()<<endl;      //6
    cout<<c.A::doGetData()<<endl;   //7
    cout<<c.B::doGetData()<<endl;   //8
    cout<<c.C::doGetData()<<endl;   //9
    system("pause");
    return 0;
}
運行結果 :1 1 1 1 1 0 1 1

解析:

        //1:首先調用A類的構造函數,再調用B類構造函數,最後調用自己的構造函數。

        //2:調用C中的getData,在C中未定義,於是調用基類B的getData,發現基類B也沒有這個函數,繼續往上走,則調用A中的getData。getData調用了虛函數doGetData,於是首先搜索C有沒有定義這個函數,發現沒定義,於是搜尋C的基類B有沒有定義該虛函數,發現定義了,則調用B::doGetData。在B中的 doGetData返回的是B類的x,所以打印1。

        //3:調用A::getData。後面分析同上紅色地方。

        //4:直接調用B::getData,然後B沒有,轉到B的基類A,所以調用A::getData。後面的分析同上紅色的地方。


        //5:同//2。

        //6:調用C類的虛函數doGetData,C中未定義,於是就轉到C類的基類B的goGetData,所以打印出1。

        //7:直接調用A::doGetData,所以這次虛函數沒有動態綁定了,直接打印出0。
        //8:直接調用B::doGetData,所以這次虛函數沒有動態綁定了,直接打印出1。

        //9:直接調用 C::doGetData,但是C沒有定義該虛函數,於是轉到基類B,又打印出1。


注:這裡有一個規則就是就近調用,及父輩如果有相關接口則調用父輩接口,父輩沒有則轉到祖父輩接口。

(2)范例2《面試寶典P125》


[cpp]
class A{ 
public: 
    virtual void fun(); 

class B:public A{ 
public: 
    virtual void fun(); 

void main(){ 
     A* pa = new A(); 
     B* pb = (B*)pa;  //1  

class A{
public:
    virtual void fun();
}
class B:public A{
public:
    virtual void fun();
}
void main(){
     A* pa = new A();
     B* pb = (B*)pa;  //1
}

     //1 最終的結果還是運行A::fun,因為雖然轉換了指針類型,但是pa指向的內容始終沒有發生變化。

 

 

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