亚洲乱码中文字幕综合,中国熟女仑乱hd,亚洲精品乱拍国产一区二区三区,一本大道卡一卡二卡三乱码全集资源,又粗又黄又硬又爽的免费视频

詳解C++中單繼承與多繼承的使用

 更新時間:2022年04月24日 10:46:21   作者:賣寂寞的小男孩  
C++的繼承機(jī)制相對其他語言是比較復(fù)雜的一種,不同于java只支持單繼承,C++不僅支持單繼承,也支持多繼承。本文將詳細(xì)講解C++中單繼承與多繼承的使用,需要的可以參考一下

前言

C++的繼承機(jī)制相對其他語言是比較復(fù)雜的一種,不同于java只支持單繼承,C++不僅支持單繼承,也支持多繼承,對于多繼承中的菱形問題會引發(fā)一系列的麻煩,C++的兩個重要缺陷,一個是多繼承,一個是垃圾回收器。本文將詳細(xì)講解C++的單繼承和多繼承,以及菱形繼承的解決方法及原理。

1.繼承的概念和定義

(1)繼承的概念

繼承是面向?qū)ο笤O(shè)計(jì)使代碼可以復(fù)用的重要手段,它允許程序員在保持原有類的基礎(chǔ)上進(jìn)行擴(kuò)展。被擴(kuò)展的類稱為基類或者父類,擴(kuò)展生成的類叫做子類或者派生類,繼承是類設(shè)計(jì)層次的復(fù)用。

繼承的作用是使得子類中既包含父類的成員,也可以包含自己的成員。

(2)繼承的定義方法

class Person
{
private:
	string _name;
	int _age;
};
class Student :public Person
{
private:
	int _id;
};

看這一段代碼,其中子類Student繼承了父類Person,Student后的public表示的是繼承方式。

(2)繼承后子類的成員類型

繼承方式和父類的成員屬性共同決定了子類中的成員屬性。我們用一張表來表示三者之間的關(guān)系。

類成員/繼承方式public繼承protected繼承private繼承
基類的public成員派生類的public成員派生類的protected成員派生類的private成員
基類的protected成員派生類的protected成員派生類的protected成員派生類的private成員
基類的private成員派生類中不可見派生類中不可見派生類中不可見

我們只需要兩點(diǎn)來記憶這個表格:

1.基類的private成員在派生類中無論以什么方式繼承都是不可見的。

2.子類中的成員屬性取繼承方式和父類成員屬性中權(quán)限小的那個: public>protected>private

表格的說明:

1.不可見的意思不是沒有被繼承,而是不能使用,在底層繼承下來比沒有繼承下來更方便。

2.在父類中private和protected沒有區(qū)別,但是在子類中,protected成員可以在類內(nèi)訪問,而private不能,因此可以說protected是為了繼承而存在的。

3.如果不寫繼承方式,如果子類是class定義的,那么默認(rèn)為private繼承,是struct定義的,默認(rèn)是public繼承。

4.不可見與private成員區(qū)別:不可見指的是在類內(nèi)與類外都不能使用,private成員在類內(nèi)可以使用,在類外不可以使用。

5.不想給子類訪問的成員我們設(shè)成private。

2.基類與派生類的賦值轉(zhuǎn)換

(1)派生類賦值給基類

在這里插入圖片描述

我們定義了一個父類person和它的派生類student,以上是它們各自的成員。

當(dāng)我們將一個派生類的對象賦值給基類的對象時,發(fā)生的過程我們稱之為切片。即只將子類中父類成員賦值過去。當(dāng)父類中有private成員時,同樣會進(jìn)行切片,只是不顯示而已,因此繼承中盡量不要定義私有成員。

注意,這種賦值兼容方式僅限于公有繼承。

私有繼承不支持切片,這是因?yàn)閷τ诟割愔械膒ublic成員,私有或保護(hù)繼承之后會轉(zhuǎn)變成private/protected類型,而賦值時會發(fā)生將派生類對象中的private/protected成員賦值給父類對象中的public成員的現(xiàn)象,但是private/protected成員在類外是不能被訪問的,因此不支持私有繼承。

	Person b;
	Student a;
	b = a;
	Person* ptr = &a;
	Person& ref = a;

