溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊(cè)×
其他方式登錄
點(diǎn)擊 登錄注冊(cè) 即表示同意《億速云用戶服務(wù)條款》

C++數(shù)據(jù)結(jié)構(gòu)繼承的概念與菱形繼承及虛擬繼承和組合分析

發(fā)布時(shí)間:2022-03-01 09:15:21 來(lái)源:億速云 閱讀:169 作者:iii 欄目:開(kāi)發(fā)技術(shù)

這篇“C++數(shù)據(jù)結(jié)構(gòu)繼承的概念與菱形繼承及虛擬繼承和組合分析”文章的知識(shí)點(diǎn)大部分人都不太理解,所以小編給大家總結(jié)了以下內(nèi)容,內(nèi)容詳細(xì),步驟清晰,具有一定的借鑒價(jià)值,希望大家閱讀完這篇文章能有所收獲,下面我們一起來(lái)看看這篇“C++數(shù)據(jù)結(jié)構(gòu)繼承的概念與菱形繼承及虛擬繼承和組合分析”文章吧。

繼承的概念

繼承:繼承機(jī)制是面向?qū)ο蟪绦蛟O(shè)計(jì)使代碼可以復(fù)用的最重要的手段,它允許程序員在保持原有類特性的基礎(chǔ)上進(jìn)行擴(kuò)展,增加功能,這樣產(chǎn)生新的類,稱派生類。繼承呈現(xiàn)了面向?qū)ο蟪绦蛟O(shè)計(jì)的層次結(jié)構(gòu),體現(xiàn)了由簡(jiǎn)單到復(fù)雜的認(rèn)知過(guò)程。以前我們接觸的復(fù)用都是函數(shù)復(fù)用,繼承是類設(shè)計(jì)層次的復(fù)用。

繼承的定義

語(yǔ)法:

C++數(shù)據(jù)結(jié)構(gòu)繼承的概念與菱形繼承及虛擬繼承和組合分析

說(shuō)明: 派生類會(huì)將基類的成員變量和成員函數(shù)都繼承下來(lái),但是訪問(wèn)限定符會(huì)根據(jù)繼承方式而發(fā)生變化。

繼承方式有三種:

  • public繼承

  • protected繼承

  • private繼承

訪問(wèn)限定符:

  • public訪問(wèn)

  • protected訪問(wèn)

  • private訪問(wèn)

繼承基類成員的訪問(wèn)方式的變化:

類成員/繼承方式public繼承protected繼承private繼承
基類的public成員派生類的public成員派生類的protected成員派生類的private成員
基類的protected成員派生類的protected成員派生類的protected成員派生類的private成員
基類的private成員派生類中不可見(jiàn)派生類中不可見(jiàn)派生類中不可見(jiàn)

總結(jié):

  • 基類的private成員在派生類中都是不可見(jiàn)的,這里的不可見(jiàn)是指基類的私有成員還是被繼承到了派生類對(duì)象中,但是語(yǔ)法上限制派生類對(duì)象不管在類里面還是類外面都不能去訪問(wèn)它。

  • 基類成員在父類中的訪問(wèn)方式=min(成員在基類的訪問(wèn)限定符,繼承方式),public>protected>private。

  • 一般會(huì)把基類中不想讓類外訪問(wèn)的成員設(shè)置為protecd成員,不讓類外訪問(wèn),但是讓派生類可以訪問(wèn)。

基類和派生類對(duì)象之間的賦值轉(zhuǎn)換

派生類對(duì)象會(huì)通過(guò) “切片” 或 “切割” 的方式賦值給基類的對(duì)象、指針或引用。但是基類對(duì)象不能賦值給派生類對(duì)象。

C++數(shù)據(jù)結(jié)構(gòu)繼承的概念與菱形繼承及虛擬繼承和組合分析

實(shí)例演示:

