天天看點

3.8、基類指針、虛純虛函數、多态性、虛析構虛函數與多态虛函數析構函數一般寫成虛函數

基類指針、虛純虛函數、多态性、虛析構

  • 虛函數與多态
  • 虛函數
  • 析構函數一般寫成虛函數

虛函數與多态

#include<iostream>
#include<cstdlib>
#include<string>
#include<vector>
using namespace std;

class  Human
{
public:
	int age;
	string name;
public:
	//構造函數
	Human();
	Human(int age, string name);
	//析構函數
	~Human()
	{
		cout << "Human析構函數" << endl;
	}
public:
	void myFunction();
	//virtual void eat() final;
	virtual void eat()//虛函數的實作	運作時動态識别
	{
		cout << "這是Human的eat函數" << endl;
	}
};

class Man:public Human
{
public:
	Man();
public:
	void myFunction();
	void eat() override;
	
};
void Man::eat()
{
	cout << "man中的eat()函數執行" << endl;
}


class Woman:public Human
{
public:
	Woman();
public:
	void myFunction();
	void eat() override
	{
		cout << "woman中的eat()函數" << endl;
	}
};


void Human::myFunction()
{
	cout << "Human類中的函數執行" << endl;
}

void Man::myFunction()
{
	cout << "Man類中的myFunction函數執行" << endl;
}


Human::Human()
{
	cout << "Human預設構造函數" << endl;
}

Human::Human(int age, string name)
{
	this->age = age;
	this->name = name;
	
}

Woman::Woman()
{
	cout << "woman的構造函數" << endl;
}

void Woman::myFunction()
{
	cout << "Woman類中的myFunction函數" << endl;
}



Man::Man()
{
	cout << "Man類的預設構造函數" << endl;
}



int main(void)
{
	Human*p_human = new Human;
	Man *p_man = new Man;

	//子類是特殊的父類,父類指針可以new一個子類對象
	Human*p_human2 = new Man;	//ok,使用子類對象初始化父類指針
	Human*p_human3 = new Woman;
	
	p_human2->myFunction();	//這裡調用父類中的函數
	//但是父類指針實際上是指向子類的對象,如果想調用子類的函數?--->新需求
	//---父類指針指向父類對象還是子類對象就調用對應的函數--->多态
	p_human->eat();	//調用父類eat()
	p_human2->eat();//調用Man類eat()
	p_human3->eat();//調用Woman中的函數eat()
	p_human3->Human::eat();//顯式調用父類的eat()函數

	
	delete p_human;
	delete p_human2;
	delete p_human3;
	/*
	 *都是調用Human的析構函數,若果想調用各個子類的析構函數應該怎麼辦?
	 *
	 */
	system("pause");
	return 0;

}
/*(1)基類指針,派生類指針
 *	使用new建立對象都是傳回指針
 *	父類指針使用子類對象初始化,但是隻能調用父類中的同名函數---->如何調用子類同名函數--->新需求--->虛函數-->多态
 *
 *
 *實際中當函數的參數是父類指針,可以接受所有的子類對象作為實參傳遞過來,實作動态類型識别來調用父類和子類中的同名函數。
 *(2)虛函數--實作多态
 *	父類的同名函數前面增加virtual.來表示這是一個虛函數,可以實作多态。子類的同名函數可以不寫virtual,自動為虛函數。
 *	父類函數和子類函數的名字,參數,參數類型,必須完全相同。才會有多态。
 *
 *	重載--在一個類内部函數名字相同,但是參數個數,參數類型,參數順序不同,調用時候使用參數來差別。
 *	重寫:虛函數實作多态,父類中的同名函數在子類中函數名字,參數都不能改變。
 *	重定義:繼承中子類重新寫了這個函數,改變了參數個數,類型,參數順序等。
 *
 *為了避免你在子類中寫錯虛函數,在c++中,你可以在函數聲明中增加一個override的關鍵字。這個關鍵字用在“子類”中,
 *	必須父類是虛函數,才可以加上override,否則會報錯。當函數不能覆寫的時候也會報錯。
 *	override就是用來說明派生類中的虛函數,編譯器就會認為你這個eat()函數覆寫了父類中的同名函數,
 *	編譯器就會在父類中找同名同參數的虛函數,如果沒找到,就會報錯。
 *
 *(3)final關鍵字
 *	是與overrode相對的,也是用在虛函數中,使用在“父類”中,如果我們在父類的函數中聲明了“final”關鍵字,那麼任何嘗試
 *	覆寫該函數的操作都将引發錯誤。
 *
 *	動态綁定:動态表示的就是我們的程式運作的時候才知道父類指針指向哪個對象,調用對象的對應的函數。
 *	運作的時候才知道哪個對象綁定到eat()函數中。
 *(4)多态性
 *	多态性是針對虛函數來說的。
 *	展現在具有繼承關系的父類和子類之間,子類重寫了父類成員函數virtual,通過父類的指針,隻有到了程式運作時期找到程式動态綁定到
 *	父類指針的函數,然後系統内部查詢虛函數表執行對應的函數(父類和子類的虛函數)。
 *
 *
 *(6)
 * 
 */




           

虛函數

#include<iostream>
#include<cstdlib>
#include<string>
#include<vector>
using namespace std;