注意一個細(xì)節(jié),我們可以使用引用賦值,說明這里并不存在類型轉(zhuǎn)換的行為,因?yàn)轭愋娃D(zhuǎn)換中間會產(chǎn)生臨時變量,需要使用const引用。

double d;
const int& r=d;//發(fā)生了類型轉(zhuǎn)換。

(2)基類給派生類

先說結(jié)論:

父類對象不可以直接賦值給子類對象。

這是因?yàn)樽宇悓ο笾杏懈割惒淮嬖诘念愋?,無法進(jìn)行賦值。也不能通過所謂的強(qiáng)制類型轉(zhuǎn)換進(jìn)行賦值。

但是C++支持指針和引用的賦值:

	Person b;
	Student a;
	a = (Student)b;//不正確
	Student* ptr = (Student*)&b;//支持
	Student& ref = (Student&)b;//支持

雖然指針和引用可以,但是當(dāng)指針向下訪問的時候超過父類對象的時候會出現(xiàn)問題。

在這里插入圖片描述

會出現(xiàn)指向空的情況。

3.繼承中的作用域

(1)隱藏的概念

基類和派生類都有各自獨(dú)立的作用域。

如果不同的域內(nèi)有同名的成員,我們根據(jù)就近原則或者指定作用域的方式來指定成員的位置。

隱藏:子類與父類中出現(xiàn)同名成員,子類成員將屏蔽父類成員對同名成員進(jìn)行直接訪問,這種情況叫隱藏,也叫重定向

注意如果是成員函數(shù)的隱藏,只要函數(shù)名相同就會構(gòu)成隱藏,與參數(shù)無關(guān)。

舉一個例子:

class Person
{
protected:
	string _name = "小六子";
	int _num = 111;
};
class Student :public Person
{
public:
	void Print()
	{
		cout << "姓名:" << _name << endl;
		cout << "身份證號:" << Person::_num << endl;
		cout << "學(xué)號:" << _num << endl;
	}
protected:
	int _num = 999;
};
int main()
{
	Student s1;
	s1.Print();
}

在這段代碼中,Person和Student分別定義了_num,當(dāng)子類對象中的成員函數(shù)直接訪問_num時,根據(jù)的是就近原則,訪問的是子類中的_num,當(dāng)要訪問父類中的_num時,需要使用::來指定類域,就可以進(jìn)行訪問。父類中的_num與子類中的_num構(gòu)成隱藏。

這段代碼打印的結(jié)果是:

在這里插入圖片描述

(2)例題

這里有一道小小的題目,是關(guān)于函數(shù)隱藏的:

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

提問在Test中的兩個函數(shù)能否調(diào)用成功?