class Person
{
public:
	Person(const char* name = "")
		:_name(name)
	{}
	void Print()
	{
		cout << "name:" << _name << " age:" << _age << endl;
	}
protected:
	string _name = "";
	int _age = 1;
};
class Student : public Person
{
public:
	Student()
		:Person("xiaoming")
	{}
	void Print()
	{
		cout << "name:" << _name << " age:" << _age << " _stuid:" << _stuid << " _major:" << _major << endl;
	}
private:
	int _stuid = 0;// 學(xué)號(hào)
	int _major = 0;// 專業(yè)
};
int main()
{
	Student s;
	// 子類對(duì)象可以賦值給父類的對(duì)象、指針和引用  反過(guò)來(lái)不行
	// Student對(duì)象通過(guò) “切片” 或 “切割” 的方式進(jìn)行賦值
	Person p1 = s;
	Person* p2 = &s;
	Person& p3 = s;

	p1.Print();
	p2->Print();
	p3.Print();

	// 基類的指針可以通過(guò)強(qiáng)制類型轉(zhuǎn)換賦值給派生類的指針
	Student* ps = (Student*)p2;

	ps->Print();

	return 0;
}

C++數(shù)據(jù)結(jié)構(gòu)繼承的概念與菱形繼承及虛擬繼承和組合分析

總結(jié):

  • 派生類對(duì)象可以“切片”或“切割”的方式賦值給基類的對(duì)象,基類的指針或基類的引用,就是把基類的那部分切割下來(lái)。

  • 基類對(duì)象不能給派生類對(duì)象賦值。

  • 基類的指針可以通過(guò)強(qiáng)制類型轉(zhuǎn)換賦值給派生類的指針。但必須是基類的指針指向派生類的對(duì)象才是安全的,因?yàn)槿绻愂嵌鄳B(tài)類型,會(huì)引發(fā)多態(tài)。

繼承中的作用域

在繼承體系中,基類和派生類對(duì)象都有獨(dú)立的作用域,子類中的成員(成員變量和成員函數(shù))會(huì)對(duì)父類的同名成員進(jìn)行隱藏,也叫重定義。

實(shí)例演示:

class Person
{
public:
	Person(const char* name = "")
		:_name(name)
	{}
	void Print()
	{
		cout << "name:" << _name << " age:" << _age << endl;
	}
protected:
	string _name = "";
	int _age = 1;
};
class Teacher : public Person
{
public:
	void Print()
	{
		cout << "name:" << _name << " age:" << _age << " jobid:" << _jobid << endl;
	}
private:
	int _jobid = 0;// 工號(hào)
};
int main()
{
	Teacher t;

	t.Print();
	t.Person::Print();// 子類會(huì)隱藏(重定義)父類的同名成員(同名函數(shù)或同名成員變量) 可以通過(guò)指定域作用限定符訪問(wèn)

	return 0;
}

代碼運(yùn)行結(jié)果如下:

C++數(shù)據(jù)結(jié)構(gòu)繼承的概念與菱形繼承及虛擬繼承和組合分析

得出結(jié)論: 子類中的成員(成員變量和成員函數(shù))會(huì)對(duì)父類的同名成員進(jìn)行隱藏,如果相要訪問(wèn)父類的同名成員,必須指定類域訪問(wèn)。

看下面一個(gè)小問(wèn)題: 請(qǐng)問(wèn)A中的fun函數(shù)和B中的fun函數(shù)是構(gòu)成重載還是隱藏?

class A
{
public:
	void fun()
	{
		cout << "func()" << endl;
	}
};
class B : public A
{
public:
	void fun(int i) 
	{
		A::fun();
		cout << "func(int i)->" << i << endl;
	}
};
void Test()
{
	B b;
	b.fun(10);
};

答案: 兩個(gè)函數(shù)在不同的作用域,不可能構(gòu)成重載。因?yàn)闃?gòu)成重載的條件是兩個(gè)函數(shù)必須在同一作用域,而隱藏是要求在基類和派生類不同作用域的,所以這里同名成員是構(gòu)造隱藏。

派生類的默認(rèn)成員函數(shù)

C++中的每個(gè)對(duì)象中會(huì)有6個(gè)默認(rèn)成員函數(shù)。默認(rèn)的意思就是我們不寫,編譯器會(huì)生成一個(gè)。那么在繼承中,子類的默認(rèn)成員函數(shù)是怎么生成的呢?

先看下面一個(gè)例子:

