[英]Strange behavior with virtual functions
使用以下代碼,我希望輸出為Bf Bf DD.f,但是我得到的輸出為Bf Bf Bf當DD
從具有f
為虛數的D
派生時,這怎么可能。
class B
{
public:
void f() { cout << "B.f "; }
};
class D : public B
{
public:
virtual void f() { cout << "D.f "; }
};
class DD : public D{
public:
virtual void f() { cout << "DD.f "; }
};
B * b = new B();
B * d = new D();
B * dd = new DD();
b->f();
d->f();
dd->f();
函數從被聲明為virtual
的級別開始變為virtual
。 您首先在D
聲明f
virtual
,這意味着動態分配將僅從D
向上進行。 B
沒有,也不應該知道派生類。
考慮一下編譯器如何看待它:
您有一個指向B
的指針B
具有以下定義:
class B
{
public:
void f() { cout << "B.f "; }
};
由於f
不是virtual
,所以我將繼續靜態解決該調用-即B::f()
。
為了使動態分配從指向B
的指針開始工作,您需要在B
中將f()
虛擬化:
class B
{
public:
virtual void f() { cout << "B.f "; }
};
您需要將B::f()
設置為虛擬,而不將B::f()
為虛擬,它不會出現在B虛擬表(vtbl)中,因此調用B::f()
而不是調度調用派生類。
class B
{
public:
virtual void f() { cout << "B.f "; }
};
在B中將f()聲明為虛擬后,這便開始維護一個虛擬表,該表包含具有相同名稱的派生類的所有其他函數的函數指針。 這是一個查找表,用於以動態/后期綁定方式解析函數調用。
當您使用引用或指針來調用方法時,編譯器會在該方法的聲明中搜索指針或引用的類型(此處,它在B
搜索帶有簽名f()
的某個方法的聲明)。 當找到一個:
virtual
,則將其解決為對該類定義的方法的調用-這是靜態綁定。 virtual
,則調用的方法將是所引用或指向的對象中的合適對象之一-這是動態綁定。 下一個測試應該是:
DD * dd = new DD();
D * d = dd;
B * b = d;
b->f();
d->f();
dd->f();
一個單獨的對象new DD()
的使用/查看方式有所不同...每種類型都可以認為是您對對象的一種查看。 如果將其視為B
則f()
執行某些操作,但始終是相同的,但是如果將其視為D
或DD
,則f()
執行不同的操作...
如果您在街上遇到某人,向他致敬的標准方法是打個招呼,但對於同一個人,當他遇到他的一個朋友時,他要么打個招呼! 或喲! :
class Person {
public:
void salute() { cout << "Hello" << endl; }
};
class Friend : public Person {
public:
virtual void salute() { cout << "Hi!" << endl; }
};
class RoomMate : public Friend {
public:
virtual void salute() { cout << "Yo!" << endl; }
};
void asACustomer(Person &p) {
p.salute(); // static binding, we need the standard politeness
}
void asAFriend(Friend &f) {
p.salute(); // dynamic binding, we want an appropriate message...
}
RoomMate joe;
asCustomer(joe);
asFriend(joe);
使用靜態綁定,您可以在編譯時知道調用哪個方法。 使用動態綁定,您將無法做到,只知道合適的綁定就可以了。 這是子類型多態性的關鍵點。
通常,將靜態綁定和動態綁定混合用於方法時要小心。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.