b.func(10)可以調(diào)用成功,因?yàn)闃?gòu)成了隱藏。
b.func()不能調(diào)用成功,會發(fā)生變異報(bào)錯,因?yàn)殡[藏了調(diào)不動。

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

對于六大默認(rèn)成員函數(shù)我們這里暫時先討論4種重要的,即:構(gòu)造函數(shù),析構(gòu)函數(shù),拷貝構(gòu)造,賦值運(yùn)算符重載。

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

當(dāng)我們不在子類中書寫時,編譯器會默認(rèn)生成。這里只需要記住一句話:

繼承下來的成員調(diào)用父類的來處理,自己的按基本規(guī)則來處理。

以構(gòu)造函數(shù)舉例:派生類中的父類成員調(diào)用父類中的構(gòu)造函數(shù),自己的成員按照構(gòu)造函數(shù)自動生成的規(guī)則來。

(2)自己寫

自己寫的情況

1.父類沒有默認(rèn)構(gòu)造函數(shù),需要我們自己寫構(gòu)造函數(shù)。

2.子類有資源需要釋放,需要我們自己寫析構(gòu)函數(shù)。

3.如果子類涉及淺拷貝問題,需要自己寫拷貝構(gòu)造和賦值重載。

構(gòu)造函數(shù)

父類成員調(diào)用對應(yīng)的父類構(gòu)造函數(shù)處理。子類成員按普通類處理。

舉一個例子:

class Person
{
public:
	Person(string name , int num=2)
		:_name(name)
		,_num(num)
	{}
protected:
	string _name ;
	int _num ;
};
class Student :public Person
{
public:
	Student(int num,string _name,int _num)
		:_num(num)
		,Person(_name,_num)
	{}
protected:
	int _num;
};
int main()
{
	Student s1(2,"zhangsan",2);
}

看這一段代碼,父類中沒有默認(rèn)構(gòu)造函數(shù)(注意與默認(rèn)成員函數(shù)區(qū)分),因此要初始化父類中的對象需要我們自己書寫子類中的構(gòu)造函數(shù)。在書寫構(gòu)造函數(shù)時,父類對象成員初始化使用父類中的構(gòu)造函數(shù),子類成員的初始化按正常方式書寫即可。

拷貝構(gòu)造和運(yùn)算符重載函數(shù)

	Student(const Student& s)
		:Person(s)
		,_num(s._num)
	{}
	Student& operator=(const Student& s)
	{
		if (this != &s)
		{
			Person::operator=(s);//不指明類域的話會發(fā)生自己調(diào)自己的情況
			_num = s._num;
			return *this;
		}
	}
	int main()
{
	Student s1(2,"zhangsan",2);
	Student s2(s1);
	Student s3 = s2;
}

我們可以通過調(diào)試來查看結(jié)果:

在這里插入圖片描述

析構(gòu)函數(shù)

析構(gòu)函數(shù)比較特殊,對于父類中的析構(gòu)函數(shù),我們不需要指定去書寫,就像下面這種情況:

//父類中的析構(gòu)
	~Person()
	{
		cout << "~Person" << endl;
	}
//子類中的析構(gòu)
		~Student()
	{
		Person::~Person();
	}

注意,析構(gòu)函數(shù)的名字在最后會被統(tǒng)一處理成destructor(),如果不指定類域的話,父類析構(gòu)函數(shù)和子類析構(gòu)函數(shù)會構(gòu)成隱藏,因此需要指定類域。
對于上述int中的代碼,需要析構(gòu)三個子類對象,打印出的結(jié)果是:

在這里插入圖片描述

我們發(fā)現(xiàn)調(diào)用了六次父類中的析構(gòu)函數(shù)。這說明每個對象的父類成員都被析構(gòu)了兩次。如果需要釋放空間,則一定會報(bào)錯。

先說結(jié)論:我們自己實(shí)現(xiàn)子類構(gòu)造函數(shù)時,不需要顯示調(diào)用父類析構(gòu)函數(shù),我們顯示調(diào)用一次,它還會自動調(diào)用一次。

在這里插入圖片描述

下面簡單說明一下,為什么程序需要自動調(diào)用:

我們知道變量的定義是發(fā)生在棧中的,因此就存在構(gòu)造和析構(gòu)的順序問題,棧滿足先入后出原則,因此先構(gòu)造的需要后析構(gòu)。

在構(gòu)造的過程中,我們會先初始化父類成員,再初始化子類成員。因此我們需要先析構(gòu)子類成員,再析構(gòu)父類成員。

在這里插入圖片描述

如果先析構(gòu)父類會打亂棧的順序,因此編譯器會自動調(diào)用父類的析構(gòu)函數(shù)。

5.友元與靜態(tài)成員

這個只需要記住兩點(diǎn):

1.友元關(guān)系不能繼承。

2.靜態(tài)成員會被繼承下來,無論繼承多少,靜態(tài)成員只有一個。

6.多繼承

(1)概念

一個類有兩個及以上父類時稱這個繼承關(guān)系為多繼承。

class Student
{
public:
protected:
	int _id;
};
class Teacher
{
public:
protected:
	int _course;
};
class Assistant:public Student,public Teacher
{
public:
protected:
protected:
};

我們使用逗號表示分隔,即繼承多個父類。可以通過調(diào)試來觀察子類Assitant的內(nèi)容:

在這里插入圖片描述

(2)復(fù)雜的菱形繼承

菱形繼承是多繼承的一種情況:

在這里插入圖片描述

具有這樣的繼承關(guān)系的稱為菱形繼承。

菱形繼承出現(xiàn)的問題:從對象成員模型構(gòu)造,可以看出菱形繼承有數(shù)據(jù)冗余和二義性的問題。

數(shù)據(jù)冗余指的是類Assistant中會有兩份Person的成員,二義性指的是這兩份成員每一次調(diào)用不知道調(diào)用的的是哪一個,需要指定類域。

這段代碼表示的就是菱形繼承的關(guān)系:

class Person
{
public:
	string _name;
};
class Student:public Person
{
public:
protected:
	int _num;
};
class Teacher:public Person
{
public:
protected:
	int _id;
};
class Assistant:public Student,public Teacher
{
public:
protected:
protected:
	int _course;
};
int main()
{
	Assistant a;
}

我們通過調(diào)試可以觀測a中的內(nèi)容,發(fā)現(xiàn)會存在兩份Person中的成員:

在這里插入圖片描述

如果要對這兩個Person成員賦值時,需要指定類域。

	a.Student::_name = "xxx";
	a.Teacher::_name = "yyy";
}

