淺談C++類的繼承與派生的關係

一、基本概念

1、類的繼承,是新的類從已有類那裏得到已有的特性。或從已有類產生新類的過程就是類的派生。原有的類稱爲基類或父類,產生的新類稱爲派生類或子類。
 
2、派生類的聲明:
class 派生類名:繼承方式 基類名1, 繼承方式 基類名2,...,繼承方式 基類名n
{
    派生類成員聲明;
};
 
3、一個派生類可以同時有多個基類,這種情況稱爲多重繼承,派生類只有一個基類,稱爲單繼承。直接派生,間接派生。
 
4、繼承方式規定了如何訪問基類繼承的成員。繼承方式有public, private, protected。如果不顯示給出繼承方式,默認爲private繼承。繼承方式指定了派生類成員以及類外對象對於從基類繼承來的成員的訪問權限。
 
5、派生類繼承基類中除構造和析構函數以外的所有成員。
 
6、派生類生成:
   吸收基類成員(除構造析構函數以外的所有成員);
   改造基類成員(根據繼承方式調整基類成員的訪問,函數在子類中的覆蓋,以及虛函數在子類中的覆蓋);
   添加新的成員;
 
7、公有繼承
當類的繼承方式爲公有繼承時,基類的公有和保護成員的訪問屬性在派生類中不變,而基類的私有成員不可訪問。即基類的公有成員和保護成員被繼承到派生類中仍作爲派生類的公有成員和保護成員。派生類的其他成員可以直接訪問它們。無論派生類的成員還是派生類的對象都無法訪問基類的私有成員。
 
8、私有繼承
當類的繼承方式爲私有繼承時,基類中的公有成員和保護成員都以私有成員身份出現在派生類中,而基類的私有成員在派生類中不可訪問。基類的公有成員和保護成員被繼承後作爲派生類的私有成員,派生類的其他成員可以直接訪問它們,但是在類外部通過派生類的對象無法訪問。無論是派生類的成員還是通過派生類的對象,都無法訪問從基類繼承的私有成員。通過多次私有繼承後,對於基類的成員都會成爲不可訪問。因此私有繼承比較少用。
 
9、保護繼承
保護繼承中,基類的公有成員和私有成員都以保護成員的身份出現在派生類中,而基類的私有成員不可訪問。派生類的其他成員可以直接訪問從基類繼承來的公有和保護成員,但是類外部通過派生類的對象無法訪問它們,無論派生類的成員還是派生類的對象,都無法訪問基類的私有成員。
 
二、派生類的構造函數和析構函數
1、派生類中由基類繼承而來的成員的初始化工作還是由基類的構造函數完成,然後派生類中新增的成員在派生類的構造函數中初始化。
 
2、派生類構造函數的語法:
派生類名::派生類名(參數總表):基類名1(參數表1),基類名(參數名2)....基類名n(參數名n),內嵌子對象1(參數表1),內嵌子對象2(參數表2)....內嵌子對象n(參數表n)
{
    派生類新增成員的初始化語句;
}
注:構造函數的初始化順序並不以上面的順序進行,而是根據聲明的順序初始化。
 
3、如果基類中沒有不帶參數的構造函數,那麼在派生類的構造函數中必須調用基類構造函數,以初始化基類成員。
 
4、派生類構造函數執行的次序:
   調用基類構造函數,調用順序按照它們被繼承時聲明的順序(從左到右);
   調用內嵌成員對象的構造函數,調用順序按照它們在類中聲明的順序;
   派生類的構造函數體中的內容。
例子:
 
複製代碼
#include <iostream>
#include <time.h>
using namespace std;

class B1
{
public:
    B1(int i)
    {
        cout<<"constructing B1 "<<i<<endl;
    }
};

class B2
{
public:
    B2(int j)
    {
        cout<<"constructing B2 "<<j<<endl;
    }
};

class B3
{
public:
    B3()
    {
        cout<<"constructing B3"<<endl;
    }
};

class C: public B2, public B1, public B3
{
public:
    C(int a, int b, int c, int d):B1(a), memberB2(d), memberB1(c),B2(b)
    {

    }
private:
    B1 memberB1;
    B2 memberB2;
    B3 memberB3;
};

