c++之類的繼承入門

c++ — 類的繼承入門

繼承與派生概述:

1、保持已有類的特性而構造新類的過程稱爲繼承;在已有類的基礎上新增自己的特性而產生新類的過程稱爲派生
2、被繼承的已有類稱爲基類(或父類)
3、派生出的新類稱爲派生類(或子類)
4、直接參與派生出某類的基類稱爲直接基類
5、基類的基類甚至更高層的基類稱爲間接基類
繼承的目的: 實現設計與代碼的重用
派生的目的: 原程序無法解決新問題時,需對原程序進行改造
單繼承時派生類定義:
class 派生類名:繼承方式 基類名
{
成員聲明;
}

多繼承時派生類定義:
class 派生類名:繼承方式1 基類名1,繼承方式2 基類名2… …
{
成員聲明;
}
注:每一個繼承方式僅用於限制對緊隨其後之基類的繼承

派生類構成:
1、吸收的基類成員
默認條件下派生類包含了全部基類中除構造和析構函數之外的所有成員,但c++11標準中規定可以用using語句繼承基類構造函數。
2、改造的基類成員
如果派生類聲明瞭一個某基類成員同名的新成員,則派生的新成員就隱藏或覆蓋了外層同名成員
3、添加新的成員
增加的新功能和數據。

繼承方式

不同繼承方式的影響:
1、派生類成員對基類成員的訪問權限
2、通過派生類對象對基類成員的訪問權限

公有繼承(public)
在這裏插入圖片描述
私有繼承(private)
在這裏插入圖片描述
(若想調用基類的成員函數,可以在派生類中寫一個成員函數,讓這個函數去調用基類的成員函數,就可以用派生類的對象去間接調用基類的成員函數)

保護繼承(protected)在這裏插入圖片描述
protected成員的特點及作用:
1、對建立其所在類的對象來說,它與private成員的性質相同來說。
2、對於其派生類來說,它與public成員的性質相同。
3、既實現了數據隱藏,又方便繼承,實現代碼重用。

多繼承:
(一個類可以用不同的繼承方式繼承不同的類)

#include <iostream>
using namespace std;

class A{
public:
	void setA(int x){
		a=x;
	}
	void showA(){
		cout<<a<<endl;
	}
private:
	int a;
};

class B{
public:
	void setB(int x){
		b=x;
	}
	void showB(){
		cout<<b<<endl;
	}
private:
	int b;
};

class C:public A,private B{      //多繼承,公有繼承A類,私有繼承B類 
public:
	void setC(int,int,int);
	void showC(){
		cout<<c<<endl;
	}
private:
	int c;
};

void C::setC(int x,int y,int z){
	setA(x);
	setB(y);              //不管是公有繼承還是私有繼承,派生類都可以訪問基類的公有成員 
	c=z;
}

int main()
{	
	C c;
	c.setA(4);
	c.showA();              //派生類的對象可以直接訪問公有繼承過來的類的非私有成員 
	c.setC(1,2,3);
	c.showC();
//	c.setB(5);      錯誤 
//	c.showB();      因爲派生類的對象不能直接訪問私有繼承過來的基類的成員 
	return 0;
}

基類與派生類的類型轉換

1.公有繼承的派生類對象可以被當做基類的對象使用,反之則不可:
派生類的對象可以隱含轉換爲基類的對象
派生類的對象可以初始化基類的引用
派生類的指針可以隱含轉換爲基類的指針
2、通過基類對象名、指針只能使用從基類繼承的成員
在這裏插入圖片描述
在這裏插入圖片描述

派生類的構造函數

默認情況下:
1、基類的構造函數不被繼承
2、派生類需要定義自己的構造函數
如何初始化:
1、派生類新增成員:派生類定義構造函數初始化。
2、繼承來的成員:自動調用基類構造函數進行初始化。
3、派生類的構造函數需要給基類的構造函數傳遞參數。
單繼承時構造函數定義語法:
派生類名::派生類名(基類所需的形參,本類所需的形參):
基類名(參數表),本類成員初始化列表
{
//其他初始化;
}
例:

#include <iostream>
using namespace std;

class B{
public:
	B(){
		b=0;
		cout<<"B的默認構造函數"<<endl;
	}
	B(int i){
		b=i;
		cout<<"B的構造函數"<<endl; 
	}
	~B(){
		cout<<"B的析構函數"<<endl;
	}
	void print() const{
		cout<<b<<endl;
	}
private:
	int b;
};

class C:public B{      
public:
	C(){
		c=0;
		cout<<"C的默認構造函數"<<endl;
	}
	C(int,int);
	~C(){
		cout<<"C的析構函數"<<endl;
	}
	void print() const{
		B::print();
		cout<<c<<endl;
	}
private:
	int c;
};

C::C(int i,int j):B(i),c(j){
	cout<<"C的構造函數"<<endl;
}                                //單繼承的構造函數定義

int main()
{	
	C c(4,5);
	c.print();
	return 0;
}

在這裏插入圖片描述
多繼承時構造函數定義語法:
派生類名::派生類名(參數表):
基類名1(基類1初始化參數表),
基類名2(基類2初始化參數表),
… …
基類名n(基類n初始化參數表),
本類成員初始化參數表
{
//其他初始化;
}
注:

多繼承且有對象成員時的派生類的構造函數定義語法:
派生類名::派生類名(形參表)
基類名1(參數),基類名2(參數),… …,基類名n(參數),
本類成員和對象成員的初始化列表
{
//其他初始化
}

