1.常見問題
1.什麼是多態?
多態是不同對象調用同一個函數,產生不同的結果(狀態),
例如買票函數,大人->全價,小孩->半價。
2.多態實現的原理?
基類的引用或者指針調用虛函數。
被調用的函數必須是虛函數,派生類的虛函數必須進行重寫(覆蓋)。
3.構造函數可以是虛函數嗎?
構造函數不可以是虛函數,因爲對象還沒有產生,
沒有虛函數表指針,對象初始化才產生虛函數表指針。
4.析構函數可以是虛函數嗎?
析構函數可以是虛函數(非必須),在實現多態的時候必須是虛函數,
編譯器將析構函數優化成同名的(destructor),如果不寫成虛函數,
就會造成內存泄漏,調用是根據對象類型調用,調用的是父類指針。
5.靜態成員函數可以是虛函數嗎?
靜態成員函數沒有this指針,是所有對象共有,
而多態就是爲了體現對象不同狀態不同,所以靜態成員函數不能是虛函數。
6.虛函數在什麼時候生成,存在哪個位置。
虛函數在編譯階段生成,通過反彙編查看到,存在代碼段的常量區。
同類對象共用一個虛函數表。
7.什麼是抽象類?
抽象類只聲明瞭純虛函數的類,不能實例化出對象,作爲接口繼承。
8.對象訪問普通函數和虛函數哪個快?
如果直接通過對象訪問,普通函數和虛函數一樣都是直接通過地址訪問。
如果通過基類對象的引用或者指針訪問的話(多態),就會先去虛函數指針表中找對應的虛函數。普通函數快。
2.代碼展示
#define _CRT_SECURE_NO_WARNINGS 1
#include<iostream>
#include<string>
using namespace std;
/*class A {};
class B :public A {};*/
class person
{
public:
person()
{
cout << "person()" << endl;
}
~person()
{
cout << "~person()" << endl;
}
virtual void buyticket()//final虛函數不能被子類重寫
{
cout << "全價" << endl;
cout << _name << endl;
}
/* virtual person& buyticket()
{
cout << "全價" << endl;
cout << _name << endl;
return*this;
}*/
/*virtual A* buyticket()
{
cout << "全價" << endl;
cout << _name << endl;
return new A;
}*/
string _name;
};
class stu: public person
{
public:
stu()
{
cout << "stu()" << endl;
}
~stu()
{
cout << "~stu()" << endl;
}
void buyticket()//override檢查是否重寫基類的虛函數
{
cout << "半價" << endl;
cout << _name << endl;
cout << _num << endl;
cout << person::_name << endl;
}
/*virtual void buyticket(int)//虛函數重寫,返回值,函數名,參數都相同時,成爲子類重寫了基類的虛函數
{
cout << "半價" << endl;
cout << _name << endl;
cout << _num << endl;
}*/
/*virtual stu& buyticket()//虛函數重寫,函數名,參數都相同時,成爲子類重寫了基類的虛函數(協變引用都必須是引用,指針都指針)
{
cout << "半價" << endl;
cout << _name << endl;
cout << _num << endl;
return *this;
}*/
/*virtual B* buyticket()//只要返回值滿足父類對父類,子類對子類的引用或者指針,都可以是協變。其他類繼承也可以。
{
cout << "半價" << endl;
cout << _name << endl;
cout << _num << endl;
return new B;
}*/
int _num;
string _name;
};
void test1()
{
//同名隱藏 - 》重定義
person p;
p._name = "xff";
stu s;
s._name = "cf";
s._num = 1;
s.person::_name = "name";
//p.buyticket();
//s.buyticket();
//person*pa = new stu;
//cout << pa->_name << endl;
//delete pa;
//父類調用子類對象出現切片
//person* pp = &s;
//person& ps = s;
//ps.buyticket();
//pp->buyticket();//調用指針對象(person)類型的,數據是指向(stu)類型的。
//stu*pps = &p;//子類調用不合法
//stu* ps = (stu*)&p; //強轉
//ps->buyticket(); //強轉成stu類型,調用的函數是stu對象指向的,數據是指向(person)類型的,有越界訪問。
}
void func(person p)
{
p.buyticket();
}
void func1(person& p)//父類引用
{
p.buyticket();
}
void func2(person* p)//父類指針
{
p->buyticket();
}
void test2()
{
person p;
p._name = "xff";
stu s;
s._name = "cf";
s._num = 1;
//func(p);
//func(s);
func1(p);
func1(s);
//func2(&p);
//func2(&s);
}
void test3()
{
//person p;
//stu s;
person *pp = new stu;//不是多態會內存泄漏
//person *pp = new person;
delete pp;
}
//抽象類(接口類) 純虛函數的類
class A
{
public :
virtual void fun1()
{
cout << "A::fun1()" << endl;
}
virtual void fun2()
{
cout << "A::fun2()" << endl;
}
/* void fun3()
{
cout << "A()" << endl;
}*/
//virtual void fun1() = 0;
int _num1;
};
class B
{
public:
virtual void fun1()
{
cout << "B::fun1()" << endl;
}
/*virtual void fun()//不重寫純虛函數子類就不能實例化出對象
{
}*/
void fun3()
{
cout << "B()" << endl;
}
virtual void fun2()
{
cout << "B::fun2()" << endl;
}
int _age;
};
class C :public B,public A
{
public:
virtual void fun1()
{
cout << "C::fun1()" << endl;
}
virtual void fun3()
{
cout << "C::fun3()" << endl;
}
};
typedef void(*VFPTR)();
void PrintVTable(VFPTR* vTable)
{
// 依次取虛表中的虛函數指針打印並調用。調用就可以看出存的是哪個函數
cout << " 虛表地址>" << vTable << endl;
for (int i = 0; vTable[i] != nullptr; ++i)
{
printf(" 第%d個虛函數地址 :0X%p,->", i, vTable[i]);
VFPTR f = vTable[i];
f();
}
cout << endl;
}
void test4()
{
A a;//抽象類不能實例化出對象
B b;
C c1;
C c2;
//A*pa = new B;
//pa->fun1();
//printf("%p\n", *((int*)&a));
//printf("%p\n", *((int*)&b));
VFPTR* va = (VFPTR*)(*(int*)&a);
PrintVTable(va);
VFPTR* vb = (VFPTR*)(*(int*)&b);
PrintVTable(vb);
VFPTR* vc1 = (VFPTR*)(*(int*)&c1);
PrintVTable(vc1);
VFPTR* vc2 = (VFPTR*)(*(int*)&c2);
PrintVTable(vc2);
//a.fun3();
//b.fun3();
//cout << sizeof(a) << endl;//虛函數表指針32位-4字節大小
//cout << sizeof(b) << endl;
}
int main()
{
//test1();//隱藏重定義名字相同
//test2();//多態重重寫
//test3();
//test4();
system("pause");
return 0;
}
3.結果展示
1.test1()
通過對象調用,函數與調用對象有關。
同名隱藏,隱藏父類。
基類指針調用虛函數,子類重寫虛函數。
基類對象的引用,調用子類的重寫的虛函數。
子類調用父類的不合法
強轉成子類對象,可以實現多態,但是存在越界訪問。
父類對象裏面沒有子類的_num
父類的引用或者指針直接訪問的話,會出現切片,也就是隻能訪問到子類對象繼承父類的部分。
2.test2()
父類的指針或者引用,虛函數重寫實現多態。
第一個沒有傳這兩個類型,所以沒有構成多態,函數是通過對象調用,
所以我們可以看出來,傳遞的時候切片了,子類顯示的_name是繼承父類對象裏面的_name.
有很好的展示了隱藏(重定義)
。
3.test3()
析構函數沒寫virtual,只析構了基類對象的。
析構寫成虛函數,構成多態。
基類的不影響。
4.test4()
我們可以看出,多繼承中虛函數的繼承以及重寫,C類中的A B類的對象重寫了fun1(),fun2()直接繼承了之前的,fun3()是沒有重寫的虛函數,它放在第一繼承基類的虛函數最後面,我們改一下第一繼承類,發現未重寫的虛函數,會放到它後面。
相同類對象共用一個虛函數表。
4.總結
只有滿足基類的引用或者指針調用派生類虛函數重寫,才能構成多態。
對象直接調用的話,和對象類型有關,基類調子類會切片。
同一類不同對象共用一個虛函數表,不同類對象不共用,多繼承的時候,一個對象不止只有一個虛函數表。
多繼承中,派生類未重寫的虛函數放在第一繼承類的後面。
協變是虛函數重寫中,返回類型可以不同,但必須是基類與派生類的關係,不能調換順序。