C++基礎知識,多重繼承,虛基類,虛繼承

派生類繼承構造函數

如果基類含多個構造函數,多數情況下,派生類一般會集成所有構造函數,以下幾種情況比較特殊

  • 1、如果你在派生類中定義的構造函數與基類構造函數有相同的參數列表,那麼從基類中繼承的會被覆蓋掉也就是只繼承了及部分構造函數
  • 2、拷貝,移動構造函數不會被繼承
  • 3、using classA::classA,這不屬於自己定義構造函數,編譯器還是會合成一個默認的無參構造函數,但是這樣聲明瞭之後,父類必須有無參構造函數

自己管自己的父類
一個類只繼承直接基類的(父類)的構造函數。默認,拷貝,移動構造函數不能被集成

class classA
{
public:
	//classA(int i, int j, int k){}
	classA(int i, int j, int k = 0){}
	classA() {}
};
class classB:public classA
{
public:
	//classB(int i, int j, int k) :classA(i, j, k){}
	using classA::classA;
};

這樣看來,using就是讓某個函數名字可見
遇到這條代碼是,編譯器會把基類中每個構造函數,都生成一個與之對應的構造函數
但是到底長什麼樣呢?
classB(參數列表):classA(照抄父類參數列表){},比如這個
classB(int i,int j,int k):classA(i,j,k){}

如果有默認參數的話,那麼編譯器遇到using classA::classA的時候
就會幫我們在派生類中構造出多個構造函數出來

  • 1、帶所有參數的構造函數
  • 2、其餘分別少一個默認參數
    例如classA(int i, int j, int k = 0)
    如果使用using classA::classA,那麼就會有如下的默認構造函數生成
    1、classB(int i , int j , int k):A(i,j,k){}
    2、classB(int i , int j):A(i,j){}

多重集成

class grandFather
{
public:
	int m_gf;
public:
	//這裏參數列表賦值
	grandFather(int i) :m_gf(i)
	{
		cout << "grandFather構造函數" << endl;
	}

	void myInfo()
	{
		cout << m_gf << endl;
	}
	virtual ~grandFather()
	{
		cout << "grandFather析構函數" << endl;
	}
};

class father :public grandFather
{
public:
	int m_f;
public:
	//每個子類的構造函數,負責解決自己父類的初始化問題
	//每個類都要解決直接父類的初始化問題
	//不然只會調用默認的無參構造函數
	//想一想,java的雙親委託機制是不是就是這樣實現的?
	father(int i) :m_f(i), grandFather(m_f)
	{
		cout << "father構造函數" << endl;
	}
	virtual ~father()
	{
		cout << "father析構函數" << endl;
	}
	void myInfo()
	{
		cout << m_f << endl;
	}
};

class others
{
public:
	int m_o;
public:
	//每個子類的構造函數,負責解決自己父類的初始化問題
	others(int i) :m_o(i) 
	{
		cout << "others構造函數" << endl;
	}
	virtual ~others()
	{
		cout << "others析構函數" << endl;
	}
	void myInfo()
	{
		cout << m_o << endl;
	}
};

//派生列表
class classSon :public father, public others
{
public:
	int m_s;
public:
	//有兩個直接父類,都要把他們初始化
	classSon(int i, int j, int k) :father(i), others(j),m_s(k)
	{
		cout << "classSon構造函數" << endl;
	}
	virtual ~classSon()
	{
		cout << "classSon析構函數" << endl;
	}
	//因爲父類函數衝突了,子類重寫覆蓋
	void sonInfo()
	{
		cout << m_s << endl;
	}
	//遇到衝突的問題,可以直接在子類中,進行
	//父類::函數/參數
	void sonCallParentInfo()
	{
		father::myInfo();
		others::myInfo();
		sonInfo();
	}
};
  • 再多重繼承中,派生類會包含每個基類的子對象
int main()
{
	classSon class1(1, 2, 3);
	class1.sonInfo();
}

執行結果:
在這裏插入圖片描述

  • father覆蓋了grandFather中的myInfo();如果子類繼承的多個類中都有myInfo這個函數,就要顯示聲明調用的是哪個函數
	classSon class1(1, 2, 3);
	class1.father::myInfo();
	class1.others::myInfo();

在這裏插入圖片描述

靜態成員變量

屬於類,不屬於對象
但是對象也是可以調用的
在grandFather中加上

class grandFather
{
public:
	static int gf_SI;
}
//爲了能夠使用,要定義這個靜態類
//沒有用到就不用定義,使用必須定義,光聲明是不行的
int grandFather::gf_SI = 666;

int main()
{
	cout << grandFather::gf_SI << endl;
	cout << father::gf_SI << endl;
	cout << classSon::gf_SI << endl;
	//對象也是可以的
	cout << class1.gf_SI << endl;
}

