四、c++中的多態規則。一) c++中函數動態綁定規則。看下面的例子:
類TextWindow繼承與類Window。我想程序運行的結果,大多數熟悉C++的人都會知道,win.oops()最終調用的是父類oops函數,而tWin->oops()調用的是子類TextWindow的函數。通過這個例子,我們先總結一下c++中的多態調用規則:第一、對于指針和引用類型,當消息調用的成員函數有可能被重寫時,最終被選擇調用的成員函數由消息接收者的動態類型確定(注意:在OO概念中,對某個對象成員函數進行調用,常常稱為給該對象發送消息,該對象就是消息的接收者)。 如上例:tWin->oops(),由于tWin的動態類型是子類TextWindow,而不是Windows,所以tWin->oops()調用的是子類TextWindow的函數。第二、對于其它的變量,對虛擬函數調用綁定完全由該變量的靜態類型確定(即該變量的聲明),而不是該變量的真實類型確定。 如上例:win.oops(),由于win的聲明類型為Window類,所以其結果調用的是父類oops函數。二) 探討。 接下來,我們要看的問題是,在c++中,為什么對于多態規則(或者說是動態函數綁定規則),做出了兩中不同的劃分,即:只有指針與引用類型,才進行函數的后期動態綁定,也就是多態。這或許也是許多c++初學者非常迷惑的地方。這種規則的不一致性,的確給c++的語法造成一定的復雜性。而這在Java,或者C#中是沒有的,后面我們會涉及到。 我們先來看例子。
在這里,如果我們假設,c++的函數動態綁定規則是一致的,看看會發生什么問題??? 現在win被聲明為Window類型,然而其真實的類型為TextWindow(因為win=*tWinPtr),由于我們的假設,win現在是允許進行動態函數綁定的,所以當執行win.oops()時,實際上是調用子類TextWindow的成員函數。 現在,我們有必要來審視一下win變量的內存布局。由于win變量是在棧上聲明的變量,其內存也是從棧進行分配(這是c++從c語言那里繼承過來的優良特質,從棧上分配內存空間比動態分配內存有更好的執行速度),c++標準規定:給win變量分配內存空間的大小,由其靜態的類型確定,即應該是Window類所使用的內存空間大小。在這種情況下,當執行win=*tWinPtr時,什么會發生?如下圖:在默認的拷貝構造函數情況下,信息會出現丟失,這就是著名的slicing off現象。結果,變量cursorLocation在win的內存空間里丟失了。然而,問題是:在我們假設下,我們要求win.oops()導致TextWindow的成員函數調用,而在這個函數中,訪問到的cursorLocation變量是不存在!win.oops()調用將導致內存違例! 到這里,我們可以總結一下:c++標準基于的其特定的內存分配規則,給出了以上,我們在前一節總結出的函數動態綁定規則。三) 深入。 當然,我們也可以說,c++也可以通過改變其內存分配規則,來給出一個一致性的函數動態綁定規則。比如:可以考慮在給win變量分配內存空間時,考慮其所有子類需求,然后分配最大數量的內存空間給win變量。這種做法可行性很差,對于編譯器而言,需要掃描整個程序(確定該類的所有子類),才能確定最大的內存空間是多少。在使用類庫或者框架的情況下,會引起整個類庫,框架的重新編譯,這是得不償失的!而這種做法,在oo的語言中,基本上是沒有的。這也是c++不得不基于其現有的內存管理機制,而對多態規則作出的不一致的解釋。 對于這個c++現有的內存管理機制,我們如果從另外角度去理解的話,是很合理的。當win=*tWinPtr發生時,我們可以類似地認為:好比一個float類型的數賦給了一個interger類型的變量,其結果當然是float的值被截斷了。 我們再來看其它語言,Java(或者C#)是怎么解決的。 最重要的一點是,在Java(C#)中只有引用的概念,所以在棧上聲明的類的變量,只需要分配一個指針大小的內存空間就行了,而不需要給該變量分配空間來保存變量內容本身,這其實就是我們現在看到的c++中指針和引用的情況。
Powered by: C++博客 Copyright © 愛上龍卷風