class Person
{
public:
	Person(const char* name = "", int age = 1)
		:_name(name)
		,_age(age)
	{
		cout << "Person()" << endl;
	}
	Person(const Person& p)
		:_name(p._name)
		, _age(p._age)
	{
		cout << "Person(const Person& p)" << endl;
	}
	Person& operator=(const Person& p)
	{
		_name = p._name;
		_age = p._age;
		cout << "Person& operator=(const Person& p)" << endl;
		return *this;
	}
	void Print()
	{
		cout << "name:" << _name << " age:" << _age << endl;
	}
	~Person()
	{
		cout << "~Person()" << endl;
	}
protected:
	string _name;
	int _age;
};


class Student : public Person
{
public:
	Student(const char* name, int age, int stuid = 0)
		:Person(name, age)// 此處調(diào)用父類的構(gòu)造函數(shù)堆繼承下來(lái)的成員進(jìn)行初始化,不謝的話,編譯器調(diào)用父類的默認(rèn)構(gòu)造函數(shù)
		, _stuid(stuid)
	{
		cout << "Student()" << endl;
	}
	Student(const Student& s)
		:Person(s)// 子類對(duì)象可以傳給父類的對(duì)象、指針或引用
		,_stuid(s._stuid)
	{
		cout << "Student(const Student& s)" << endl;
	}
	Student& operator=(const Student& s)
	{
		cout << "Student& operator=(const Student& s)" << endl;
		if (this != &s)
		{
			Person::operator=(s);// 先完成基類的復(fù)制
			_stuid = s._stuid;
		}

		return *this;
	}
	void Print()
	{
		cout << "name:" << _name << " age:" << _age << " _stuid:" << _stuid << endl;
	}
	~Student()
	{
		// 基類和派生類的析構(gòu)函數(shù)的函數(shù)名都被編譯器處理成了destruction,構(gòu)成隱藏,是一樣指定域訪問(wèn)
		//Person::~Person();// 不需要顯示調(diào)用 編譯器會(huì)自動(dòng)先調(diào)用派生類的析構(gòu)函數(shù),然后調(diào)用基類的析構(gòu)函數(shù)
		cout << "~Student()" << endl;
	}
private:
	int _stuid;// 學(xué)號(hào)
};

測(cè)試1:構(gòu)造函數(shù)和析構(gòu)函數(shù)

void test1()
{
	Student s("小明",18,10);
}

代碼運(yùn)行結(jié)果如下:

C++數(shù)據(jù)結(jié)構(gòu)繼承的概念與菱形繼承及虛擬繼承和組合分析

總結(jié)1: 子類的構(gòu)造函數(shù)必須調(diào)用基類的構(gòu)造函數(shù)初始化基類的那一部分成員,如果基類沒(méi)有默認(rèn)構(gòu)造函數(shù),則必須在派生類構(gòu)造函數(shù)的初始化列表階段顯示調(diào)用。子類的析構(gòu)函數(shù)會(huì)在被調(diào)用完成后自動(dòng)調(diào)用基類的析構(gòu)函數(shù)清理基類的成員。不需要顯示調(diào)用。這里子類和父類的析構(gòu)函數(shù)的函數(shù)名會(huì)被編譯器處理成destructor,這樣兩個(gè)函數(shù)構(gòu)成隱藏。

測(cè)試2:拷貝構(gòu)造函數(shù)

void test2()
{
	Student s1("小明", 18, 10);
	Student s2(s1);
}

代碼運(yùn)行結(jié)果如下:

C++數(shù)據(jù)結(jié)構(gòu)繼承的概念與菱形繼承及虛擬繼承和組合分析

總結(jié)2: 子類的拷貝構(gòu)造必須代用父類的拷貝構(gòu)造完成父類成員的拷貝。

測(cè)試3:operator=

C++數(shù)據(jù)結(jié)構(gòu)繼承的概念與菱形繼承及虛擬繼承和組合分析

結(jié)論3: 子類的operator=必須調(diào)用基類的operator完成基類的賦值。

思考

如何設(shè)計(jì)一個(gè)不能被繼承的類? 把該類的構(gòu)造函數(shù)設(shè)為私有。如果基類的構(gòu)造函數(shù)是私有,那么派生類不能調(diào)用基類的構(gòu)造函數(shù)完成基類成員的初始化,則無(wú)法進(jìn)行構(gòu)造。所以這樣設(shè)計(jì)的類不可以被繼承。(后面還會(huì)將加上final關(guān)鍵字的類也不可以被繼承)