class  Human
{
public:
	int age;
	string name;
public:
	//構造函數
	Human();
	Human(int age, string name);
	//析構函數
	~Human()
	{
		cout << "Human析構函數" << endl;
	}
public:
	virtual  void eat() = 0;
};

class Man :public Human
{
public:
	Man();
	virtual void eat() override;
public:

};

class Woman :public Human
{
public:
	Woman();
	virtual void eat() override;
public:

};

Woman::Woman()
{
	cout << "這是woman的構造函數" << endl;
}

Human::Human()
{
	cout << "Human預設構造函數" << endl;
}

Human::Human(int age, string name)
{
	this->age = age;
	this->name = name;

}

Man::Man()
{
	cout << "Man類的預設構造函數" << endl;
}



void Woman::eat()
{
	cout << "Woman類的eat()執行" << endl;
}
void Man::eat()
{
	cout << "Man類的eat()執行" << endl;
}


void playObject()
{
	Human *p_human01 = new Woman;
	p_human01->eat();//調用Woman的eat()函數

	Human*p_human02 = new Man;
	p_human02->eat();//調用Man的eat()函數
}

int main(void)
{
	playObject();

	
	system("pause");
	return 0;

}
/*(1)純虛函數 - 在基類中定義的 函數傳回值  函數名字(函數參數) = 0子類必須實作所有的春純虛函數
 *一旦父類有純虛函數,那麼就不能生成父類的執行個體化對象。主要用于當成基類用來生成子類用的。
 *因為父類不能執行個體化對象,是以父類的純虛函數也就沒有辦法調用。

*
*/





           

析構函數一般寫成虛函數

#include<iostream>
#include<cstdlib>
#include<string>
#include<vector>
using namespace std;

class  Human
{
public:
	int age;
	string name;
public:
	//構造函數
	Human();
	Human(int age, string name);
	//析構函數
	virtual ~Human()
	{
		cout << "Human析構函數" << endl;
	}
public:
	virtual  void eat() = 0;
};

class Man :public Human
{
public:
	Man();
	virtual void eat() override;
public:
	~Man()
	{
		cout << "執行了Man的析構函數" << endl;
	}

};

class Woman :public Human
{
public:
	Woman();
	virtual void eat() override;
public:
	~Woman()
	{
		cout << "執行了Woman的析構函數" << endl;
	}

};

Woman::Woman()
{
	cout << "這是woman的構造函數" << endl;
}

Human::Human()
{
	cout << "Human預設構造函數" << endl;
}

Human::Human(int age, string name)
{
	this->age = age;
	this->name = name;

}

Man::Man()
{
	cout << "Man類的預設構造函數" << endl;
}



void Woman::eat()
{
	cout << "Woman類的eat()執行" << endl;
}
void Man::eat()
{
	cout << "Man類的eat()執行" << endl;
}


void playObject()
{
	Human *p_human01 = new Woman;
	p_human01->eat();//調用Woman的eat()函數

	Human*p_human02 = new Man;
	p_human02->eat();//調用Man的eat()函數

	//執行正确,會執行父類構造和子類構造 ,父類析構,子類析構
	Woman *p_woman = new Woman;
	delete p_woman;
	
	//都是隻執行父類的析構函數,沒有執行子類的析構函數
	cout << "-------------------------------------" << endl;
	delete p_human01;
	delete p_human02;
	/*
	執行了Woman的析構函數
	Human析構函數
	執行了Man的析構函數
	Human析構函數
	 */
}

int main(void)
{
	playObject();


	system("pause");
	return 0;

}
/*(1)用基類指針指向子類的對象,當delete隻調用父類的析構函數,不會執行子類的析構函數
 *解決辦法:
 *	把父類中的析構函數寫成虛函數。
 *
 *	總結:
 *		1.一般子類都是public繼承。在public繼承中,基類對派生類機器對象的操作,隻能影響到從基類繼承下來的成員。
 *			如果想要用派生類對基類非繼承成員函數進行操作,怎要把這個成員函數定義為虛函數。析構函數也是如此。
 *		2.基類中析構函數的虛函數特性也會繼承給子類,這樣子類的析構函數自然也是虛函數。雖然子類的析構函數和父類的
 *			析構函數名字不同,但是他們都是繼承的虛函數。
 *		3.delete删除一個指向子類對象的父類指針的時候,肯定要調用父類的析構函數,在子類的析構函數中調用父類的析構函數。
 *			是以父類的析構函數就要聲明為虛函數,也就是說,c++為了獲得運作時的多态,所調用的成員函數必須是Virtual類型的。
 *
 *		4.結論:如果一個類如果想做基類,務必把類的析構函數寫成virtual類型,隻要基類的析構函數是虛函數,就能保證我們delete
 *		基類指針的時候調用正确的析構函數版本(先調用子類,再調用父類)
 *
 *		虛函數會增加記憶體開銷,類裡面定義虛函數,編譯器就會給這個類增加虛函數表,這個表裡面存放虛函數的指針。
 *
 *
 *調試資訊:
 *	當我們把析構或者構造函數的寫在類内部實作,可能更改為内聯函數,不能f11進入函數内部執行。可以在函數體設定一個斷點。
 *	(前面說的沒有執行我們自己的寫的構造或者析構是錯誤的)

*
*/





           

繼續閱讀