這就是所謂的二義性,在實(shí)際中一個人不能有兩個名字,對于冗余性來說,如果Person中有一個很大的數(shù)組浪費(fèi)的空間會很多。

(3)虛繼承解決菱形繼承問題

虛繼承可以解決菱形繼承的二義性和數(shù)據(jù)冗余問題。如上面的繼承關(guān)系,在Student和Teacher的繼承Person時使用的虛擬繼承,即可解決問題。需要注意的是,虛擬繼承不要在其他地方去使用。

class Student:virtual public Person
{
public:
protected:
	int _num;
};
class Teacher:virtual public Person
{
public:
protected:
	int _id;
};

只需要在菱形的腰部兩個父類加入virtual關(guān)鍵詞即可。

注意要在菱形的腰部。

當(dāng)加完之后,在Assistant的對象中,Person類的_name成員就只有一個了。無論是否指定類域,更改的變量都只有一個:

在這里插入圖片描述

(4)虛繼承的原理

內(nèi)存演示

要研究虛繼承的原理,我們給出一個簡化的菱形繼承結(jié)構(gòu),再借助內(nèi)存窗口窗口觀察對象成員的模型。

class A
{
public:
	int _a;
};
class B:public A
{
public:
	int _b;
};
class C: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 = 3;
	d._c = 4;
	d._d = 5;
	return 0;
}

當(dāng)沒使用虛繼承(即沒有使用virtual時)

我們使用內(nèi)存窗口來觀察內(nèi)容:

在這里插入圖片描述

通過觀察內(nèi)存中的布局,我們發(fā)現(xiàn)d中的B父類對象和C父類對象中的內(nèi)容分別是連續(xù)存放的,B中有父類A中成員_a的值是1,其自己成員_b的值是3,兩者的內(nèi)存是挨著的,C同理,對于D類中自己的成員_d,放在了內(nèi)存的最后。

確定d中B類對象和C類對象的存儲順序是根據(jù)繼承順序決定的。由于上述代碼是class D :public B, public C,因此B類的對象會存在C類的前面。
而當(dāng)我們給腰部加上virtual構(gòu)成虛繼承之后:

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

在這里插入圖片描述

使用virtual之后,我們發(fā)現(xiàn)已經(jīng)將A中對象_a放入在了最后,因此無論指定不指定類域,改變的都是同一個_a的值。

但同時我們發(fā)現(xiàn)內(nèi)存中多了兩行,那么這兩行是干什么的呢?

虛基表

從格式來看,這兩行顯然是都是地址。