總結(jié):

  • 子類的構(gòu)造函數(shù)必須調(diào)用基類的構(gòu)造函數(shù)初始化基類的那一部分成員,如果基類沒(méi)有默認(rèn)構(gòu)造函數(shù),則必須在派生類構(gòu)造函數(shù)的初始化列表階段顯示調(diào)用。

  • 子類的拷貝構(gòu)造必須代用父類的拷貝構(gòu)造完成父類成員的拷貝。

  • 子類的operator=必須調(diào)用基類的operator完成基類的賦值。

  • 子類的析構(gòu)函數(shù)會(huì)在被調(diào)用完成后自動(dòng)調(diào)用基類的析構(gòu)函數(shù)清理基類的成員。不需要顯示調(diào)用。

  • 子類對(duì)象會(huì)先調(diào)用父類的構(gòu)造在調(diào)用子類的構(gòu)造。

  • 子類對(duì)象會(huì)先析構(gòu)子類的析構(gòu)再調(diào)用父類的析構(gòu)。

繼承中的兩個(gè)小細(xì)節(jié)

繼承和友元

友元關(guān)系不能被繼承。也就是說(shuō)基類的友元不能夠訪問(wèn)子類的私有和保護(hù)成員。

繼承和靜態(tài)成員

基類定義的static靜態(tài)成員,存在于整個(gè)類中,不屬于某個(gè)類,無(wú)論右多少個(gè)派生類,都這有一個(gè)static成員。

實(shí)例演示:

class Person
{
public:
	Person()
	{
		++_count;
	}
	// static成員存在于整個(gè)類  無(wú)論實(shí)例化出多少對(duì)象,都只有一個(gè)static成員實(shí)例
	static int _count;
};

int Person::_count = 0;

class Student :public Person
{
public:
	int _stuid;
};

int main()
{
	Student s1;
	Student s2;
	Student s3;

	// Student()._count = 10;
	cout << "人數(shù):" << Student()._count - 1 << endl;

	return 0;
}

代碼運(yùn)行結(jié)果如下:

C++數(shù)據(jù)結(jié)構(gòu)繼承的概念與菱形繼承及虛擬繼承和組合分析

單繼承和多繼承(菱形繼承)

單繼承: 一個(gè)子類只有一個(gè)直接父類時(shí)稱這個(gè)繼承關(guān)系為單繼承。

C++數(shù)據(jù)結(jié)構(gòu)繼承的概念與菱形繼承及虛擬繼承和組合分析

多繼承: 一個(gè)子類有兩個(gè)或以上的直接父類時(shí)稱這個(gè)繼承關(guān)系為多繼承。

C++數(shù)據(jù)結(jié)構(gòu)繼承的概念與菱形繼承及虛擬繼承和組合分析

菱形繼承: 多繼承的一種特殊情況。

C++數(shù)據(jù)結(jié)構(gòu)繼承的概念與菱形繼承及虛擬繼承和組合分析

多繼承帶來(lái)的問(wèn)題: 子類會(huì)得到兩份BenZ的數(shù)據(jù),會(huì)造成數(shù)據(jù)冗余和二義性。

虛擬繼承

概念

為了解決菱形繼承帶來(lái)的數(shù)據(jù)冗余和二義性的問(wèn)題,C++提出來(lái)虛擬繼承這個(gè)概念。虛擬繼承可以解決前面的問(wèn)題,在繼承方式前加椰果virtual的關(guān)鍵字即可。

class Person
{
public:
	string _name;
};
// 不要在其他地方去使用。
class Student : virtual public Person
{
public:
	int _num; //學(xué)號(hào)
};
class Teacher : virtual public Person
{
public:
	int _id; // 職工編號(hào)
};
class Assistant : public Student, public Teacher
{
protected:
	string _majorCourse; // 主修課程
};

虛擬繼承的原理

先看下面一串代碼:

class A
{
public:
	int _a;
};

class B :virtual public A
{
public:
	int _b;
};

class C :virtual public A
{
public:
	int _c;
};

