C++基礎之繼承

1、類的繼承與類的派生

  • 繼承和派生是人們認識客觀世界的過程。在程序設計方法中,人們追求代碼複用(這是提升軟件開發效率的重要手段),將繼承和派生用於程序設計方法中,從而有了面向對象程序設計的重要特色。C++對代碼複用有很強的支持,
  • 「繼承」就是支持代碼複用的機制之一。
  • 經過已有的類創建新類的過程,叫做類的派生。原來的類稱爲基類,也稱爲父類或通常類;新類稱爲派生類,也稱爲子類或特殊類。派生類派生自基類,或繼承於基類,也能夠說基類派生了派生類。派生機制是C++語言及面向對象程序設計方法的重要特徵之一。派生類能夠再做爲基類派生新的派生類,由此基類和派生類的集合稱做類繼承層次結構。

繼承:ios

  • 使用基類派生新類時,除構造函數和析構函數外,基類的全部成員自動成爲派生類的成員,包括基類的成員變量和成員函數。同時,派生類能夠增長基類中沒有的成員,這一樣是指成員變量和成員函數。能夠從新定義或修改基類中已有的成員,包括能夠改變基類中成員的訪問權限。固然派生類須要定義本身的構造函數和析構函數。
  • 使用基類成員是一個重用的過程,在基類之上進行調整,不管是添加新成員仍是改造己有的,都是擴充的過程。

覆蓋:函數

  若派生類中定義了一個與基類中同名的成員,則會出現基類與派生類有同名成員的狀況,這是容許的。同名的成員既能夠是成員變量,也能夠是成員函數。這種狀況下,若在派生類的成員函數中訪問這個同名成員,或經過派生類對象訪問這個同名成員時,除非特別指明,訪問的就是派生類中的成員,這種狀況叫「覆蓋」,即派生類的成員覆蓋基類的同名成員。覆蓋也稱爲重定義或是重寫。對於成員函數來講,派生類既繼承了基類的同名成員函數,又在派生類中重寫了這個成員函數。這稱爲函數重定義,也稱爲同名隱藏。「隱藏」的意思是指,使用派生類對象調用這個名字的成員函數時,調用的是派生類中定義的成員函數,即隱藏了基類中的成員函數。spa

派生類能夠改變基類中成員的訪問權限
空類也能夠做爲基類,也就是說,空類能夠派生子類:設計

class emptyClass{ }; //空基類
class subemptyClass : public emptyClass{ }; //派生類

類的大小指針

  • 派生類對象中包含基類成員變量,並且基類成員變量的存儲位置位於派生類對象新增的成員變量以前。派生類對象佔用的存儲空間大小,等於基類成員變量佔用的存儲空間大小加上派生類對象自身成員變量佔用的存儲空間大小。
  • 對象佔用的存儲空間包含對象中各成員變量佔用的存儲空間。出於計算機內部處理效率的考慮,爲變量分配內存時,會根據其對應的數據類型,在存儲空間內對變量的起始地址進行邊界對齊。可使用sizeof( )函數計算對象佔用的字節數。
  • 對象的大小與普通成員變量有關,與成員函數和類中的靜態成員變量無關,即普通成員函數、靜態成員函數、靜態成員變量、靜態常量成員變量等均對類對象的大小沒有影響。

2、繼承關係的特殊性

一、友元code

若是基類有友元類或友元函數,則其派生類不會因繼承關係而也有此友元類或友元函數。若是基類是某類的友元,則這種友元關係是被繼承的。即被派生類繼承過來的成員函數,若是原來是某類的友元函數,那麼它做爲派生類的成員函數仍然是某類的友元函數。總之,基類的友元不必定是派生類的友元;基類的成員函數是某類的友元函數,則其做爲派生類繼承的成員函數還是某類的友元函數。對象

#include<iostream>
using namespace std;
class another; //前向引用聲明
//基類
class Base {
    private:
      float x;
    public:
      void print(const another &K);
};
//派生類
class Derived:public Base {
    private:
        float y;
};
 //其餘類
class another{private:
    int aaa;
public:
    another(){
        aaa=100;
    }
friend void Base::print(const another &K);//基類的成員函數聲明爲本類的友元
};
void Base::print(const another &K){
    cout<<"Base:"<<K.aaa<<endl; //能夠訪問私有成員變量
}
int main(){
    Base a;
    Derived d;
    another ano; //aaa 初始化爲100
    a.print(ano); //輸出爲:Base:100
    d.print(ano); //輸出爲:Base:100
    return 0;
}

二、靜態屬性blog

