首頁 > 軟體

C++中的繼承問題(繼承基本概念、菱形虛擬繼承的物件模型)

2023-02-06 06:01:57

一、繼承的概念與定義格式

概念及定義格式

繼承機制是物件導向程式設計使程式碼可以複用的最重要手段,它允許程式設計師在保留原有類特性的基礎上進行擴充套件,增加功能,這樣產生的類,稱為派生類。繼承呈現了物件導向程式設計的層次結構,體現了由簡單到複雜的認知過程。繼承是類設計層次的複用

個人理解:父類別實際上是抽取類的共性,將其它類都有的屬性和方法進行提取,再定義其它類時只需要繼承父類別,並寫出該類獨有的屬性即可

以Person類為父類別,Student類為學生類舉例:

父類別

子類

這裡Student類繼承了Person類,學生中就包含Person類中的name和age兩個屬性,只需要再寫出Student類獨有的num屬性即可。

存取限定符與繼承許可權

一句話總結上面的表格:繼承許可權決定了子類能繼承的父類別的最高許可權。即public繼承不會改變類成員的存取許可權;protected繼承方式會改變原來存取許可權為public的成員;private繼承方式會影響原來存取許可權為public和protected的成員。

另外還有幾點要注意:

父類別的private成員被子類繼承了,但是子類不能存取父類別的private成員,通過檢視子類的大小可以得知,子類中包含繼承自父類別的私有成員變數。

在子類中存取父類別私有成員會報錯:

檢視子類大小:

  • protected成員存取限定符只因為繼承體系才出現的,因為protected在繼承中才有意義
  • 實際中一般使用public繼承
  • 使用關鍵字class預設的繼承方式是private,使用struct預設的繼承方式是public,一般最好顯式給出繼承許可權。

ps: class和struct的區別

  • 定義類的預設存取許可權不同,class為私有,struct為公有,相容C語言
  • 模板參數列中可以使用class,不能使用struct
  • 繼承中的預設繼承許可權不同,class預設private,struct預設public

二、賦值相容規則

這裡的複製相容規則是在public繼承的前提下:

  • 可以使用子類物件給父類別物件賦值賦值,但是不能使用父類別物件給子類物件賦值
  • 可以使用父類別指標指向子類物件,但不能使用子類指標指向父類別物件,如果一定要指向,進行強制型別轉換後可以,但是會有指標越界存取的問題。
  • 可以使用父類別的參照去參照子類,不能使用子類的參照參照父類別,與指標原理相同。

仍以 Person類和Student類舉例:

Person類:

lass Person
{
protected:
	string _name;
	int _age ;
};

Student類繼承Person類:

class Student :public Person
{
protected:
	int _num = 1;
};

分別驗證賦值、指標和參照:

原理如圖:

指標和參照原理與上圖相同,父類別的指標可以指向子類中繼承自父類別的部分;但是子類的指標如果指向父類別,存取_name和_age時不會有問題,存取到_num時就會超出父類別物件的範圍,越界存取,所以編譯器禁止了子類指標指向父類別物件。

三、繼承中的作用域

  • 在繼承體系中,父類別和子類都有獨立的作用域
  • 如果父類別和子類中有同名成員,子類成員會遮蔽對父類別同名成員的直接存取,優先存取自己類中的成員,即同名隱藏,也叫重定義。
  • 對於成員函數,只要函數名相同就構成重定義,與型別無關。

Person類:

class Person
{
public:
	void Print()
	{
		cout << "Person name:" << _name << endl;
		cout << "Person age" << _age << endl;
	}
protected:
	string _name = "ZS";
	int _age = 17;
};

Student類繼承Person類:

class Student :public Person
{
public:
	void Print()
	{
		cout << "Student name:" << _name << endl;
		cout << "Student age:" << _age << endl;
		cout << "Student num:" << _num << endl;
	}
protected:
	string _name = "LS";
	int _age = 18;
	int _num = 2;
};

驗證結果:

當不加作用域限定符時,子類物件會優先存取自己的成員變數和成員函數。

對程式稍作修改:

這裡兩個Print函數的引數不同,看起來像“過載”,但是實際上是同名隱藏,子類中對父類別的Print函數進行了重定義。

四、子類的預設成員函數

建構函式

父類別 沒有顯式定義建構函式 或者父類別有 全預設的建構函式 或者 無參的建構函式 ,子類可以不定義建構函式。

即下面三種情況,子類都可以不顯式地給出建構函式:

但是如果父類別顯式定義了建構函式,且不是無參或者全預設的,子類必須顯式定義建構函式,並在初始化列表顯式呼叫父類別的建構函式,因為如果不顯式定義,編譯器會自動呼叫父類別預設拷貝建構函式,而父類別沒有預設的建構函式,便會報錯:

正確的寫法:

這裡的name是傳遞給Person類建構函式的實參,即:用name給Student物件中繼承的_name賦值。

構造一個Student類的物件分兩步:

  • 將從父類別繼承的成員初始化
  • 將子類新增加的成員初始化

拷貝建構函式

子類的拷貝建構函式必須在初始化列表中顯式呼叫父類別的拷貝建構函式

父類別沒有定義拷貝建構函式,子類可以定義也可以不定義;父類別如果定義了拷貝建構函式,子類一般要定義,並且要在初始化列表中呼叫父類別的拷貝建構函式完成從父類別繼承的成員的拷貝初始化,否則會報錯:

正確寫法:

此處s是傳遞給拷貝建構函式的引數。

賦值運運算元過載

子類的賦值運運算元過載函數必須呼叫父類別的賦值運運算元過載完成對父類別的賦值。

父類別的賦值運運算元過載:

子類:

解構函式

子類解構函式會在被呼叫完後自動呼叫父類別的解構函式完成清理父類別成員,所以清理順序是:先清理子類,再清理父類別

構造和解構函式呼叫順序

構造子類物件時,先呼叫父類別的建構函式,再呼叫子類別建構函式,清理物件時,先呼叫子類的解構函式,再呼叫父類別的解構函式

如圖:

因為構造子類物件時會在初始化列表中呼叫父類別的建構函式,執行完之後才會執行子類別建構函式的函數體,所以父類別的構造會先於子類的構造執行。

五、繼承與友元、靜態成員

友元關係

友元關係不能繼承

tips:王叔是你父親的好朋友,但是不一定是你的好朋友,王叔的財產不會 給你繼承


IT145.com E-mail:sddin#qq.com