在這里插入圖片描述

我們再開辟一個內(nèi)存2,向其中輸入上面地址,我們發(fā)現(xiàn)地址中存儲的內(nèi)容是00 00 00 00,C類對象中同理,這里就不演示了。

這里00 00 00 00的意義在后面多態(tài)中會學(xué)習(xí)到,注意看它的下一個位置存放的是00 00 00 14

這里是十六進(jìn)制,因此表示的是20這個數(shù)字。

再來看內(nèi)存1:

在這里插入圖片描述

兩者的地址之差剛剛好是20個字節(jié)。

因此我們可以知道:在虛繼承中,B類對象和C類對象的內(nèi)存中新加入的是一個地址,分別用于尋找兩者與A類型變量的偏移量。B類對象與A類對象的偏移量是20,同理可驗(yàn)證C類對象的偏移量是12。而內(nèi)存2也有一個專有名詞:虛基表

總結(jié):A一般叫做虛基類,在D里面,A類成員放在一個公共的位置,有時B要找A,C要找A,就要通過虛基表中的偏移量進(jìn)行計(jì)算。

比如,當(dāng)我們再用B類和C類建立兩個變量:

	B b = d;
	C c = d;

此時會發(fā)生切片處理,需要將d中的A類對象賦值到b和c中,此時就需要使用到虛基表來尋找。

再比如:

	B* pb = &d;
	pb->_a = 10;

pb指向了d的首地址,要更改d中的_a的值,指針pb也需要使用虛基表來進(jìn)行尋找。

7.繼承與組合

(1)兩者區(qū)別

首先我們要對繼承和組合進(jìn)行區(qū)分:

繼承表示的是子類繼承父類,組合表示的是在一個類中定義了另一個類的成員變量。

//繼承
class A
{
public:
	int _a;
};
class B:public A
{
public:
	int _b;
};
//組合
class C
{
public:
	int _c;
};
class D 
{
public:
	int _d;
	C _obj;
};

(2)繼承與組合的區(qū)別

我們需要明確一點(diǎn):類之間,模塊之間最好是低耦合,高內(nèi)聚的,因?yàn)榉奖憔S護(hù)。

低耦合:類之間依賴關(guān)系越弱越好。

高內(nèi)聚:內(nèi)部成員關(guān)系緊密。

1.繼承對應(yīng)于白盒:B可以直接使用A中的公有和保護(hù)成員,破壞了封裝性。

2.組合對應(yīng)于黑盒:D只能使用C的公有,不能直接使用保護(hù)成員。

舉一個例子:

如果A中有5個public,5個protected

對于組合來說,非基類只能使用這5個public,基類中的其他成員隨便修改都不會影響該非基類。

對于繼承來說,基類中一切的改變都會影響子類。

那可以拋棄繼承的語法嗎?當(dāng)然是不行的。

多態(tài)是建立在繼承的基礎(chǔ)上的。

(3)使用情況

1.如果B就是一個A,比如Student是一個Person,我們稱這種關(guān)系為is-a關(guān)系,此時適合使用繼承。

2.如果D被包含于C,比如head包含eyes,我們稱這種關(guān)系為has-a關(guān)系,此時適合使用組合。

3.當(dāng)遇到特殊情況,is-a和has-a都可以講通時,優(yōu)先使用組合

8.總結(jié)

C++的語法復(fù)雜在于C++是第一個吃螃蟹的人,很多地方會考慮太多,拿多繼承舉例,有了多繼承就有了菱形繼承,有了菱形繼承,就有了菱形虛擬繼承,底層實(shí)現(xiàn)就更為復(fù)雜了,所以一般不建議設(shè)計(jì)多繼承,設(shè)計(jì)了多繼承也不建議設(shè)計(jì)菱形繼承。