若是基類中的成員是靜態的,則在其派生類中,被繼承的成員也是靜態的,即其靜態屬性隨靜態成員被繼承。
若是基類的靜態成員是公有的或是保護的,則它們被其派生類繼承爲派生類的靜態成員。訪問這些成員時,一般用「<類名>::<成員名>」的方式引用或調用。不管有多少個對象被建立,這些成員都只有一個拷貝,它爲基類和派生類的全部對象所共享。繼承

#include<iostream>
using namespace std;
// 基類
class Base {
    private:
        float x;
    public:
        static int staV;
    Base(){ 
        staV++;
    }
};
int Base::staV=0;
//派生類
class Derived: public Base {
    private:
        float y;
    public:
        Derived( ){ 
            staV++;
        }
};
int main(){
    Base a;
    cout<< "Base:" <<a.staV<<endl; //輸出1
    Derived d;
    cout<< "Derived:" << d.staV<<endl; //輸出3(建立子類對象會調用父類構造器,而後調用自身的構造器)
    return 0;
}

 

 

 三、繼承之間的訪問關係內存

  • 派生類和基類中均可以定義本身的成員變量和成員函數,派生類中的成員函數能夠訪問基類中的公有成員變量,但不能直接訪問基類中的私有成員變量。也就是說,不能在派生類的函數中,使用「基類對象名.基類私有成員函數(實參)」,或是「基類對象名.基類私有成員變量」,或是「基類名::基類私有成員」的形式訪問基類中的私有成員。
  • 在類的派生層次結構中,基類的成員和派生類新增的成員都具備類做用域。兩者的做用範圍不一樣,是相互包含的兩個層,派生類在內層,基類在外層。若是派生類聲明瞭一個和基類某個成員同名的新成員,派生的新成員就隱藏了外層同名成員,直接使用成員名只能訪問到派生類的成員。若是派生類中聲明瞭與基類成員函數同名的新函數,即便函數的參數表不一樣,從基類繼承的同名函數的全部重載形式也都會被隱藏。若是要訪問被隱藏的成員,就須要使用基類名和做用域分辨符來限定。
#include<iostream>
using namespace std;
class CB{
    public:
        int a;
        CB(int x){
            a=x;
        }
        void showa(){
            cout<<"Class CB--a="<<a<<endl;
        }
};
class CD:public CB{
    public:
        int a; //與基類a同名
        //x用來初始化基類的成員變量a
        CD(int x,int y):CB(x) { 
            a=y; 
        }
        //與基類showa同名        
        void showa() {
            cout<<"Class CD--a="<<a<<endl; 
        }
        //訪問派生類a            
        void print2a(){ 
            cout<<"a=" << a<<endl; 
            //訪問基類a
            cout<<"CB::a="<<CB::a<<endl;
        }
};
int main(){
    CB CBobj(12);
    CBobj.showa();//Class CB--a=12
    CD CDobj(48,999);
    CDobj.showa(); //訪問派生類的showa ()  Class CD--a=999
    CDobj.CB::showa(); //訪問基類的showa ()  Class CB--a=48
    cout<<"CDobj.a="<<CDobj.a<<endl; // CDobj.a=999
    cout<<"CDobj.CB::a="<<CDobj.CB::a<<endl;//CDobj.CB::a=48
}

四、protected訪問範圍說明符

  • 定義類時,類成員可使用protected訪問範圍說明符進行修飾,從而成爲「保護成員」。保護成員的訪問範圍比私有成員的訪問範圍大,能訪問私有成員的地方都能訪問保護成員。此外,基類中的保護成員能夠在派生類的成員函數中被訪問。
  • 在基類中,通常都將須要隱藏的成員說明爲保護成員而非私有成員。將基類中成員變量的訪問方式修改成protected後,在派生類中能夠直接訪問。

3、多重繼承

  • C++容許從多個類派生一個類,即一個派生類能夠同時有多個基類。這稱爲多重繼承。相應地,從一個基類派生一個派生類的狀況,稱爲單繼承或單重繼承
  • 派生類繼承了基類名一、基類名二、……、基類名n的全部成員變量和成員函數,各基類名前面的繼承方式說明符用於限制派生類中的成員對該基類名中成員的訪問權限,其規則與單繼承狀況同樣。
  • 多重繼承狀況下若是多個基類間成員重名時,按以下方式進行處理:對派生類而言,不加類名限定時默認訪問的是派生類的成員;而要訪問基類重名成員時,要經過類名加以限定。
