欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

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

 更新時(shí)間:2022年02月28日 16:27:13   作者:呆呆獸學(xué)編程  
今天我要給大家介紹C++中更深入的內(nèi)容了。C++這門語(yǔ)言為了使代碼不冗余,做了些什么操作呢?C++的繼承就很好地實(shí)現(xiàn)了類層次的代碼復(fù)用,今天我就要來和大家好好聊一聊它了

??博客代碼已上傳至gitee:https://gitee.com/byte-binxin/cpp-class-code

??繼承的概念

繼承:繼承機(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)知過程。以前我們接觸的復(fù)用都是函數(shù)復(fù)用,繼承是類設(shè)計(jì)層次的復(fù)用。

??繼承的定義

語(yǔ)法:

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

繼承方式有三種:

  • public繼承
  • protected繼承
  • private繼承

訪問限定符:

  • public訪問
  • protected訪問
  • private訪問

繼承基類成員的訪問方式的變化:

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

總結(jié):

  • 基類的private成員在派生類中都是不可見的,這里的不可見是指基類的私有成員還是被繼承到了派生類對(duì)象中,但是語(yǔ)法上限制派生類對(duì)象不管在類里面還是類外面都不能去訪問它。
  • 基類成員在父類中的訪問方式=min(成員在基類的訪問限定符,繼承方式),public>protected>private。
  • 一般會(huì)把基類中不想讓類外訪問的成員設(shè)置為protecd成員,不讓類外訪問,但是讓派生類可以訪問。

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

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

實(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ì)象、指針和引用  反過來不行
	// Student對(duì)象通過 “切片” 或 “切割” 的方式進(jìn)行賦值
	Person p1 = s;
	Person* p2 = &s;
	Person& p3 = s;

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

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

	ps->Print();

	return 0;
}

總結(jié):

  • 派生類對(duì)象可以“切片”或“切割”的方式賦值給基類的對(duì)象,基類的指針或基類的引用,就是把基類的那部分切割下來。
  • 基類對(duì)象不能給派生類對(duì)象賦值。
  • 基類的指針可以通過強(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ù)或同名成員變量) 可以通過指定域作用限定符訪問

	return 0;
}

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

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

看下面一個(gè)小問題: 請(qǐng)問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ù)堆繼承下來的成員進(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)成隱藏,是一樣指定域訪問
		//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é)果如下:

總結(jié)1: 子類的構(gòu)造函數(shù)必須調(diào)用基類的構(gòu)造函數(shù)初始化基類的那一部分成員,如果基類沒有默認(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é)果如下:

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

測(cè)試3:operator=

結(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ù)初始化基類的那一部分成員,如果基類沒有默認(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)系不能被繼承。也就是說基類的友元不能夠訪問子類的私有和保護(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é)果如下:

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

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

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

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

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

??虛擬繼承

??概念

為了解決菱形繼承帶來的數(shù)據(jù)冗余和二義性的問題,C++提出來虛擬繼承這個(gè)概念。虛擬繼承可以解決前面的問題,在繼承方式前加椰果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;
}

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

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

??組合與繼承

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

  • 組合和繼承都屬于類層次的復(fù)用。
  • public繼承是一種is-a的關(guān)系。也就是說每個(gè)派生類對(duì)象都是一個(gè)基類對(duì)象-。
  • 組合是一種has-a的關(guān)系。假設(shè)B組合了A,每個(gè)B對(duì)象中都有一個(gè)A對(duì)象。
  • 優(yōu)先使用對(duì)象組合,而不是類繼承 。
  • 繼承允許你根據(jù)基類的實(shí)現(xiàn)來定義派生類的實(shí)現(xiàn)。這種通過生成派生類的復(fù)用通常被稱為白箱復(fù)用。術(shù)語(yǔ)“白箱”是相對(duì)可視性而言:在繼承方式中,基類的內(nèi)部細(xì)節(jié)對(duì)子類可見 。繼承一定程度破壞了基類的封裝,基類的改變,對(duì)派生類有很大的影響。派生類和基類間的依賴關(guān)系很強(qiáng),耦合度高。
  • 對(duì)象組合是類繼承之外的另一種復(fù)用選擇。新的更復(fù)雜的功能可以通過組裝或組合對(duì)象來獲得。對(duì)象組合要求被組合的對(duì)象具有良好定義的接口。這種復(fù)用風(fēng)格被稱為黑箱復(fù)用,因?yàn)閷?duì)象的內(nèi)部細(xì)是不可見的。對(duì)象只以“黑箱”的形式出現(xiàn)。 組合類之間沒有很強(qiáng)的依賴關(guān)系,耦合度低。優(yōu)先使用對(duì)象組合有助于你保持每個(gè)類被封裝。
  • 實(shí)際盡量多去用組合。組合的耦合度低,代碼維護(hù)性好。不過繼承也有用武之地的,有些關(guān)系就適合繼承那就用繼承,另外要實(shí)現(xiàn)多態(tài),也必須要繼承。類之間的關(guān)系可以用繼承,可以用組合,就用組合。

