多態調用需要滿足:1、虛函數的重寫,2、對象時基類的指針或引用,當滿足這兩點時才說明是多態。
虛函數—類的成員函數前面加上virtual關鍵字,則這個成員函數成爲虛函數。
虛函數重寫—子類定義了與父類相同的虛函數時,則就稱子類重寫了父類的這個虛函數。
先來一段代碼:
#include<iostream>
#include<Windows.h>
using namespace std;
class Person
{
public:
virtual void BuyTicket()
{
cout << "成人全價" << endl;
}
};
class Student : public Person
{
virtual void BuyTicket()
{
cout << "學生半價" << endl;
}
};
void func(Person& p)
{
p.BuyTicket();
}
int main()
{
Person p;
Student s;
func(p);
func(s);
system("pause");
return 0;
}
來看看運行結果
注意:這邊子類即派生類就是與父類函數名相同的可以不使用virtual,因爲子類繼承了父類public的內容。
多態調用:與調用的對象有關,指向那個對象虛函數調用就是誰的。還是以上面的代碼爲例
看看調試的監視窗口
此時有一個指針變量_vfptr,p的第一個元素他指向的是Person所對應的BuyTicket();函數,s繼承父類此時這個指針指向student類裏的BuyTicket();函數。
_vfptr指針其實是虛表指針,這個虛表指針是在構造函數中初始化的,虛表是在編譯時生成的,虛表沒有固定的存儲的區域有時是在靜態區或是數據常量區,一般是在靜態區。
沒有成員的類的大小爲1,但是在多態中是4。
看代碼
int main()
{
Person p;
Student s;
/*func(p);
func(s);*/
cout << sizeof(p) << endl;
cout << sizeof(s) << endl;
system("pause");
return 0;
}
把上面的主函數改改看看運行結果
總結:
1、子類(派生類)重寫父類(基類)的虛函數實現多態,要求函數名、參數列表、返回值完全相。(協變除外)
協變:
虛函數重寫的特殊情況,返回值可以不同,但必須是父子類的指針或引用
2、子類重寫的虛函數可以不寫virtual (C++語法比較不嚴謹的地方)
3、只有類的成員函數可以定義爲虛函數。
4、靜態成員函數不能定義爲虛函數。因爲靜態沒有this指針,沒有對象可以調用,但虛函數必須要有對象才能找到虛表
5、operator=(沒有意義) 參數不同
6、析構函數(最好是虛函數)也是重寫和隱藏的特例
看代碼
#include<iostream>
#include<Windows.h>
using namespace std;
class Person
{
public:
virtual void BuyTicket()
{
cout << "成人全價" << endl;
}
~Person()
{
cout << "~Person()" << endl;
}
};
class Student : public Person
{
virtual void BuyTicket()
{
cout << "學生半價" << endl;
}
~Student()
{
cout << "~Stduent()" << endl;
}
};
int main()
{
Person *p=new Student;
delete p;
system("pause");
return 0;
}
看看結果
若不重寫虛函數則只調用~person()
若重寫了虛函數那麼會先調用~student,最後調用person的析構函數
#include<iostream>
#include<Windows.h>
using namespace std;
class Person
{
public:
virtual void BuyTicket()
{
cout << "成人全價" << endl;
}
virtual ~Person()
{
cout << "~Person()" << endl;
}
};
class Student : public Person
{
virtual void BuyTicket()
{
cout << "學生半價" << endl;
}
virtual ~Student()
{
cout << "~Stduent()" << endl;
}
};
int main()
{
Person *p=new Student;
delete p;
system("pause");
return 0;
}
析構函數之所以可以實現隱藏,是因爲析構函數名在編譯時被替換成了destructor函數名,這兩個父子類相同的虛函數就是同一份貨。