構造函數的執行順序:
在這裏插入圖片描述

派生類的複製構造函數

若派生類未聲明覆制構造函數:
編譯器會在需要時生成一個隱含的複製構造函數,先調用基類的複製構造函數,再爲派生類新增的成員執行復制。
若派生類定義複製構造函數:
1、一般要爲基類的複製構造函數傳遞參數;
2、複製構造函數只能接受一個參數,即用來初始化派生類定義的成員,也將被傳遞給基類的複製構造函數;
3、基類的複製構造函數的形參類型時基類對象的引用,實參可以是派生類對象的引用。
例如:
C::C(const C &c1):B(c1){ }

派生類的析構函數

1、析構函數不被繼承,派生類如果需要則需自行聲明析構函數
2、聲明方法與無繼承關係時類的析構函數相同
3、不需要顯式地調用基類的析構函數,系統會自動隱式調用
4、先執行派生類析構函數的函數體,再調用基類的析構函數

訪問從基類繼承的成員

當派生類與基類有相同成員時:
1、若未特別限定,則通過派生類的對象使用的是派生類中的同名成員。
2、若想通過派生類對象訪問基類中的同名成員,應使用基類名和作用域操作符(::)來限定。
二義性問題:
1、如果從不同基類繼承了同名成員,但是在派生類中沒有定義同名成員,則訪問該成員時會出現二義性問題。不能再用 “派生類對象名或引用名 . 成員名” 、 “派生類指針->成員名” 來訪問。
2、此時則只能且必須用類名進行限定。
多繼承時的二義性和冗餘問題:

#include <iostream>
using namespace std;
class Base0{
	public:
		int var0;
		void fun(){cout<<"Base0"<<endl;}
};

class Base1:public Base0{
	public:
		int var1;
};

class Base2:public Base0{
	public:
		int var2;
};

class Derived:public Base1,public Base2{
	public:
		int var;
		void fun(){cout<<"Derived"<<endl;}
};
int main()
{	
	Derived d;
	d.Base1::var0=1;
	d.Base1::fun();
	d.Base2::var0=2;
	d.Base2::fun();
	d.fun();       //訪問Derived的fun函數
	return 0;
}

在這裏插入圖片描述注: 此時可以看出對象的存儲時冗餘的,這樣會帶來空間的浪費,同時會造成不一致性使使用時會發生混淆,故這種情況應該在寫程序時避免。

虛基類

需要解決的問題:
當派生類從多個基類派生,而這些基類又有共同基類,則在訪問此共同基類中的成員時,將產生冗餘,並有可能因爲冗餘帶來不一致性。
虛基類聲明:
以virtual說明基類繼承方式
例:class B1:virtual public B
作用:
1、主要用來解決多繼承時可能發生的對同一基類繼承多次產生的二義性問題
2、爲最遠的派生類提供唯一的基類成員,而不重複產生多次複製
注意:
在第一次繼承時就要將共同基類設置成虛基類

#include <iostream>
using namespace std;
class Base0{
	public:
		int var0;
		void fun0(){cout<<"Base0"<<endl;}
};

class Base1:virtual public Base0{         //虛繼承
	public:
		int var1;
};

class Base2:virtual public Base0{         //虛繼承
	public:
		int var2;
};

class Derived:public Base1,public Base2{
	public:
		int var;
		void fun(){cout<<"Derived"<<endl;}
};
int main()
{	
	Derived d;
	d.var0=2;         //直接訪問虛基類的數據成員
	d.fun0();        //直接訪問虛基類的函數成員
	return 0;
}

在這裏插入圖片描述
此時Derived中只包含一個var0和fun0(),則可以用Derived對象直接調用var0且不存在二義性和冗餘問題。

虛基類及其派生類構造函數:
1、建立對象時所指定的類稱爲最遠派生類。
2、虛基類的成員是由最遠派生類的構造函數通過調用虛基類的構造函數進行初始化的。
3、在整個繼承結構中,直接或間接繼承虛基類的所有派生類,都必須在構造函數的成員初始化表中爲虛基類的構造函數列出參數。如果未列出,則表示調用該虛基類的默認構造函數。
4、在建立對象時,只有最遠派生類的構造函數調用虛基類的構造函數,其他類對虛基類的構造函數的調用被忽略。

#include <iostream>
using namespace std;
class Base0{
	public:
		int var0;
		Base0(int var):var0(var){}
		void fun0(){cout<<"Base0"<<endl;}
};

class Base1:virtual public Base0{
	public:
		Base1(int var):Base0(var){}
		//直接或間接繼承虛基類的所有派生類,都必須在構造函數的成員初始化表中爲虛基類的構造函數列出參數
		int var1;
};

class Base2:virtual public Base0{
	public:
		Base2(int var):Base0(var){}
		int var2;
};

class Derived:public Base1,public Base2{
	public:
		int var;
		Derived(int var):Base0(var),Base1(var),Base2(var){}
		//若不是虛繼承,則第一個Base0(var)不用寫,因爲只需要給它的直接基類的構造函數傳遞參數 
		void fun(){cout<<"Derived"<<endl;}
};
int main()
{	
	Derived d(1);
	//在執行時,只會給虛基類構造函數傳遞一次參數,而Base1和Base2中給虛基類傳遞參數的將會被忽略 
	d.var0=2;
	d.fun0();
	return 0;
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章