C++的缺陷之一:

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

??總結(jié)

C++的繼承使我們變得更加的富有,其中多繼承也是C++的缺陷。我們要盡量避開不好的而選擇好的一面。這篇博客就介紹到這里了,喜歡的話,歡迎點(diǎn)贊。支持和關(guān)注~

到此這篇關(guān)于C++數(shù)據(jù)結(jié)構(gòu)繼承的概念與菱形繼承及虛擬繼承和組合的文章就介紹到這了,更多相關(guān)C++ 繼承內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

  • C++類常量和類枚舉

    C++類常量和類枚舉

    這篇文章主要介紹了C++類常量和類枚舉,給類當(dāng)中定義一些常量,可以給所有類的對(duì)象使用,比如說我們?cè)陬惍?dāng)中定義一個(gè)數(shù)組,希望可以定義一個(gè)常量,用來初始化數(shù)組的長(zhǎng)度,那么下面我i嗎就來看看過程當(dāng)如何吧
    2022-01-01
  • C語(yǔ)言的Struct Hack筆記

    C語(yǔ)言的Struct Hack筆記

    這篇文章主要介紹了C語(yǔ)言的Struct Hack例子,個(gè)人的一篇筆記,需要的朋友可以參考下吧
    2014-04-04
  • C語(yǔ)言結(jié)構(gòu)體嵌套與對(duì)齊超詳細(xì)講解

    C語(yǔ)言結(jié)構(gòu)體嵌套與對(duì)齊超詳細(xì)講解

    這篇文章主要介紹了C語(yǔ)言結(jié)構(gòu)體嵌套與對(duì)齊,C語(yǔ)言中結(jié)構(gòu)體是一種構(gòu)造類型,和數(shù)組、基本數(shù)據(jù)類型一樣,可以定義指向該種類型的指針。結(jié)構(gòu)體指針的定義類似其他基本數(shù)據(jù)類型的定義
    2022-12-12
  • C++構(gòu)造函數(shù)詳解

    C++構(gòu)造函數(shù)詳解

    這篇文章主要介紹了C++構(gòu)造函數(shù)詳解,上一篇文章我們介紹了定義了類,在使用之前,往往還需要對(duì)類進(jìn)行初始化。這篇介紹的就是對(duì)類進(jìn)行初始化的方法,需要的朋友可以參考一下
    2022-01-01
  • C++編寫生成不重復(fù)的隨機(jī)數(shù)代碼

    C++編寫生成不重復(fù)的隨機(jī)數(shù)代碼

    本文給大家匯總介紹了3種c++實(shí)現(xiàn)生成不重復(fù)的隨機(jī)數(shù)的函數(shù),十分的簡(jiǎn)單實(shí)用,有需要的小伙伴可以參考下。
    2015-05-05
  • MFC程序設(shè)計(jì)常用技巧匯總

    MFC程序設(shè)計(jì)常用技巧匯總

    這篇文章主要介紹了MFC程序設(shè)計(jì)常用技巧,實(shí)例匯總了MFC程序設(shè)計(jì)中常見的問題與解決方法,非常具有實(shí)用價(jià)值,需要的朋友可以參考下
    2015-05-05
  • Qt編寫地圖綜合應(yīng)用之繪制雨量分布

    Qt編寫地圖綜合應(yīng)用之繪制雨量分布

    雨量分布圖是在區(qū)域地圖基礎(chǔ)上,針對(duì)區(qū)域中的每個(gè)最小單位區(qū)域比如縣城點(diǎn)位不同顏色顯示。本文將詳細(xì)為大家介紹如何通過QT編寫繪制雨量分布,感興趣的小伙伴可以了解一下
    2021-12-12
  • 探究在C++程序并發(fā)時(shí)保護(hù)共享數(shù)據(jù)的問題

    探究在C++程序并發(fā)時(shí)保護(hù)共享數(shù)據(jù)的問題

    這篇文章主要介紹了探究在C++程序并發(fā)時(shí)保護(hù)共享數(shù)據(jù)的問題,也有利于大家更好地理解C++多線程的一些機(jī)制,需要的朋友可以參考下
    2015-07-07
  • 詳解C++之函數(shù)重載

    詳解C++之函數(shù)重載

    這篇文章主要介紹了c++函數(shù)重載的相關(guān)知識(shí),文章講解的非常細(xì)致,代碼幫助大家更好的理解和學(xué)習(xí),感興趣的朋友可以了解下
    2020-06-06
  • C++之重載 重定義與重寫用法詳解

    C++之重載 重定義與重寫用法詳解

    這篇文章主要介紹了C++之重載 重定義與重寫用法詳解,本篇文章通過簡(jiǎn)要的案例,講解了該項(xiàng)技術(shù)的了解與使用,以下就是詳細(xì)內(nèi)容,需要的朋友可以參考下
    2021-09-09

最新評(píng)論