int main() 
{ 
    C obj(1,2,3,4);

    return 0; 
}
複製代碼

 

 
輸出結果爲:
constructing B2 2
constructing B1 1
constructing B3
constructing B1 3
constructing B2 4
constructing B3
 
5、析構函數
派生類的析構函數的功能是在該對象消亡之前進行一些必要的清理工作,析構函數沒有類型,也沒有參數。析構函數的執行順序與構造函數相反。
例子:
複製代碼
#include <iostream>
#include <time.h>
using namespace std;

class B1
{
public:
    B1(int i)
    {
        cout<<"constructing B1 "<<i<<endl;
    }
    ~B1()
    {
        cout<<"destructing B1"<<endl;
    }
};

class B2
{
public:
    B2(int j)
    {
        cout<<"constructing B2 "<<j<<endl;
    }
    ~B2()
    {
        cout<<"destructing B2"<<endl;
    }
};

class B3
{
public:
    B3()
    {
        cout<<"constructing B3"<<endl;
    }
    ~B3()
    {
        cout<<"destructing B3"<<endl;
    }
};

class C: public B2, public B1, public B3
{
public:
    C(int a, int b, int c, int d):B1(a), memberB2(d), memberB1(c),B2(b)
    {

    }
private:
    B1 memberB1;
    B2 memberB2;
    B3 memberB3;
};

int main() 
{ 
    C obj(1,2,3,4);

    return 0; 
}
複製代碼

 

輸出結果爲:
constructing B2 2
constructing B1 1
constructing B3
constructing B1 3
constructing B2 4
constructing B3
destructing B3
destructing B2
destructing B1
destructing B3
destructing B1
destructing B2
 
三、派生類成員的標識和訪問
1、派生類成員屬性劃分爲四種:
   不可訪問的成員;私有成員;保護成員;公有成員;
 
2、作用域分辨
形式爲:基類名::成員名;基類名::成員名(參數表);
如果某派生類的多個基類擁有同名的成員,同時,派生類又新增這樣的同名成員,在這種情況下,派生類成員將覆蓋所有基類的同名成員。這就需要這樣的調用方式才能調用基類的同名成員。
例子:多繼承同名 
 
複製代碼
#include <iostream>
#include <time.h>
using namespace std;

class B1
{
public:
    int nV;
    void fun()
    {
        cout<<"member of B1 "<<nV<<endl;
    }
};

class B2
{
public:
    int nV;
    void fun()
    {
        cout<<"member of B2 "<<nV<<endl;
    }
};

class D1: public B1, public B2
{
public:
    int nV;
    void fun()
    {
        cout<<"member of D1 "<<nV<<endl;
    }
};

int main() 
{ 
    D1 d1;
    d1.nV = 1;
    d1.fun();
    d1.B1::nV = 2;
    d1.B1::fun();
    d1.B2::nV = 3;
    d1.B2::fun();

    return 0; 
}
複製代碼

 

輸出結果爲:
member of D1 1
member of B1 2
member of B2 3
以上通過作用域分辨符,解決了訪問基類中被屏蔽的同名成員。
 
3、如果某個派生類的部分或全部直接基類是從另一個共同的基類派生而來,在這些直接基類中,從上一級基類繼承來的成員就擁有相同的名稱,因此派生類中也就會產生同名現象,對這種類型的同名成員也要使用作用域分辨符來唯一標識,而且必須用直接基類進行限定。
例子:
複製代碼
#include <iostream>
#include <time.h>
using namespace std;

class B0
{
public:
    int nV;
    void fun()
    {
        cout<<"member of B0 "<<nV<<endl;
    }
};

class B1:public B0
{
public:
    int nV1;
};

class B2:public B0
{
public:
    int nV2;
};

class D1:public B1, public B2
{
public:
    int nVd;
    void fund()
    {
        cout<<"member of D1"<<endl;
    }
};

int main() 
{ 
    D1 d1;
    d1.B1::nV = 2;
    d1.B1::fun();
    d1.B2::nV = 3;
    d1.B2::fun();

    return 0; 
}
複製代碼

 

