1. 程式人生 > >C++面向物件的程式設計——繼承

C++面向物件的程式設計——繼承

  首先讓我們瞭解一個面向物件程式設計的4個主要特點:抽象,封裝,繼承和多型。其中繼承性和派生性是面向物件程式設計的兩個重要特性。

1.什麼是繼承:

   在C++中繼承就是在一個已存在類的基礎上建立一個新類,然後新類可以從已有類那裡獲得它已有的特性。

   繼承可以提高程式碼的複用率。

class Base
{
public:
	int a;
	int b;
};
class Derived :public Base
{
public:
	int c;
};
  在這裡首先定義了一個基類Base它有兩個公有成員a,b。然後通過Base類派生出一個新類Derived,稱它為Base的派生類,那麼Derived就繼承了Base類的兩個公有成員a,b與此同時又添加了屬於自己的成員c。
2.派生類的宣告:

  class 派生類名:[繼承方式] 基類名

  {

             派生類新增加的成員

  };

  其中繼承方式又包括:public(公有的),private(私有的)和protected(受保護的)三種。

3.繼承關係與訪問限定符:


3.1.公有繼承派生類的訪問屬性:

 

3.2.私有繼承派生類的訪問屬性:


3.3.保護繼承派生類的訪問屬性:


總結:

1.基類的private成員在派生類中是不能被訪問的,如果基類成員不想在類外直接被訪問,但需要在派生類中能訪問,就定義為protected。可以看出保護成員限定符是因繼承才出現的。

2.public繼承是一個介面繼承,保持is-a原則,每個父類可用的成員對子類也可用,因為每個子類物件也都是一個父類物件。

3.protetced/private繼承是一個實現繼承,基類的部分成員並非完全成為子類介面的一部分,是 has-a 的關係原則,所以非特殊情況下不會使用這兩種繼承關係,在絕大多數的場景下使用的都是公有繼承。

4.不管是哪種繼承方式,在派生類內部都可以訪問基類的公有成員和保護成員,基類的私有成員存在但是在子類中不可見(不能訪問)。

5.使用關鍵字class時預設的繼承方式是private,使用struct時預設的繼承方式是public,不過最好顯示的寫出繼承方式。

6.在實際運用中一般使用都是public繼承,極少場景下才會使用protetced/private繼承.

4.派生類的預設建構函式:

4.1派生類中建構函式的呼叫順序:


通過一個例子來驗證:

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

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

class Derived :private Base
{
public:
	Derived()
	{
		cout << "Derived()" << endl;
	}
private:
	C c;
};
int main()
{
	Derived d;
	system("pause");
	return 0;
}
執行結果為:

  顯而易見的程式在呼叫派生類的構造之前首先呼叫的是基類的建構函式,然後進入自己的建構函式構造自己的的成員物件c,與此同時呼叫C類的建構函式。當派生類中的所有成員物件建立完成後,最後執行自己的建構函式體。

【注意】

1.基類沒有預設建構函式,派生類必須要在初始化列表中顯式給出基類名和引數列表。

2.基類沒有定義建構函式,則派生類也可以不用定義,全部使用預設建構函式。

3.基類定義了帶有形參表建構函式,派生類就一定定義建構函式。

4.2.派生類中解構函式的呼叫順序:

  派生類中解構函式的呼叫規則跟普通類中的呼叫規則完全相同,先建立的物件後析構,後建立的物件先析構。

class Base
{
public:
	Base()
	{
		cout << "Base()" << endl;
	}
	~Base()
	{
		cout << "~Base" << endl;
	}
};
class A
{
public:
	A()
	{
		cout << "A()" << endl;
	}
	~A()
	{
		cout << "~A()" << endl;
	}
};
class C
{
public:
	C()
	{
		cout << "C()" << endl;
	}
	~C()
	{
		cout << "~C()" << endl;
	}
};

class Derived :public Base,public A
{
public:
	Derived()
	{
		cout << "Derived()" << endl;
	}
	~Derived()
	{
		cout << "~Derived()" << endl;
	}
private:
	C c;
};
void Test()
{
	Derived d;
}
int main()
{
	Test();
	system("pause");
	return 0;
}
執行結果為:

【同名隱藏】:

  在繼承體系中基類和派生類屬於兩個不同的作用域。當基類和派生類中出現同名的函式時,如果通過一個派生類物件來呼叫此函式,則系統自動呼叫派生類中的這個函式,而隱藏基類中與之同名的函式(雖然派生類繼承了基類的這個函式)。把這種現象叫做同名隱藏。

  但是在派生類中可以通過 派生類物件.基類::成員函式 這種形式來讓派生類物件訪問基類中成員函式。

注意:

1.在實際中在繼承體系裡面最好不要定義同名的成員。

2.區分函式過載,同名隱藏以及多型內容中的重寫(覆蓋)。

5.基類與派生類的轉換(賦值相容規則)

1. 派生類物件可以賦值給基類物件(切割/切片)
2. 基類物件不能賦值給派生類物件
3. 基類的指標/引用可以指向派生類物件
4. 派生類的指標/引用不能指向基類物件(可以通過強制型別轉換完成)

        Base b;             //定義基類物件b
	Derived d;          //定義基類物件d
	b = d;              //用派生類物件d對基類物件b賦值
  但是,賦值後不能通過物件b去訪問派生類物件d的成員,因為b的成員與d的成員是不同的。
        Base b;
	Derived d;
	Base *pb = &b;       //使基類指標指向基類物件b
	pb = &d;             //使基類指標指向派生類物件d
	Derived *pd = &d;    //使派生類指標指向派生類物件d
	pd = (Derived*)&b;   //使派生類指標指向基類物件b   需要強制型別轉換, pd = &b出現型別錯誤
【友元與繼承】
  友元關係不能繼承,也就是說基類友元不能訪問子類私有和保護成員。(相當於在類外訪問私有和保護成員)
【繼承與靜態成員】
  基類定義了static成員,則整個繼承體系裡面只有一個這樣的成員。無論派生出多少個子類,都只有一個static成員例項。 6.單繼承、多繼承和菱形繼承 6.1.單繼承:   一個派生類只有一個基類。
6.2.多繼承:   一個派生類有兩個或多個基類:
6.3.菱形繼承:
可以通過一段簡單地程式碼來思考菱形繼承的物件模型:
class Base
{
public:
	Base()
	{
		cout << "Base()" << endl;
	}
	~Base()
	{
		cout << "~Base" << endl;
	}
protected:
	int _b;
};

class C1:public Base
{
public:
	C1()
	{
		cout << "C1()" << endl;
	}
	~C1()
	{
		cout << "~C1()" << endl;
	}
protected:
	int _c1;
};

class C2 :public Base
{
public:
	C2()
	{
		cout << "C2()" << endl;
	}
	~C2()
	{
		cout << "~C2()" << endl;
	}
protected:
	int _c2;
};
class Derived :public C1,public C2
{
public:
	Derived()
	{
		cout << "Derived()" << endl;
	}
	~Derived()
	{
		cout << "~Derived()" << endl;
	}
protected:
	int _d;
};
int main()
{
	Derived d;
	cout << sizeof(Derived) << endl;
	system("pause");
	return 0;
}
  通過這段程式碼可以得出派生類Derived的型別長度以及建構函式的呼叫順序。
  不難看出最終的派生類是由三部分構成的即C1類,C2類和Derived類。      但是這種方法使得Derived物件中出現了兩份Base類的成員,這顯然是沒有必要的。菱形繼承存在資料冗餘和二義性的問題。解決的辦法就是採用虛繼承的方法。 虛繼承--解決菱形繼承的二義性和資料冗餘的問題 1. 虛繼承解決了在菱形繼承體系裡面子類物件包含多份父類物件的資料冗餘&浪費空間的問題。
2. 虛繼承體系看起來好複雜,在實際應用我們通常不會定義如此複雜的繼承體系。一般不到萬不得
已都不要定義菱形結構的虛繼承體系結構,因為使用虛繼承解決資料冗餘問題也帶來了效能上的
損耗。   具體做法就在C1類和C2類繼承Base類是通過加關鍵字virtual使得繼承關係變成虛繼承。
class C1:virtual public Base
class C2 :virtual public Base
  再次檢視下這種情況下Derived類的型別長度和物件的構造情況:   此時的派生類物件模型為:
  此時基類成員只被儲存了一份,而且C++編譯系統只執行最後的派生類對虛基類的建構函式的呼叫,而忽略其他派生類(C1和C2)對虛基類建構函式的呼叫。