91超碰碰碰碰久久久久久综合_超碰av人澡人澡人澡人澡人掠_国产黄大片在线观看画质优化_txt小说免费全本

溫馨提示×

溫馨提示×

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

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務條款》

C++數據結構繼承的概念與菱形繼承及虛擬繼承和組合分析

發布時間:2022-03-01 09:15:21 來源:億速云 閱讀:177 作者:iii 欄目:開發技術

這篇“C++數據結構繼承的概念與菱形繼承及虛擬繼承和組合分析”文章的知識點大部分人都不太理解,所以小編給大家總結了以下內容,內容詳細,步驟清晰,具有一定的借鑒價值,希望大家閱讀完這篇文章能有所收獲,下面我們一起來看看這篇“C++數據結構繼承的概念與菱形繼承及虛擬繼承和組合分析”文章吧。

繼承的概念

繼承:繼承機制是面向對象程序設計使代碼可以復用的最重要的手段,它允許程序員在保持原有類特性的基礎上進行擴展,增加功能,這樣產生新的類,稱派生類。繼承呈現了面向對象程序設計的層次結構,體現了由簡單到復雜的認知過程。以前我們接觸的復用都是函數復用,繼承是類設計層次的復用。

繼承的定義

語法:

C++數據結構繼承的概念與菱形繼承及虛擬繼承和組合分析

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

繼承方式有三種:

  • public繼承

  • protected繼承

  • private繼承

訪問限定符:

  • public訪問

  • protected訪問

  • private訪問

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

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

總結:

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

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

  • 一般會把基類中不想讓類外訪問的成員設置為protecd成員,不讓類外訪問,但是讓派生類可以訪問。

基類和派生類對象之間的賦值轉換

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

C++數據結構繼承的概念與菱形繼承及虛擬繼承和組合分析

實例演示:

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;// 學號
	int _major = 0;// 專業
};
int main()
{
	Student s;
	// 子類對象可以賦值給父類的對象、指針和引用  反過來不行
	// Student對象通過 “切片” 或 “切割” 的方式進行賦值
	Person p1 = s;
	Person* p2 = &s;
	Person& p3 = s;

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

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

	ps->Print();

	return 0;
}

C++數據結構繼承的概念與菱形繼承及虛擬繼承和組合分析

總結:

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

  • 基類對象不能給派生類對象賦值。

  • 基類的指針可以通過強制類型轉換賦值給派生類的指針。但必須是基類的指針指向派生類的對象才是安全的,因為如果基類是多態類型,會引發多態。

繼承中的作用域

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

實例演示:

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;// 工號
};
int main()
{
	Teacher t;

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

	return 0;
}

代碼運行結果如下:

C++數據結構繼承的概念與菱形繼承及虛擬繼承和組合分析

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

看下面一個小問題: 請問A中的fun函數和B中的fun函數是構成重載還是隱藏?

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);
};

答案: 兩個函數在不同的作用域,不可能構成重載。因為構成重載的條件是兩個函數必須在同一作用域,而隱藏是要求在基類和派生類不同作用域的,所以這里同名成員是構造隱藏。

派生類的默認成員函數

C++中的每個對象中會有6個默認成員函數。默認的意思就是我們不寫,編譯器會生成一個。那么在繼承中,子類的默認成員函數是怎么生成的呢?

先看下面一個例子:

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)// 此處調用父類的構造函數堆繼承下來的成員進行初始化,不謝的話,編譯器調用父類的默認構造函數
		, _stuid(stuid)
	{
		cout << "Student()" << endl;
	}
	Student(const Student& s)
		:Person(s)// 子類對象可以傳給父類的對象、指針或引用
		,_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);// 先完成基類的復制
			_stuid = s._stuid;
		}

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

測試1:構造函數和析構函數

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

代碼運行結果如下:

C++數據結構繼承的概念與菱形繼承及虛擬繼承和組合分析

總結1: 子類的構造函數必須調用基類的構造函數初始化基類的那一部分成員,如果基類沒有默認構造函數,則必須在派生類構造函數的初始化列表階段顯示調用。子類的析構函數會在被調用完成后自動調用基類的析構函數清理基類的成員。不需要顯示調用。這里子類和父類的析構函數的函數名會被編譯器處理成destructor,這樣兩個函數構成隱藏。

測試2:拷貝構造函數

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

代碼運行結果如下:

C++數據結構繼承的概念與菱形繼承及虛擬繼承和組合分析

總結2: 子類的拷貝構造必須代用父類的拷貝構造完成父類成員的拷貝。

測試3:operator=

C++數據結構繼承的概念與菱形繼承及虛擬繼承和組合分析

結論3: 子類的operator=必須調用基類的operator完成基類的賦值。

思考

如何設計一個不能被繼承的類? 把該類的構造函數設為私有。如果基類的構造函數是私有,那么派生類不能調用基類的構造函數完成基類成員的初始化,則無法進行構造。所以這樣設計的類不可以被繼承。(后面還會將加上final關鍵字的類也不可以被繼承)