#include<iostream>
using namespace std;
class CB1{
    public:
        int a; 
        CB1 (int x){ a=x; }
        void showa(){ cout<<"ClassCB1==>a="<<a<<endl; }
};
class CB2{
    public:
        int a; 
        CB2 (int x){ a=x; }
        void showa(){ cout<<"ClassCB1==>a="<<a<<endl; }
};
//多重繼承,兩個基類
class CD:public CB1,public CB2 {
public:
    int a; //與兩個基類成員變量a重名
    CD(int x,int y,int z):CB1(x) ,CB2(y){ a=z; }
    //與兩個基類成員函數showa()重名
    void showa() { 
        cout<<"Class CD==>a="<<a<<endl; 
    }
    void print3a(){ 
        cout<<"a="<<a<<endl;
        cout<<"CB1::a="<<CB1::a<<endl;
        cout<<"CB2::a="<<CB2::a<<endl;
    }
};
int main(){
    CB1 CB1obj(11);
    CB1obj.showa();//ClassCB1==>a=11
    CD CDobj(101,202,909);
    CDobj.showa(); //調用派生類的showa()  //Class CD==>a=909
    CDobj.CB1::showa(); //調用基類的showa() //ClassCB1==>a=101
    cout<<"CDobj.a="<<CDobj.a<<endl;//訪問派生類成員a  //CDobj.a=909
    cout<<"CDobj.CB2::a="<<CDobj.CB2::a<<endl; //CDobj.CB2::a=202
}

二、多重繼承的二義性

⚫ 若是派生類中新增了同名成員,則派生類成員將隱藏全部基類的同名成員。使用「派生類對象名.成員名」或「派生類對象指針->成員名」的方式能夠惟一標識和訪問派生類新增成員。這種狀況下,不會產生二義性。
⚫ 若是派生類中沒有新增同名成員,當知足訪問權限時,使用「派生類對象名.成員名」或「派生類對象指針->成員名」方式時,系統沒法判斷究竟是調用哪一個基類的成員,從而產生二義性。爲了不二義性,必須經過基類名和做用域分辨符來標識成員。
⚫ 當要訪問派生類對象中的某個變量時,添加「基類::」做爲前綴,指明須要訪問從哪一個基類繼承來的,從而能夠排除二義性。

三、繼承權限控制

設計繼承類時,須要使用繼承方式說明符指明派生類的繼承方式。繼承方式說明符能夠是public(公有繼承)、private(私有繼承)或protected(保護繼承)

 

 

 4、類型兼容

⚫類型兼容規則是指在須要基類對象的任何地方,均可以使用公有派生類的對象來替代,也稱爲賦值兼容規則。在公有派生的狀況下,有如下3條類型兼容規則。

  • 1)派生類的對象能夠賦值給基類對象。
  • 2)派生類對象能夠用來初始化基類引用。
  • 3)派生類對象的地址能夠賦值給基類指針,即派生類的指針能夠賦值給基類的指針。

⚫上述3條規則反過來是不成立的。例如,不能把基類對象賦值給派生類對象。在進行替代以後,派生類對象就能夠做爲基類的對象使用了,但只能使用從基類繼承的成員。

⚫若是類B爲基類,類D爲類B的公有派生類,則類D中包含了基類B中除構造函數、析構函數以外的全部成員。這時,根據類型兼容規則,在基類B的對象能夠出現的任何地方,均可以用派生類D的對象來替代。假設有如下的聲明:

class B{…}
class D : public B{…}
B b1, *pb1;
D d1;

⚫這時,派生類對象能夠隱含轉換爲基類對象,即用派生類對象中從基類繼承來的成員變量的值,逐個爲基類對象的成員變量的值進行賦值。b1=d1;
⚫派生類的對象也能夠用來初始化基類對象的引用,即:B &rb=d1;
⚫派生類對象的地址能夠隱含轉換爲指向基類的指針,即派生類對象的地址賦給基類指針:pb1=&d1;
⚫因爲類型兼容規則的引入,對於基類及其公有派生類的對象,可使用相同的函數統一進行處理。由於當函數的形參爲基類的對象(或引用、指針)時,實參能夠是派生類的對象(或指針),從而沒有必要爲每個類設計單獨的模塊,大大提升了程序的效率。

5、私有繼承

 

 6、保護繼承

  保護繼承中,基類的公有成員和保護成員都以保護成員的身份出如今派生類中,而基類的私有成員不能夠直接訪問。這樣,派生類的其餘成員能夠直接訪問從基類繼承來的公有和保護成員,但在類外經過派生類的對象沒法直接訪問它們。

class A{};
class B:private A{};//私有繼承
class C:protected A{};//保護繼承
相關文章
相關標籤/搜索