柚子快報邀請碼778899分享:開發(fā)語言 【C++】多態(tài)
柚子快報邀請碼778899分享:開發(fā)語言 【C++】多態(tài)
多態(tài)的概念
了解多態(tài)之前,我們在現(xiàn)實生活一定經(jīng)歷過這倆個例子:
????????在購買火車票的時候,會根據(jù)你的類型來確定你的票價:
成人:全價學生:半價軍人:免費
? ? ? ? 而某外賣系統(tǒng)的紅包系統(tǒng),也會根據(jù)一定類型來確定紅包大?。?/p>
新人:大額紅包不經(jīng)常使用該外賣系統(tǒng)的人:中等額度經(jīng)常使用外面系統(tǒng)的人:小額紅包
從這些例子中,可以大致對多態(tài)有一定的理解:
多態(tài)的概念:通俗來講,就是多種形態(tài),具體點就是去完成某個行為,當不同的對象去完成時會產(chǎn)生出不同的狀態(tài)。
多態(tài)的定義以及實現(xiàn)
虛函數(shù)
class Person
{
public:
virtual void fare()
{
cout << "全價" << endl;
}
};
虛函數(shù):被virtual修飾的類成員函數(shù)被稱為虛函數(shù)。
虛函數(shù)的重寫
class Person
{
public:
virtual void fare()
{
cout << "全價" << endl;
}
};
class Student : public Person
{
public:
virtual void fare()
{
cout << "半價" << endl;
}
};
虛函數(shù)的重寫(覆蓋):派生類中有一個跟基類完全相同的虛函數(shù)(即派生類虛函數(shù)與基類虛函數(shù)的返回值相同,函數(shù)名字,參數(shù)列表完全相同),稱為子類的虛函數(shù)重寫了基類的虛函數(shù)。
class Person
{
public:
virtual void fare()
{
cout << "全價" << endl;
}
};
class Student : public Person
{
public:
void fare()
{
cout << "半價" << endl;
}
};
【注意】觀察上述代碼,在重寫基類虛函數(shù)的時候,派生類的虛函數(shù)在不加virtual關鍵字時,雖然也可也構成重寫(因為繼承后基類的虛函數(shù)被繼承下來,在派生類依舊保持虛函數(shù)屬性),但是這種寫法不是很規(guī)范,不建議這樣使用。
通過觀察代碼,前倆段代碼是通過對象類型來調用函數(shù),而后面?zhèn)z條代碼是通過看指向的對象,如果指向的對象是基類,則調用基類;如果指向的對象是派生類,則調用派生類。
虛函數(shù)重寫的倆個例外:
1.協(xié)變(基類與與派生類虛函數(shù)返回值類型不同)
派生類重寫基類虛函數(shù)時,與基類虛函數(shù)返回值類型不同。即基類虛函數(shù)返回基類對象的指針或者引用,派生類虛函數(shù)返回派生類對象的指針或者引用,稱為協(xié)變。
class A{};
class B : public A{};
class Person
{
public:
virtual A* fare()
{
cout << "全價" << endl;
}
};
class Student : public Person
{
public:
virtual B* fare()
{
cout << "半價" << endl;
}
};
2.析構函數(shù)的重寫(基類與派生類析構函數(shù)的名字不同)
如果基類的析構函數(shù)為虛函數(shù),此時派生類析構函數(shù)只要定義,無論是否加virtual關鍵字,都與基類的析構函數(shù)構成重寫,雖然基類與派生類析構函數(shù)名字不同。雖然函數(shù)名不相同,看起來違背了重寫的規(guī)則,但是這是不對的,這里可以理解為編譯器對析構函數(shù)的名稱做了特殊處理,編譯后析構函數(shù)的名稱被統(tǒng)一處理稱desructor。
觀察下面這段代碼:
class Person
{
public:
~Person()
{
cout << "~Person" << endl;
}
};
class Student : public Person
{
public:
~Student()
{
cout << "~Student" << endl;
}
};
int main()
{
Person* p = new Person;
delete p;
p = new Student;
delete p;
return 0;
}
這段代碼使用了為經(jīng)過處理的析構函數(shù),使用Person這個指針進行析構第一次p的時候,會調用Person這個析構函數(shù),而使用p析構第二次p的時候,還是只會調用Person這個析構函數(shù)。
?
class Person
{
public:
virtual ~Person()
{
cout << "~Person" << endl;
}
};
class Student : public Person
{
public:
virtual ~Student()
{
cout << "~Student" << endl;
}
};
int main()
{
Person* p = new Person;
delete p;
p = new Student;
delete p;
return 0;
}
????????將析構函數(shù)前面加上virtual,進行虛函數(shù)的重寫,可以將類析構函數(shù)都被處理為destructor這個統(tǒng)一的名字。
????????這里也是由于期望p->destructor()是一個多態(tài)調用,而不是普通調用。通過多態(tài)調用,可以將第二次的p在student析構一次,在Student析構一次,在Person析構一次。
? ? ? ? 這也是為什么將析構函數(shù)統(tǒng)一處理稱destructor這個名字的原因,為了保證函數(shù)名的統(tǒng)一。
class Person
{
public:
virtual ~Person()
{
cout << "~Person" << endl;
}
};
class Student : public Person
{
public:
virtual ~Student()
{
cout << "~Student" << endl;
}
};
int main()
{
Person* p1 = new Person;
delete p1;
Person* p2 = new Student;
delete p2;
return 0;
}
【注意】只有派生類student的析構函數(shù)重寫了person的析構函數(shù),當p1析構時會調用person的析構函數(shù),而p2析構的時候會調用person與student的析構函數(shù)。
【注意】派生類的析構可以不寫。
多態(tài)的構成條件
多態(tài)時在不同繼承關系的類對象,去調用同一個函數(shù),產(chǎn)生了不同的行為。
在繼承中構成多態(tài)的倆個條件:
1.必須通過基類的指針或者引用調用函數(shù)。
2.被調用的函數(shù)必須是虛函數(shù),且派生類必須對基類的虛函數(shù)進行重寫。
重寫虛函數(shù)的條件以及一些例外:
條件1:三同“函數(shù)名相同、參數(shù)相同、返回值相同”
條件2:必須是虛函數(shù)(virtual)
例外1:派生類的重寫虛函數(shù)可以不寫virtual,但是建議都加上。
例外2:協(xié)變的返回值可以不同,但是要求返回值必須是父子類關系的引用與指針。
例外3:析構函數(shù)可以名字不同。
【注意】:對于多態(tài)而言,不同的對象傳遞過去,會調用不同的函數(shù),多態(tài)調用看的是指向的對象,而普通類型調用看的是當前類型。
C++11特性:override與fianl
從前面的學習中了解到,C++對函數(shù)重寫的要求比較嚴格,但是可以由于各種原因導致函數(shù)名字母次序寫反而無法構成重載,而這種錯誤在編譯期間是不會報錯的,只有在程序運行時沒有達到預期的結果需要進行調試會得不償失。
C++11中提供了override和final倆個關鍵字,以便幫助用戶檢測是否重寫。
fianl:檢測虛函數(shù),表示該虛函數(shù)不能再被重寫。
class A
{
public:
virtual void Fun1() final
{
cout << "A" << endl;
}
};
class B : public class A
{
public:
virtual void Fun1()
{
cout << "B" << endl;
}
};
override:檢查派生類虛函數(shù)是否重寫了基類某個虛函數(shù),如果沒有重寫編譯錯誤。
class A
{
public:
virtual void Fun1()
{
cout << "A" << endl;
}
};
class B : public A
{
public:
virtual void Fun2() override
{
cout << "B" << endl;
}
};
如何設計一個不想被繼承的類?
倆種方法:
1.C++98的方法:將基類構造函數(shù)私有化(private)
class A
{
public:
private:
A()
{}
};
class B : public A
{
public:
B()
{}
};
此時會之間報錯:無法訪問 private 成員(在“A”類中聲明),私有構造函數(shù)在子類不可見,而派生類的構造函數(shù)需要調用基類的構造函數(shù)。
? ? ? ? 伴隨著報錯還會出現(xiàn)一系列的問題:該類如何進行初始化操作?可以使用一個函數(shù)來調用構造函數(shù)。
class A
{
public:
A Creatobj()
{
return A();
}
private:
A()
{}
};
?此時需要考慮的是,如果沒有初始化對象,那么該如何調用在函數(shù)來初始化對象呢?這是一個先有蛋還是先有雞的關系。
可以使用靜態(tài)函數(shù)來初始化創(chuàng)建對象:
class A
{
public:
static A Creatobj()
{
return A();
}
private:
A()
{}
};
int main()
{
A::Creatobj();
return 0;
}
可以通過這樣的方式來進行對對象的初始化。
2.C++11的方法:基類后面加一個fianl可以變成最終類。
在C++11中規(guī)定,如果一個類后面加關鍵字fianl會導致這個類變成最終類,而使其無法被繼承。
class A final
{
public:
};
class B : public A
{
public:
};
重載、覆蓋(重寫)、隱藏(重定義)的對比
多態(tài)的原理
虛函數(shù)表
觀察下面這段代碼:
class A
{
public:
virtual void Fun1()
{
cout << "A::Fun1()" << endl;
}
protected:
int _a;
};
int main()
{
A a;
cout << sizeof(a) << endl;
return 0;
}
在x86環(huán)境下,sizeof(a)的值為8.
????????這個答案可能有點不合常理,這是因為除了_a成員是int類型占了4個字節(jié),還多了一個_vfptr的指針占4個字節(jié),該指針被放在對象的前面(這是在vs環(huán)境下,有些平臺可能會放在對象的后面,這個跟平臺有關系),對象中的這個指針被稱為虛函數(shù)表指針(v代表virtual,f代表function)。
? ? ? ? 一個含有虛函數(shù)的類中都至少都會有一個虛函數(shù)表指針,這是因為虛函數(shù)的地址要被放在虛函數(shù)表中,虛函數(shù)表也簡稱虛表。
class A
{
public:
virtual void Fun1()
{
cout << "A::Fun1()" << endl;
}
virtual void Fun2()
{
cout << "A::Fun2()" << endl;
}
virtual void Fun3()
{
cout << "A::Fun3()" << endl;
}
protected:
int _a;
};
如果我們存放多個虛函數(shù),在這個虛函數(shù)指針所指向的表中就會出現(xiàn)三個地址。
class A
{
public:
virtual void Fun1()
{
cout << "A::Fun1()" << endl;
}
virtual void Fun2()
{
cout << "A::Fun2()" << endl;
}
void Fun3()
{
cout << "A::Fun3()" << endl;
}
protected:
int _a;
};
class B : public A
{
public:
virtual void Fun1()
{
cout << "B::Fun2()" << endl;
}
void Fun4()
{
cout << "B::Fun4()" << endl;
}
protected:
int _b;
};
int main()
{
A a;
B b;
return 0;
}
下面將通過測試來講解這段代碼:
1.派生類對象b中也有一個虛表函數(shù),b對象由倆部分組成,一部風是基類繼承下來的成員,虛表指針也就是存在這部分的,另一部分是自己的成員。
2.基類a對象和派生類b對象虛表是不一樣的,這里可以發(fā)現(xiàn)Fun1函數(shù)完成了重寫,所以b的虛表中存的是重寫的B::Fun1,所以虛函數(shù)的重寫也叫做覆蓋,覆蓋就是指虛表中虛函數(shù)的覆蓋。重寫是語法的叫法,覆蓋是原理層的叫法。
3.另外Fun2繼承下來后是虛函數(shù),所以被放進了虛表,F(xiàn)un3也被繼承了下來,但是由于沒有被關鍵字virtual修飾,不是虛函數(shù),所以不會放進虛表。
4.虛函數(shù)表本質是一存虛函數(shù)指針的指針數(shù)組,一般情況下這個數(shù)組最后面都會放一個nullptr。
class A
{
public:
virtual void func(int val = 1)
{
std::cout << "A->" << val << std::endl;
}
virtual void test()
{
func();
}
};
class B : public A
{
public:
void func(int val = 0)
{
std::cout << "B->" << val << std::endl;
}
};
int main(int argc, char* argv[])
{
B* p = new B;
p->test();
return 0;
}
測試這段代碼:其輸出結果是B->1.
? ? ? ? 這是因為虛函數(shù)的重寫的是虛函數(shù)的實現(xiàn),聲明式不會被改變的,所以在派生類可以不寫virtual,只需要存在三同即好,編譯器會在檢查之前,將基類的聲明域派生類的函數(shù)實現(xiàn)結合。
5.總結派生類的虛表生成:
先將基類中的虛表內(nèi)容拷貝一份到派生類虛表中如果派生類重寫了基類中的某個虛函數(shù),用派生類自己的虛函數(shù)覆蓋虛表中基類的虛函數(shù)派生類自己新增的虛函數(shù)按其在派生類中的聲明次序增加到派生類虛表的后面。
6.虛函數(shù)的重寫是接口繼承。
7.虛表是存在常量區(qū)的。
class A
{
public:
virtual void Fun1()
{
cout << "A::Fun1()" << endl;
}
protected:
int _a;
};
class B : public A
{
public:
virtual void Fun1()
{
cout << "B::Fun2()" << endl;
}
protected:
int _b;
};
int main()
{
int a = 0;
printf("棧區(qū):%p\n", &a);
static int b = 0;
printf("靜態(tài)區(qū):%p\n", &b);
int* p = new int;
printf("堆區(qū):%p\n", p);
const char* str = "hello";
printf("常量區(qū):%p\n", str);
A aa;
printf("虛表aa:%p\n", *((int*)&aa));
B bb;
printf("虛表bb:%p\n", *((int*)&bb));
return 0;
}
通過這段代碼的測試:
基本可以鎖定虛表存儲在常量區(qū)。
8.虛表里存的是虛函數(shù)指針,不是虛函數(shù),虛函數(shù)和普通函數(shù)一樣,都是存在代碼段里的。對象中存的也不是虛表,而是虛表指針。
多態(tài)的原理
之前的學習中我們知道,多態(tài)的條件有倆個:
1.必須通過基類的指針或者引用調用函數(shù)。
2.被調用的函數(shù)必須是虛函數(shù),且派生類必須對基類的虛函數(shù)進行重寫。
class A
{
public:
virtual void Fun1()
{
cout << "A::Fun1()" << endl;
}
protected:
int _a;
};
class B : public A
{
public:
virtual void Fun1()
{
cout << "B::Fun2()" << endl;
}
protected:
int _b;
};
int main()
{
A a;
A& pa = a;
pa.Fun1();
B b;
A& pb = b;
pb.Fun1();
return 0;
}
通過測試,我們可以知道不同對象去進行調用函數(shù)Fun1的時候會展現(xiàn)不同的形態(tài)。
這里有倆個問題:
1.為什么需要使用基類的指針才可以調用函數(shù),而不能是派生類指針或者引用。
? ? ? ? 是因為派生類指針或引用只能調用派生類的指針和引用,基類的指針或者引用不僅可以調用基類的指針或引用,也可以調用派生類的指針或引用。
2.為什么不能是父類的對象?
? ? ? ? 對象的切片與指針或者引用的切片不同,當派生類賦值給基類對象進行切片,對象會進行拷貝,但是不會拷貝虛表,假設如果拷貝了虛表,然后調用了派生類的成員函數(shù),但是當使用基類指針去指向該基類對象的時候,就也只能調用派生類的成員函數(shù),而不是基類的成員函數(shù)。
class A
{
public:
virtual void Fun1()
{
cout << "A::Fun1()" << endl;
}
protected:
int _a;
};
class B : public A
{
public:
virtual void Fun1()
{
cout << "B::Fun1()" << endl;
}
protected:
int _b;
};
int main()
{
B b;
A& pb = b;
pb.Fun1();
b.Fun1();
return 0;
}
我們通過反匯編的角度,分析使用基類引用與對象調用函數(shù)的不同:
這是基類引用時的匯編代碼。
pb中存的時基類的指針,將pb移動到eax中。
[eax]就是取eax值指向的內(nèi)容,這里相當于把b對象頭4個字節(jié)(虛表指針)移動到了edx
[edx]就是取edx值指向的內(nèi)容,這里相當于把虛表中的頭4個字節(jié)存的虛函數(shù)指針移動到eax
call eax中存虛函數(shù)的指針。這里可以看出滿足多態(tài)的調用,不是在編譯時確定的,是運行起來以后對象里面取到的。
這里不滿足多態(tài)的條件,所以這里是普通函數(shù)的調用轉換成地址時,是在編譯時已經(jīng)從符號表確認了函數(shù)的地址,直接call地址。
? ? ? ? 通過上面的了解,看出滿足多態(tài)以后的函數(shù)調用,不是在編譯時確定的,是運行起來以后到對象里面取棧的,不滿足多態(tài)的函數(shù)調用時編譯時確認好的。
動態(tài)綁定與靜態(tài)綁定
在上述例子中總結:
1.靜態(tài)綁定:靜態(tài)綁定又稱為前期綁定(早綁定),在程序編譯期間確定了程序的行為,也稱為靜態(tài)多態(tài),比如函數(shù)重載。
2.動態(tài)綁定:動態(tài)綁定又稱為后期綁定(晚綁定),是在程序運行期間,根據(jù)具體拿到的類型確定程序的具體行為,調用具體的函數(shù),也稱為動態(tài)多態(tài)。
單繼承和多繼承關系的虛函數(shù)表
單繼承中的虛函數(shù)表
class A
{
public:
virtual void Fun1()
{
cout << "A::Fun1()" << endl;
}
virtual void Fun2()
{
cout << "A::Fun2()" << endl;
}
protected:
int _a;
};
class B : public A
{
public:
virtual void Fun1()
{
cout << "B::Fun1()" << endl;
}
virtual void Fun3()
{
cout << "B::Fun3()" << endl;
}
virtual void Fun4()
{
cout << "B::Fun4()" << endl;
}
protected:
int _b;
};
使用這段代碼進行測試的時候,在編譯的監(jiān)視窗口看不見fun3和fun4。
這里是編譯器的監(jiān)視窗口故意隱藏了這倆個函數(shù),也可以認為這是一個bug。
// 打印函數(shù)指針數(shù)組
// void PrintVFT(FUNC_PTR table[])
void PrintVFT(FUNC_PTR* table)
{
for (size_t i = 0; table[i] != nullptr; i++)
{
printf("[%d]:%p->", i, table[i]);
FUNC_PTR f = table[i];
f();
}
printf("\n");
}
int main()
{
A a;
B b;
int vft1 = *((int*)&a);
PrintVFT((FUNC_PTR*)vft1);
int vft2 = *((int*)&b);
PrintVFT((FUNC_PTR*)vft2);
return 0;
}
這里提供一種思路:
取出a,b對象的頭4個字節(jié),就是虛表的指針,前面講解了虛函數(shù)的本質是一個存虛函數(shù)指針的指針數(shù)組,這個數(shù)組最后面放了一個nullptr。
1.先取a的地址,強轉成一個int*的指針。
2.再解引用取值,就取到了a對象的頭4個字節(jié)的指針,這個值就是指向虛表的指針。
3.再強轉成FUNC_PTR*,因為虛表就是一個存FUNC_PTR類型(虛函數(shù)指針類型)的數(shù)組。
4.虛表指針傳遞給PrintVFT進行打印虛表。
5.需要說明的是這個打印虛表的代碼經(jīng)常會崩潰,因為編譯器有時對虛表的處理不干凈,虛表最后沒有放nullptr的話,會導致越界,這是編譯器的問題。只需要清理解決方案即可。
多繼承中的虛函數(shù)表
class Base1 {
public:
virtual void func1() { cout << "Base1::func1" << endl; }
virtual void func2() { cout << "Base1::func2" << endl; }
private:
int b1;
};
class Base2 {
public:
virtual void func1() { cout << "Base2::func1" << endl; }
virtual void func2() { cout << "Base2::func2" << endl; }
private:
int b2;
};
class Derive : public Base1, public Base2 {
public:
virtual void func1() { cout << "Derive::func1" << endl; }
virtual void func3() { cout << "Derive::func3" << endl; }
private:
int d1;
};
typedef void(*VFPTR) ();
void PrintVTable(VFPTR vTable[])
{
cout << " 虛表地址>" << vTable << endl;
for (int i = 0; vTable[i] != nullptr; ++i)
{
printf(" 第%d個虛函數(shù)地址 :0X%x,->", i, vTable[i]);
VFPTR f = vTable[i];
f();
}
cout << endl;
}
int main()
{
Derive d;
VFPTR* vTableb1 = (VFPTR*)(*(int*)&d);
PrintVTable(vTableb1);
VFPTR* vTableb2 = (VFPTR*)(*(int*)((char*)&d + sizeof(Base1)));
PrintVTable(vTableb2);
return 0;
}
?多繼承派生類的未重寫的虛函數(shù)放在第一個繼承基類部分的虛函數(shù)表中。
菱形繼承與虛擬菱形繼承
菱形繼承
class A
{
public:
virtual void Func1()
{
cout << "A::Func1" << endl;
}
public:
int _a;
};
class B : public A
{
public:
virtual void Fun1()
{
cout << "B::Func1" << endl;
}
public:
int _b;
};
class C : public A
{
public:
virtual void Fun1()
{
cout << "B::Func1" << endl;
}
public:
int _c;
};
class D : public B, public C
{
public:
virtual void Fun1()
{
cout << "B::Func1" << endl;
}
public:
int _d;
};
int main()
{
D d;
d.B::_a = 1;
d.C::_a = 2;
d._b = 3;
d._c = 4;
d._d = 5;
return 0;
}
這里需要注意在d的所在地址第一塊區(qū)域是類B的所在區(qū)域,里面包含了虛表的地址,以及數(shù)據(jù)B::_a和_b。第二塊區(qū)域是類C的所在區(qū)域,里面包含了虛表的地址,以及數(shù)據(jù)C::_a和_c。
菱形虛擬繼承
class A
{
public:
virtual void Func1()
{
cout << "A::Func1" << endl;
}
public:
int _a;
};
class B : virtual public A
{
public:
virtual void Fun1()
{
cout << "B::Func1" << endl;
}
public:
int _b;
};
class C : virtual public A
{
public:
virtual void Fun1()
{
cout << "B::Func1" << endl;
}
public:
int _c;
};
class D : public B, public C
{
public:
virtual void Fun1()
{
cout << "B::Func1" << endl;
}
public:
int _d;
};
int main()
{
D d;
d._a = 1;
d._b = 3;
d._c = 4;
d._d = 5;
return 0;
}
????????實際中不建議設計出菱形繼承及菱形虛擬繼承,一方面太復雜容易出現(xiàn)問題,另一方面這樣的模型,訪問基類成員有一定的性能損耗。
? ? ? ? 所以菱形繼承、菱形虛擬繼承一般實際中很少使用。?
抽象類
概念
? ? ? ? 在虛函數(shù)的后面寫上=0,則這個函數(shù)變成純虛函數(shù)。包含純虛函數(shù)的類叫做抽象類(也叫接口類),抽象類不能實例化出對象。派生類繼承之后也不能實例化出對象,只能重寫純虛函數(shù),派生類才能實例化出對象。純虛函數(shù)規(guī)范了派生類必須重寫,另外純虛函數(shù)更體現(xiàn)處理接口繼承。
class Car
{
public:
inline virtual void Drive() = 0;
};
class Benz :public Car
{
public:
inline virtual void Drive()
{
cout << "Benz-舒適" << endl;
}
};
class BMW :public Car
{
public:
inline virtual void Drive()
{
cout << "BMW-操控" << endl;
}
};
class BYD :public Car
{
public:
inline virtual void Drive()
{
cout << "BYD-build year dream" << endl;
}
};
void Func(Car* p)
{
p->Drive();
}
int main()
{
Func(new Benz);
Func(new BMW);
Func(new BYD);
return 0;
}
這個類在現(xiàn)實世界沒有對應的實體,其用途可以是作為指針或者引用使用。
接口繼承和實現(xiàn)繼承
? ? ? ? 普通函數(shù)的繼承是一種實現(xiàn)繼承,派生類繼承了基類函數(shù),可以使用函數(shù),繼承的是函數(shù)的實現(xiàn)。
? ? ? ? 虛函數(shù)的繼承是一種接口繼承,派生類繼承的是基類函數(shù)的接口,目的是為了重寫,達成多態(tài),繼承的是接口。所以如果不實現(xiàn)多態(tài),就不需要把函數(shù)定義為虛函數(shù)。
面試相關題
1.
什么是多態(tài)?
答:多態(tài)分為靜態(tài)多態(tài):函數(shù)重載;和動態(tài)多態(tài):繼承中的虛函數(shù)重寫+基類指針引用。
2.
什么是重載、重寫
(
覆蓋
)
、重定義
(
隱藏
)
?
答:參考本節(jié)課件內(nèi)容
3.
多態(tài)的實現(xiàn)原理?
答:靜態(tài)多態(tài):函數(shù)名修飾規(guī)則;動態(tài)多態(tài):虛函數(shù)表
4. inline
函數(shù)可以是虛函數(shù)嗎?
答:可以,不過編譯器就忽略
inline
屬性,這個函數(shù)就不再是inline,因為虛函數(shù)要放到虛表中去。
5.
靜態(tài)成員可以是虛函數(shù)嗎?
答:不能,因為靜態(tài)成員函數(shù)沒有
this
指針,使用類型
::
成員函數(shù)的調用方式無法訪問虛函數(shù)表,所以靜態(tài)成員函數(shù)無法放進虛函數(shù)表。
6.
構造函數(shù)可以是虛函數(shù)嗎?
答:不能,因為對象中的虛函數(shù)表指針是在構造函數(shù)初始化列表 階段才初始化的。
7.
析構函數(shù)可以是虛函數(shù)嗎?什么場景下析構函數(shù)是虛函數(shù)?
答:可以,并且最好把基類的析 構函數(shù)定義成虛函數(shù)。參考本節(jié)課件內(nèi)容
8.
對象訪問普通函數(shù)快還是虛函數(shù)更快?
答:首先如果是普通對象,是一樣快的。如果是指針 對象或者是引用對象,則調用的普通函數(shù)快,因為構成多態(tài),運行時調用虛函數(shù)需要到虛函 數(shù)表中去查找。
9.
虛函數(shù)表是在什么階段生成的,存在哪的?
答:虛函數(shù)表是在編譯階段就生成的,一般情況 下存在代碼段(
常量區(qū)
)
的。
10. C++
菱形繼承的問題?虛繼承的原理?
答:參考繼承課件。注意這里不要把虛函數(shù)表和虛基表搞混了。
11.
什么是抽象類?抽象類的作用?
答:參考(3.
抽象類)。抽象類強制重寫了虛函數(shù),另外抽象類體現(xiàn)出了接口繼承關系。
柚子快報邀請碼778899分享:開發(fā)語言 【C++】多態(tài)
相關閱讀
本文內(nèi)容根據(jù)網(wǎng)絡資料整理,出于傳遞更多信息之目的,不代表金鑰匙跨境贊同其觀點和立場。
轉載請注明,如有侵權,聯(lián)系刪除。