輸出結果爲:
member of B0 2
member of B0 3
在這種情況下,派生類對象在內存中就同時擁有成員nV及fun的兩份拷貝。但是很多情況下,我們只需要這樣一個這樣的數據拷貝,同一成員的多份拷貝增加了內存的開銷。可以通過虛函數來解決這個問題。
 
4、虛基類
爲了解決前面提到的多重拷貝的問題,可以將共同基類設置爲虛基類,這時從不同的路徑繼承過來的同名數據成員在內存中就只有一個拷貝,同一個函數也只有一個映射。
虛基類的聲明是在派生類的聲明過程,其語法形式爲:
class 派生類名::virtual 繼承方式 基類名;
例子:
複製代碼
#include <iostream>
#include <time.h>
using namespace std;

class B0
{
public:
    int nV;
    void fun()
    {
        cout<<"member of B0 "<<nV<<endl;
    }
};

class B1:virtual public B0
{
public:
    int nV1;
};

class B2:virtual public B0
{
public:
    int nV2;
};

class D1:public B1, public B2
{
public:
    int nVd;
    void fund()
    {
        cout<<"member of D1"<<endl;
    }
};

int main() 
{ 
    D1 d1;
    d1.nV = 2;
    d1.fun();

    return 0; 
}
複製代碼

 

輸出結果爲:
member of B0 2
 
5、虛基類及其派生類的構造函數
一般而言,派生類只對其直接基類的構造函數傳遞參數,但是在虛基類中,不管是直接或間接虛基類的所有派生類,都必須在構造函數的成員初始化列表中列出對虛基類的初始化。
例子:
複製代碼
#include <iostream>
#include <time.h>
using namespace std;

class B0
{
public:
    B0(int n)
    {
        nV = n;
    }
    int nV;
    void fun()
    {
        cout<<"member of B0 "<<nV<<endl;
    }
};

class B1:virtual public B0
{
public:
    B1(int a):B0(a)   
    {
    }
    int nV1;
};

class B2:virtual public B0
{
public:
    B2(int a):B0(a)
    {
    }
    int nV2;
};

class D1:public B1, public B2
{
public:
    D1(int a):B0(a), B1(a), B2(a)
    {
    }
    int nVd;
    void fund()
    {
        cout<<"member of D1"<<endl;
    }
};

int main() 
{ 
    D1 d1(1);
    d1.nV = 2;
    d1.fun();

    return 0; 
}
複製代碼

 

以上例子看上去B0的構造函數好像被調用了三次,但是實際上只有D1類中的D1(int a):B0(a), B1(a), B2(a)
纔是真正的調用了B0構造函數。
 
四、賦值兼容規則
1、賦值兼容規則是指在需要基類對象的任何地方都可以使用公有派生類的對象來替代。
2、賦值兼容規則中所指的替代包括:
   派生類的對象可以賦值給基類對象;
   派生類的對象可以初始化基類的引用;
   派生類對象的地址可以賦給指向基類的指針。
   在替代之後,派生類對象就可以作爲基類的對象使用,但只能使用從基類繼承的成員。
例子:
複製代碼
#include <iostream>
#include <time.h>
using namespace std;

class B0
{
public:
    void display()
    {
        cout<<"B0::display()"<<endl;
    }
};

class B1:public B0
{
public:
    void display()
    {
        cout<<"B1::display()"<<endl;
    }
};

class B2:public B0
{
public:
    void display()
    {
        cout<<"B2::display()"<<endl;
    }
};

void fun(B0 *ptr)
{
    ptr->display();
}

int main() 
{ 
    B0 b0;
    B1 b1;
    B2 b2;
    fun(&b0);
    b0 = b1;
    fun(&b0);
    b0 = b2;
    fun(&b0);

    return 0; 
}
複製代碼

 

 

輸出結果爲:

B0::display()

B0::display()

B0::display()

通過這種賦值兼容後,每次調用的同名函數都是基類的同名函數,如果想調用派生類的,則需要使用虛函數。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章