總結:

  • 子類的構造函數必須調用基類的構造函數初始化基類的那一部分成員,如果基類沒有默認構造函數,則必須在派生類構造函數的初始化列表階段顯示調用。

  • 子類的拷貝構造必須代用父類的拷貝構造完成父類成員的拷貝。

  • 子類的operator=必須調用基類的operator完成基類的賦值。

  • 子類的析構函數會在被調用完成后自動調用基類的析構函數清理基類的成員。不需要顯示調用。

  • 子類對象會先調用父類的構造在調用子類的構造。

  • 子類對象會先析構子類的析構再調用父類的析構。

繼承中的兩個小細節

繼承和友元

友元關系不能被繼承。也就是說基類的友元不能夠訪問子類的私有和保護成員。

繼承和靜態成員

基類定義的static靜態成員,存在于整個類中,不屬于某個類,無論右多少個派生類,都這有一個static成員。

實例演示:

class Person
{
public:
	Person()
	{
		++_count;
	}
	// static成員存在于整個類  無論實例化出多少對象,都只有一個static成員實例
	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 << "人數:" << Student()._count - 1 << endl;

	return 0;
}

代碼運行結果如下:

C++數據結構繼承的概念與菱形繼承及虛擬繼承和組合分析

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

單繼承: 一個子類只有一個直接父類時稱這個繼承關系為單繼承。

C++數據結構繼承的概念與菱形繼承及虛擬繼承和組合分析

多繼承: 一個子類有兩個或以上的直接父類時稱這個繼承關系為多繼承。

C++數據結構繼承的概念與菱形繼承及虛擬繼承和組合分析

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

C++數據結構繼承的概念與菱形繼承及虛擬繼承和組合分析

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

虛擬繼承

概念

為了解決菱形繼承帶來的數據冗余和二義性的問題,C++提出來虛擬繼承這個概念。虛擬繼承可以解決前面的問題,在繼承方式前加椰果virtual的關鍵字即可。

class Person
{
public:
	string _name;
};
// 不要在其他地方去使用。
class Student : virtual public Person
{
public:
	int _num; //學號
};
class Teacher : virtual public Person
{
public:
	int _id; // 職工編號
};
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;
}

我們通過內存窗口查看它的對象模型:

C++數據結構繼承的概念與菱形繼承及虛擬繼承和組合分析

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

C++數據結構繼承的概念與菱形繼承及虛擬繼承和組合分析

組合與繼承

總結一下幾點:

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

  • public繼承是一種is-a的關系。也就是說每個派生類對象都是一個基類對象-。

  • 組合是一種has-a的關系。假設B組合了A,每個B對象中都有一個A對象。

  • 優先使用對象組合,而不是類繼承 。

  • 繼承允許你根據基類的實現來定義派生類的實現。這種通過生成派生類的復用通常被稱為白箱復用。術語“白箱”是相對可視性而言:在繼承方式中,基類的內部細節對子類可見 。繼承一定程度破壞了基類的封裝,基類的改變,對派生類有很大的影響。派生類和基類間的依賴關系很強,耦合度高。

  • 對象組合是類繼承之外的另一種復用選擇。新的更復雜的功能可以通過組裝或組合對象來獲得。對象組合要求被組合的對象具有良好定義的接口。這種復用風格被稱為黑箱復用,因為對象的內部細是不可見的。對象只以“黑箱”的形式出現。 組合類之間沒有很強的依賴關系,耦合度低。優先使用對象組合有助于你保持每個類被封裝。

  • 實際盡量多去用組合。組合的耦合度低,代碼維護性好。不過繼承也有用武之地的,有些關系就適合繼承那就用繼承,另外要實現多態,也必須要繼承。類之間的關系可以用繼承,可以用組合,就用組合。

C++的缺陷之一:

多繼承就是一個。多繼承會帶來菱形繼承,菱形繼承又會帶來數據冗余和二義性,為了解決這個問題,又引入了虛擬繼承。進而導致C++的底層結構對象模型非常復雜,這樣會帶來一定的損失。所以說盡量不要設計出菱形繼承。

以上就是關于“C++數據結構繼承的概念與菱形繼承及虛擬繼承和組合分析”這篇文章的內容,相信大家都有了一定的了解,希望小編分享的內容對大家有幫助,若想了解更多相關的知識內容,請關注億速云行業資訊頻道。

向AI問一下細節

免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。

c++
AI

安顺市| 灵台县| 南召县| 偏关县| 潢川县| 西昌市| 贵定县| 库尔勒市| 沛县| 林口县| 高唐县| 长海县| 浑源县| 特克斯县| 绥阳县| 桃江县| 新干县| 洛川县| 柘城县| 明星| 无为县| 临洮县| 光山县| 新密市| 托克逊县| 平原县| 肥城市| 大化| 社会| 峨山| 镇原县| 丰都县| 张北县| 博野县| 隆子县| 通海县| 万山特区| 孟州市| 忻州市| 双柏县| 武胜县|