15.2.4 virtual與其他成員函數
C++中的函數調用默認不使用動態綁定。要出動動態綁定,必須滿足兩個條件:第一,只有指定為虛函數的成員函數才能進行動態綁定,成員函數默認為非虛函數,非虛函數不進行動態綁定;第二,必須通過基類類型的引用或指針進行函數調用。
1. 從派生類到基類的轉換
因為每個派生類對象都包括基類部分,所以可將基類類型的引用綁定到派生類對象的基類部分,也可以用指向基類的指針指向派生類對象。
Item_base *item1=new Bulk_item();
Item_base &item2=*item1;
因為可以使用基類類型的指針或引用來引用派生類型對象,所以,使用基類類型的引用或指針時,不知道指針或引用所綁定的對象的類型:基類類型的引用或指針可以引用基類類型的對象,也可以引用派生類型對象。無論實際對象具有哪種類型,編譯器都將它當作基類類型對象。將派生類對象當作基類對象是安全的,因為每個派生類對象都擁有基類子對象。而且,派生類繼承基類的操作,即,任何可以在基類對象上執行的操作也可以通過派生類對象使用。
基類類型引用和指針的關鍵點在於靜態類型(static type,在編譯時可知的引用類型或指針類型)和動態類型(dynamic type,指針或引用所綁定的對象的類型,這是在運行時可知的)可能不同。
2. 可以運行時確定virtual函數的調用
Item_base *item1=new Bulk_item();
cout<<item1->net_price(2)<<endl; //Bulk_item::net_price() called
將基類類型的引用或指針綁定到派生類對象對基對象沒有影響,對象本身不會改變,仍未派生類對象。對象的實際類型可能不同於該對象的引用或指針的靜態類型,這是C++中動態綁定的關鍵。
通過引用或指針調用虛函數時,編譯器將生成代碼,在運行時確定調用哪個函數,被調用的是與動態類型相對應的函數。
引用和指針的靜態類型與動態類型可以不同,這是C++用以支持多態性的基石。
通過基類引用或指針調用基類中定義的函數時,我們並不知道執行函數的對象的確切類型,執行函數的對象可能是基類類型的,也可能是派生類型的。
如果調用非虛函數,則無論實際對象是什麼類型,都執行基類類型所定義的函數。如果調用虛函數,則直到運行時才能確定調用哪個函數,運行時的虛函數是引用所綁定的或指針所指向的對象所屬類型定義的版本。
另一方面,對象是非多態的——對象類型已知且不變。對象的動態類型總是與靜態類型相同,這一點與引用或指針相反。運行的函數(虛函數或非虛函數)是由對象的類型定義的。
3. 編譯時確定非virtual調用 www.2cto.com
cout<<item1->book()<<endl; //Item_base::book() called
即使Bulk_item定義了自己的book函數版本,這個調用也會調用基類中的版本。
非虛函數總是在編譯時根據調用該函數的對象、引用或指針的類型而確定。
4. 覆蓋虛函數機制
在某些情況下,希望覆蓋虛函數機制並強調函數調用使用虛函數的特定版本,這時可以使用作用域操作符。
cout<<item1->Item_base::net_price(2)<<endl; //Item_base::net_price() called
只有成員函數中的代碼才應該使用作用域操作符覆蓋虛函數機制。
派生類虛函數調用基類版本時,必須顯式使用作用域操作符。如果派生類函數忽略了這樣做,則函數調用會在運行時確定並且將是一個自身調用,從而導致無窮遞歸。
5. 虛函數與默認實參
像其他任何函數一樣,虛函數也可以有默認實參。通常,如果有用在給定調用中的默認實參值,該值將在編譯時確定。如果一個調用省略了具有默認值的實參,則所有的值由調用該函數的類型定義,與對象的動態類型無關。通過基類的引用或指針調用虛函數時,默認實參為在基類虛函數聲明中指定的值,如果通過派生類的指針或引用調用虛函數,則默認實參是在派生類的版本中聲明的值。
在同一虛函數的基類版本和派生類版本中使用不同的默認實參幾乎一定會引起麻煩。如果通過基類的引用或指針調用虛函數,但實際執行的是派生類中定義的版本,這是就可能會出現問題。在這種情況下,為虛函數的基類版本定義的默認實參將傳遞給派生類定義的版本,而派生類版本是用不同的默認實參定義的。
摘自 xufei96的專欄