到此這篇關(guān)于詳解C++中單繼承與多繼承的使用的文章就介紹到這了,更多相關(guān)C++單繼承 多繼承內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

  • C語言實(shí)現(xiàn)簡單計(jì)算器程序

    C語言實(shí)現(xiàn)簡單計(jì)算器程序

    這篇文章主要為大家詳細(xì)介紹了C語言實(shí)現(xiàn)簡單計(jì)算器程序,文中示例代碼介紹的非常詳細(xì),具有一定的參考價值,感興趣的小伙伴們可以參考一下
    2020-02-02
  • c++實(shí)現(xiàn)解析zip文件的示例代碼

    c++實(shí)現(xiàn)解析zip文件的示例代碼

    這篇文章主要為大家詳細(xì)介紹了如何利用c++實(shí)現(xiàn)解析zip文件,并對流式文件pptx內(nèi)容的修改,文中的示例代碼講解詳細(xì),有需要的小伙伴可以參考一下
    2023-12-12
  • OpenCV實(shí)現(xiàn)繪制輪廓外接矩形

    OpenCV實(shí)現(xiàn)繪制輪廓外接矩形

    這篇文章主要為大家詳細(xì)介紹了OpenCV實(shí)現(xiàn)繪制輪廓外接矩形的方法,文中示例代碼介紹的非常詳細(xì),具有一定的參考價值,感興趣的小伙伴們可以參考一下
    2022-12-12
  • C++?基本數(shù)據(jù)類型中int、long等整數(shù)類型取值范圍及原理分析

    C++?基本數(shù)據(jù)類型中int、long等整數(shù)類型取值范圍及原理分析

    這篇文章主要介紹了C++?基本數(shù)據(jù)類型中int、long等整數(shù)類型取值范圍及原理分析,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教
    2022-11-11
  • C++實(shí)現(xiàn)AVL樹的完整代碼

    C++實(shí)現(xiàn)AVL樹的完整代碼

    AVL樹是高度平衡的而二叉樹。它的特點(diǎn)是:AVL樹中任何節(jié)點(diǎn)的兩個子樹的高度最大差別為1。 今天通過本文給大家分享C++實(shí)現(xiàn)AVL樹的完整代碼,感興趣的朋友一起看看吧
    2021-06-06
  • C語言數(shù)據(jù)的存儲超詳細(xì)講解上篇

    C語言數(shù)據(jù)的存儲超詳細(xì)講解上篇

    使用編程語言進(jìn)行編程時,需要用到各種變量來存儲各種信息。變量保留的是它所存儲的值的內(nèi)存位置。這意味著,當(dāng)您創(chuàng)建一個變量時,就會在內(nèi)存中保留一些空間。您可能需要存儲各種數(shù)據(jù)類型的信息,操作系統(tǒng)會根據(jù)變量的數(shù)據(jù)類型,來分配內(nèi)存和決定在保留內(nèi)存中存儲什么
    2022-04-04
  • C++中extern

    C++中extern "C"的用法

    這篇文章主要介紹了C++中extern "C"的用法,是深入理解C++所應(yīng)該掌握的概念,需要的朋友可以參考下
    2014-08-08
  • C語言函數(shù)指針詳解

    C語言函數(shù)指針詳解

    大家好,本篇文章主要講的是C語言函數(shù)指針詳解,感興趣的同學(xué)趕快來看一看吧,對你有幫助的話記得收藏一下,方便下次瀏覽
    2022-01-01
  • C++?Primer的變量和基本類型詳解

    C++?Primer的變量和基本類型詳解

    這篇文章主要為大家介紹了C++?Primer,具有一定的參考價值,感興趣的小伙伴們可以參考一下,希望能夠給你帶來幫助
    2022-01-01
  • C語言簡明講解快速排序的應(yīng)用

    C語言簡明講解快速排序的應(yīng)用

    快速排序由于排序效率在同為O(N*logN)的幾種排序方法中效率較高,因此經(jīng)常被采用,再加上快速排序思想----分治法也確實(shí)實(shí)用,因此很多軟件公司的筆試面試,包括像騰訊,微軟等知名IT公司都喜歡考這個,還有大大小的程序方面的考試如軟考,考研中也常常出現(xiàn)快速排序的身影
    2022-05-05

最新評論