class D : public B, public C
{
public:
	int _d;
};
int main()
{
	D d;
	d.B::_a = 1;
	d.C::_a = 2;
	d._b = 4;
	d._c = 5;
	d._d = 6;

	return 0;
}

我們通過(guò)內(nèi)存窗口查看它的對(duì)象模型:

C++數(shù)據(jù)結(jié)構(gòu)繼承的概念與菱形繼承及虛擬繼承和組合分析

原理: 從上圖可以看出,A對(duì)象同時(shí)屬于B和C,B和C中分別存放了一個(gè)指針,這個(gè)指針叫虛基表指針,分別指向的兩張表,叫虛基表,虛基表中存的是偏移量,B和C通過(guò)偏移量就可以找到公共空間(存放A對(duì)象的位置)。

C++數(shù)據(jù)結(jié)構(gòu)繼承的概念與菱形繼承及虛擬繼承和組合分析

組合與繼承

總結(jié)一下幾點(diǎn):

  • 組合和繼承都屬于類層次的復(fù)用。

  • public繼承是一種is-a的關(guān)系。也就是說(shuō)每個(gè)派生類對(duì)象都是一個(gè)基類對(duì)象-。

  • 組合是一種has-a的關(guān)系。假設(shè)B組合了A,每個(gè)B對(duì)象中都有一個(gè)A對(duì)象。

  • 優(yōu)先使用對(duì)象組合,而不是類繼承 。

  • 繼承允許你根據(jù)基類的實(shí)現(xiàn)來(lái)定義派生類的實(shí)現(xiàn)。這種通過(guò)生成派生類的復(fù)用通常被稱為白箱復(fù)用。術(shù)語(yǔ)“白箱”是相對(duì)可視性而言:在繼承方式中,基類的內(nèi)部細(xì)節(jié)對(duì)子類可見(jiàn) 。繼承一定程度破壞了基類的封裝,基類的改變,對(duì)派生類有很大的影響。派生類和基類間的依賴關(guān)系很強(qiáng),耦合度高。

  • 對(duì)象組合是類繼承之外的另一種復(fù)用選擇。新的更復(fù)雜的功能可以通過(guò)組裝或組合對(duì)象來(lái)獲得。對(duì)象組合要求被組合的對(duì)象具有良好定義的接口。這種復(fù)用風(fēng)格被稱為黑箱復(fù)用,因?yàn)閷?duì)象的內(nèi)部細(xì)是不可見(jiàn)的。對(duì)象只以“黑箱”的形式出現(xiàn)。 組合類之間沒(méi)有很強(qiáng)的依賴關(guān)系,耦合度低。優(yōu)先使用對(duì)象組合有助于你保持每個(gè)類被封裝。

  • 實(shí)際盡量多去用組合。組合的耦合度低,代碼維護(hù)性好。不過(guò)繼承也有用武之地的,有些關(guān)系就適合繼承那就用繼承,另外要實(shí)現(xiàn)多態(tài),也必須要繼承。類之間的關(guān)系可以用繼承,可以用組合,就用組合。

C++的缺陷之一:

多繼承就是一個(gè)。多繼承會(huì)帶來(lái)菱形繼承,菱形繼承又會(huì)帶來(lái)數(shù)據(jù)冗余和二義性,為了解決這個(gè)問(wèn)題,又引入了虛擬繼承。進(jìn)而導(dǎo)致C++的底層結(jié)構(gòu)對(duì)象模型非常復(fù)雜,這樣會(huì)帶來(lái)一定的損失。所以說(shuō)盡量不要設(shè)計(jì)出菱形繼承。

以上就是關(guān)于“C++數(shù)據(jù)結(jié)構(gòu)繼承的概念與菱形繼承及虛擬繼承和組合分析”這篇文章的內(nèi)容,相信大家都有了一定的了解,希望小編分享的內(nèi)容對(duì)大家有幫助,若想了解更多相關(guān)的知識(shí)內(nèi)容,請(qǐng)關(guān)注億速云行業(yè)資訊頻道。

向AI問(wèn)一下細(xì)節(jié)

免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如果涉及侵權(quán)請(qǐng)聯(lián)系站長(zhǎng)郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。

c++
AI