在這裏插入圖片描述

派生類構造函數與析構函數

  • 1、構造一個派生類對象,將同時構造初始化所有基類子對象
  • 2、派生類的初始化列表只初始化它的直接基類,這樣所有的都能初始化
  • 3、派生類構造函數吃石化列表將實參分別傳遞給每個直接基類,基類的構造順序由派生列表決定,派生列表:class classSon :public father, public others,跟初始化列表(classSon(int i, int j, int k) :father(i), others(j),m_s(k))沒有關係

顯式的初始化與隱士的初始化(使用默認構造函數,不帶參數)

//新建了一個類others2,只有無參構造方法
class others2
{
public:
	//是默認的無參構造方法
	others2() 
	{
		cout << "others2構造函數" << endl;
	}

	virtual ~others2()
	{
		cout << "others2析構函數" << endl;
	}
};
class classSon :public father, public others,public others2
{
public:
	int m_s;
public:
	//雖然繼承了others2類,但是由於只有無參構造函數,不顯式初始化也是可以的
	classSon(int i, int j, int k) :father(i), others(j),m_s(k)
	{}
}

繼承的兩個構造函數參數重複,會導致程序錯誤,必須定義自己的版本

class A
{
public:
	A(int val) {};
};
class B
{
public:
	B(int val) {};
};
class C :public A, public B
{
public:
	//這樣繼承了A,B的構造函數,但是一摸一樣(只要參數列表一樣就是一摸一樣)
	using A::A;
	using B::B;
	//必須定義自己的參數列表一樣的去覆蓋
	C(int val) :A(val), B(val) {}
};

解釋爲什麼基類指針可以指向子類對象

基類指針了一指向派生類對象,編譯器會幫助我們進行轉換,因爲每個派生類對象包含一個基類對象區域,所以基類的指針和引用可以綁定到基類上

	grandFather* g = new classSon(1, 2, 3);

虛基類,虛繼承

派生列表中同一類只能出現一次,但是以下情況特別

  • 1、通過兩個直接基類,繼承了兩次間接基類
  • 2、直接繼承該基類,通過直接繼承另一個類,又間接繼承了該類

初始化多次,名字衝突,不能直接調用那個類的方法,因爲不知道用哪一個的
如下:
我們重寫others類,讓它也和father一樣繼承grandfather類

class others:public grandFather
{
public:
	int m_o;
public:
	//初始化grandFather
	others(int i) :m_o(i) ,grandFather(i)
	{
		cout << "others構造函數" << endl;
	}
	virtual ~others()
	{
		cout << "others析構函數" << endl;
	}
	void myInfo()
	{
		cout << m_o << endl;
	}
};

int main()
{
	classSon class1(1,2,3);
}

然後classSon類實際上通過father,others類繼承了兩次grandfather類,看一看初始化classSon類的效果
真的構造了兩次,不行不行
在這裏插入圖片描述
解決方法:虛繼承
首先,虛基類:無論這個類在繼承體系中出現多少次,派生類中都只會包含唯一一個虛基類子內容
這種虛繼承不對直接子類有意義,只對直接子類派生出來的類纔有意義
最後所有虛基類的子類都要虛繼承,才能保證孫類是虛繼承
格式:class classA:virtual father,並且要用孫類的構造函數進行初始化

class classSon : public father, public others
{
public:
	//如果父類是虛繼承,那麼初始化虛基類的任務就要交給最小輩兒的類來
	classSon(int i, int j, int k) :grandFather(k),father(i), others(j),m_s(k)
	{cout << "classSon構造函數" << endl;}
}


//			聲明虛繼承
class others:virtual public grandFather
{
public:
//						這裏初始化也沒什麼用了
	others(int i) :m_o(i) ,grandFather(i)
}


//			聲明虛繼承
class father :virtual public grandFather
{
public:
	//						這裏初始化也沒什麼用了
	father(int i) :m_f(i), grandFather(m_f)
}

int main()
{
	classSon class1(1,2,3);
}

執行效果:
在這裏插入圖片描述

說明兩點:

  • 1、現在是孫子類構造函數初始化,如果以後它再有子類,那麼也要進行加上換句話說,就是最底層的進行初始化虛基類
  • 2、虛基類不管派生列表的順序,它就是首先進行初始化
  • 3、多個虛基類,從派生列表的繼承順序,往回追溯,先找到那個虛基類,就先初始化哪個

總結:

  • 小心虛繼承,不太提倡使用
  • 簡單,不易出現二義性,實在必要才用。
發佈了157 篇原創文章 · 獲贊 167 · 訪問量 2萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章