剖析Delphi中的多態(tài)
1什么是多態(tài)? 2
1.1概念 2
1.2多態(tài)的意義 2
1.3多態(tài)在delphi中如何實現(xiàn)的? 2
1.3.1 繼承(Inheritance) 2
1.3.2 虛方法、動態(tài)方法與抽象方法,VMT/DMT,靜態(tài)綁定與動態(tài)綁定 2
1.3.3 重載(Overload)與多態(tài) 2
1.4多態(tài)種類的探討 2
1.4.1 兩級多態(tài) 2
1.4.2 不安全的多態(tài) 2
2 VCL中多態(tài)的應用 2
2.1構(gòu)造與析構(gòu)方法 2
2.2 Tstrings 2
2.3其他(請soul來補充) 2
摘 要 多態(tài)是面向?qū)ο蟮撵`魂所在,理解多態(tài)是掌握面向?qū)ο蠹夹g(shù)的關鍵之一,本文著重分析多態(tài)的基本原理、多態(tài)的實質(zhì)以及在VCL中的應用。
關鍵字 多態(tài)、繼承、面向?qū)ο蟆CL、虛函數(shù)(virtual Method)、覆載(override)
問題
多態(tài)是面向?qū)ο蟮撵`魂所在,理解多態(tài)是掌握面向?qū)ο蠹夹g(shù)的關鍵之一。但是到底什么是多態(tài)?多態(tài)有何意義?怎么實現(xiàn)多態(tài)?多態(tài)的概念我能懂,但不知道如何使用以及什么時候該使用呢?請看本文細細道來。
專家分析
天地生物(物,即對象),千變?nèi)f化;而在計算機世界里,只有一行行機器指令,兩者似乎毫不相干,過去要用計算機語言來很好地描述現(xiàn)實世界是一件很困難的事情,雖然有人用C語言寫出面向?qū)ο蟮某绦騺恚腋覕喽ㄆ鋵懛ㄊ菢O其煩瑣的,直到面向?qū)ο?Oriented-Object 簡稱OO)的出現(xiàn),一切都隨之改觀,整個軟件業(yè)發(fā)生了翻天覆地的變化,從編程語言的變化開始,出現(xiàn)了一系列面向?qū)ο缶幊陶Z言(OOP)如SmallTalk、C++、java、Object Pascal、C#等;隨之各種面向?qū)ο箝_發(fā)工具也出現(xiàn)了如VC、Delphi、BCB、JBuilder等,并出現(xiàn)了許多優(yōu)秀的類庫如VCL、.net Framework和一些商業(yè)類庫等;再發(fā)展到了面向?qū)ο蟮脑O計(OOD),面向?qū)ο蟮姆治?OOA)以及面向?qū)ο蟮?a href="http://www.5lwq4hdr.cn/sql.asp">數(shù)據(jù)庫(OODB),面向?qū)ο蠹夹g(shù)幾乎貫穿了整個軟件領域,程序員的思考方式也發(fā)生了根本性的變化!在一些OO純化論者眼中,一切皆是對象!雖然我不完全同意這種看法。但我認為這種方式最符合人們的思維習慣,它使程序員能集中精力考慮業(yè)務邏輯,由計算機來完成面向?qū)ο蟮綑C器指令的轉(zhuǎn)換(由面向?qū)ο蟮木幾g器來完成),程序員的大腦從此解放出來了!這是一場革命!
面向?qū)ο蟮暮诵膬?nèi)容是對象,封裝,繼承,多態(tài)和消息機制,其中多態(tài)就是為了描述現(xiàn)實世界的多樣性的,也是面向?qū)ο笾凶顬橹匾奶匦裕梢赃@么說,不掌握多態(tài),就沒有真正地掌握面向?qū)ο蠹夹g(shù)。
1什么是多態(tài)?
1.1概念
多態(tài)的概念眾說紛紜,下面是幾種代表性的說法:
“This ability to manipulate more than one type with a pointer or a reference to a base classis spoken of as polymorphism” (《C++ PRimer》第838頁)。即用基類的指針/引用來操作多種類(基類和其派生類)的對象的能力稱之為多態(tài)。它是從語言實現(xiàn)的角度來考慮的。
“polymorphism provides another dimension of separation of interface from implementation, to decouple what from how”(《Think in Java》3rd edtion),即多態(tài)提供了另外一種分離接口和實現(xiàn)(即把“做什么”與“怎么做”分開)的一種尺度。它是從設計的角度考慮的。
“The ability to use the same expression to denote different Operations is refered to as Polymorphism”,(《Object-Oriented Methods Principles & Practice》3rd Edition,第16頁)。簡單的說,多態(tài)就是“相同的表達式,不同的操作”,也可以說成“相同的命令,不同的操作”。這是從面向?qū)ο蟮恼Z義的角度來看的。
三種說法分別從不同的角度來闡述了多態(tài)的實質(zhì)。其中第三種說法尤為確切,下面著重分析第三種說法。
先解釋這句話的含義:
相同的表達式—函數(shù)調(diào)用
不同的操作 —根據(jù)不同的對象就有不同的操作。
舉個例子來說明,比如在公司中有各種職責不同的員工(程序員,業(yè)務員,文管等),他們“上班”時,做不同的事情(也可以看作是一種業(yè)務邏輯),我們把他們各自的工作都抽象為"上班",關系如下:
員工
/ | / ——繼承關系
程序員 業(yè)務員 文管
每天上班時間一到,相當于發(fā)了一條這樣的命令:
“員工們.開始上班”(同一條表達式)
每個員工接到這條命令(同樣的命令)后,就“開始上班”,但是他們做的是各自的工作,程序員就開始“Coding”,業(yè)務員就開始“聯(lián)系業(yè)務”,文管員就開始“整理文檔”。即“相同的表達式(函數(shù)調(diào)用),(在運行期根據(jù)不同的對象來執(zhí)行)不同的操作”。
從語言實現(xiàn)多態(tài)的角度來說,多態(tài)是通過基類指針或引用指向派生類的對象,調(diào)用其虛方法實現(xiàn)的。下面是Object Pascal語言的實現(xiàn)
TEmployee=class //把員工抽象為一個抽象類
public
procedure startWorking;virtual;abstract;
{抽象函數(shù)(即C++中純虛函數(shù)),什么也不做,實際的意義是,先預留一個接口。在其派生類中覆載實現(xiàn)它。}
end;
TProgramer=class(TEmployee) //程序員
public
procedure startWorking;override;
end;
TBusinessMan=class(TEmployee) //業(yè)務員
public
procedure startWorking;override;
end;
TDocManager=class(TEmployee) //文管
public
procedure startWorking;override;
end;
procedure TProgramer.startWorking;
begin
showmessage('coding');
end;
{ TbusinessMan }
procedure TbusinessMan.startWorking;
begin
showmessage('Linking Business');
end;
{ TDocManager }
procedure TDocManager.startWorking;
begin
showmessage('Managing Document');
end;
procedure TForm1.Button1Click(Sender: TObject);
const
eNum=3;
var
Employee:array of TEmployee;
i:integer;
begin
setLength(Employee,eNum);
Employee[0]:=TProgramer.Create;
//把基類引用employee[0]指向剛創(chuàng)建的TProgramer對象
Employee[1]:=TBusinessMan.Create;
//把基類引用employee[1]指向剛創(chuàng)建的TBusinessMan對象
Employee[2]:=TDocManager.Create;
//把基類引用employee[2]指向剛創(chuàng)建的TDocManager對象
for i:=0 to Length(Employee)-1 do
Employee[i].startWorking; //在運行期根據(jù)實際的對象類型動態(tài)綁定相應的方法。
{從語言實現(xiàn)多態(tài)的角度來說,多態(tài)是通過基類指針或引用指向派生類的對象,調(diào)用其虛方法來實現(xiàn)的。Employee []為基類對象引用數(shù)組,其成員分別指向不同的派生類對象,當調(diào)用虛方法,就實現(xiàn)了多態(tài)}
end;
試一試
大家可以敲入上面一些代碼(或Demo程序),并編譯運行,單擊按扭就可以看多態(tài)性的神奇效果了。
1.2多態(tài)的意義
封裝和繼承的意義是它們實現(xiàn)了代碼重用,而多態(tài)的意義在于,它實現(xiàn)了接口重用(同一的表達式),接口重用帶來的好處是程序更易于擴展,代碼重用更加方便,更具有靈活性,也就能真實地反映現(xiàn)實世界。
比如為了更好地管理,把程序員分為C++程序員,Delphi程序員。…
員工
/ | / ——繼承關系
程序員 業(yè)務員 文管
/ / ——繼承關系
C++程序員 Delphi程序員
在程序員添加TCppProgramer,TDelphiProgramer兩個派生類后,調(diào)用的方式還是沒有變,還是“員工們.開始上班”,用Object Pascal來描述:
…
setLength(Employee,eNum+2);
Employee[ENum]:=TCppProgramer.create;
//創(chuàng)建一個TcppProgramer對象,并把基類引用employee[ENum]指向它
Employee[eNum+1]:=TDelphiProgramer.Create;
…
{員工們.開始上班}
for i:=0 to Length(Employee)-1 do
Employee[i].startWorking; //還是同一的調(diào)用方法(因為接口并沒變)。
…
1.3多態(tài)在delphi中如何實現(xiàn)的?
實現(xiàn)多態(tài)的必要條件是繼承,虛方法,動態(tài)綁定(或滯后聯(lián)編),在Delphi是怎么實現(xiàn)多態(tài)的呢?
1.3.1 繼承(Inheritance)
繼承指類和類之間的“AKO(A Kind Of,是一種)”關系,如程序員“是一種”員工表示一種繼承關系。在Delphi中,只支持單繼承(不考慮由接口實現(xiàn)的多重繼承),這樣雖然沒有多繼承的那種靈活性,但給我們帶來了極大的好處,由此我們可以在任意出現(xiàn)基類對象的地方都可以用派生類對象來代替(反之不然),這也就是所謂的“多態(tài)置換原則”,我們就可以把派生類的對象的地址賦給基類的指針/引用,為實現(xiàn)多態(tài)提供了先決條件。
提 示
在UML中:
AKO: A Kind Of 表示繼承(Inheritance)關系
APO: A Part Of 表示組合(Composition)關系
IsA: Is A表示對象和所屬類的關系
1.3.2 虛方法、動態(tài)方法與抽象方法,VMT/DMT,靜態(tài)綁定與動態(tài)綁定
對于所有的方法而言,在對象中是沒有任何蹤影的。其方法指針(入口地址)保存在類中,實際代碼則存儲在代碼段。對于靜態(tài)方法(非虛方法),在編譯時由編譯器直接根據(jù)對象的引用類型確定對象方法的入口地址,這就是所謂的靜態(tài)綁定;而對于虛方法由于它可能覆載了,在編譯時編譯器無法確定實際所屬的類,所以只有在運行期通過VMT表入口地址(即對象的首四個字節(jié))確定方法的入口地址,這就是所謂的動態(tài)綁定(或滯后聯(lián)編)。
虛方法
虛方法,表示一種可以被覆載(Override)的方法,若沒有聲明為抽象方法,就要求在基類中提供一個默認實現(xiàn)。類中除存儲了自己虛方法指針,還存儲所有基類的虛方法指針。
聲明方法:
procedure 方法名;virtual;
這樣,相當于告訴Delphi編譯器:
可以在派生類中進行覆載(Override)該方法,覆載(Override)后還是虛方法。
不要編譯期時確定方法的入口地址。而在運行期,通過動態(tài)綁定來確定方法的入口地址。
在基類中提供一個默認實現(xiàn),如果派生類中沒有覆載(Override)該方法,就使用基類中的默認實現(xiàn)。
動態(tài)方法
動態(tài)方法和虛方法本質(zhì)上是一樣的,與虛方法不同的是,動態(tài)方法在類中只存儲自身動態(tài)方法指針,因此虛擬方法比動態(tài)方法用的內(nèi)存要多,但它執(zhí)行得比較快。但這對用戶完全是透明的。
聲明方法:
procedure 過程名;dynamic;
抽象方法
一種特殊的虛方法,在基類它不需提供默認實現(xiàn),只是一個調(diào)用的接口用,相當于C++中的純虛函數(shù)。含有抽象方法的類,稱之為抽象類。
聲明方法:
procedure 過程名;virtual;abstract;
VMT/DMT
在Delphi中,虛擬方法表(Virtual Method Table,VMT),其實在物理上本沒有,是為了更好地闡述多態(tài),人為地在邏輯上給了它一個定義,實際上它只是類中的虛方法的地址的集合,這個集合中還包括其基類的的虛方法。在對象的首四個字節(jié)中存儲的“Vmt 入口地址”,實際上就是其所屬的類的地址(參考Demo程序)。有了實際的類,和方法名就可以找到虛方法地址了。
Obj(對象名) 實際的對象 所屬的類
Vmt 入口地址
數(shù)據(jù)成員
類虛方法表vmt入口地址
數(shù)據(jù)成員模板信息
靜態(tài)方法等
虛方法(VMT)
動態(tài)方法(DMT)
圖3 對象名、對象與類的關系
DMT和VMT類似,也是邏輯上的一個概念,不同的是,在類中只保存了自身動態(tài)方法指針,而沒有基類的動態(tài)方法的地址,這樣就節(jié)省了一些內(nèi)存,但速度不如虛方法,是一種犧牲時間換空間的策略,一般情況不推薦使用。
引用上面的例子來解釋一下:
Employee[i].startWorking;
Employee[i]是一個基類Temployee的對象引用,有上面的程序知道,它可能指向了一個Tprogramer對象,也可以可能指向一個TbusinessMan,還有可能是其他的對象,而且這些都是不確定的、動態(tài)的,所以在編譯時無法知道實際的對象,也就無法確定方法地址。而在運行期,當然知道對象的“廬山真面目”了,根據(jù)實際對象的首四個字節(jié)的內(nèi)容,也就是虛擬方法表VMT的入口地址,找到實際要調(diào)用的函數(shù),即實現(xiàn)了多態(tài)。
1.3.3 重載(Overload)與多態(tài)
很多網(wǎng)友認為函數(shù)重載也是一種多態(tài),其實不然。對于“不同的操作”,重載無法提供同一的調(diào)用的方式,雖然函數(shù)名相同,但其參數(shù)不同!實現(xiàn)多態(tài)的前提,是相同的表達式!如Employee[i].startWoring,而重載的調(diào)用,有不同的參數(shù)或參數(shù)類型。重載只是一種語言機制,C語言中也有重載,但C語言沒有多態(tài)性,C語言也不是面向?qū)ο缶幊陶Z言。除非重載函數(shù)同時還虛方法,不然編譯器就可以根據(jù)參數(shù)的類型就可以確定函數(shù)的入口地址了,還是靜態(tài)綁定!引用C++之父的話“不要犯傻,如果不是動態(tài)綁定,就不是多態(tài)”。
1.4多態(tài)種類的探討
1.4.1 兩級多態(tài)
對象級:用基類指針/引用指向其派生類對象,調(diào)用虛方法(或動態(tài)方法、抽象方法),這是用的最多一種。
類級:用類引用(指向類而不是對象的引用)指向派生類,調(diào)用虛類方法(或動態(tài)類方法、抽象類方法),常用在對象創(chuàng)建的多態(tài)性(因為構(gòu)造方法是一種“特殊的”類方法,請參考我的另一篇拙作《剖析Delphi中的構(gòu)造和析構(gòu)》,第2.1節(jié))。
提 示
類引用,是類本身的引用變量,而不是類,更不是對象引用。就和對象名表示對象引用一樣,類名就表示一個類引用,因為在Delphi中,類也是作為對象處理的。類引用類型就是類引用的類型,類引用類型的聲明方法:
類引用類型名稱=class of 類名
我們在VCL的源代碼中可以看到很多的類引用的聲明,如:
TClass=class of Tobject;
TComponentClass=class of Tcomponent;
TControlClass=class of Tcontrol;
注 意
在類方法中,方法中隱含的self,是一個類引用,而不是對象引用。
1.4.2 不安全的多態(tài)
用派生類指針/引用指向基類對象也可以實現(xiàn)多態(tài)!雖然這是一種錯誤的使用方法:
procedure TForm1.btnBadPolyClick(Sender: TObject);
var
cppProgramer:TCppProgramer;//定義一個cpp程序員引用,一個派生類的引用!
begin
{*****************************聲 明***********************************
用派生類指針/引用指向基類對象實現(xiàn)的多態(tài)。是一種病態(tài)的多態(tài)!
這種多態(tài)的使用方法,它就象一個實際很小的事物(基類對象)披上一個強大
的外表(派生類引用),因而帶來了許多潛在的不安全因素(如訪問異常),所
以幾乎沒有任何價值。"杜撰"這樣一個例子,旨在說明在Delphi中的多態(tài)的本質(zhì),多態(tài)的本質(zhì):使用一個合法的(通常是基類的)指針/引用來操作對象,在運行期根據(jù)實際的對象,來執(zhí)行不同的操作方法,或者更形象的說法:由對象自己來決定自己操作方式,編譯器只需下達做什么的命令(做什么what),而不要管怎么做(how),"怎么做"由為對象自己負責。這樣實現(xiàn)了接口和實現(xiàn)的分離,使接口重用變得可能。
***********************************************************************}
cppProgramer:=TCppProgramer(TProgramer.Create);
{為了實現(xiàn)這種病態(tài)的多態(tài),把對象引用強制轉(zhuǎn)換為TCppProgramer類型,
從而逃過編譯器的檢查}
cppProgramer.startWorking;
{調(diào)用的TProgramer.startWorking而不是TcppProgramer.startWorking
這就是用派生類指針/引用指向基類對象實現(xiàn)的多態(tài)。}
cppProgramer.Free;
cppProgramer:=TCppProgramer(TDocManager.Create);
cppProgramer.startWorking;
{調(diào)用的竟然是TDocManager.startWorking,
這就是用派生類指針/引用指向基類對象實現(xiàn)的多態(tài)。這種方法極不安全,
而且沒有什么必要}
cppProgramer.Free;
end;
試一試
為獲得這種多態(tài)的感性認識,建議動手試試,上面說到這種使用方法會有潛在的不安全性(如訪問異常),而上面的程序運行一點錯誤都沒有出現(xiàn),想想為什么?什么情況下會出現(xiàn)訪問異常,動手寫個訪問異常的例子,你將收獲更多。(參考Demo程序)
2 VCL中多態(tài)的應用
2.1構(gòu)造與析構(gòu)方法
構(gòu)造方法的多態(tài)
由于構(gòu)造方法可以看作“特殊的”類方法,在Tcomponent之后的所有的派生類的又被重新定義為虛類方法,因此要實現(xiàn)構(gòu)造方法的多態(tài)性,就得使用類引用,在Delphi中有個經(jīng)典的例子,就在每一個工程文件中都有一個類似下面的代碼:
application.CreateForm(TForm1, Form1);
其方法的定義:
procedure TApplication.CreateForm(InstanceClass: TComponentClass; var Reference);
var// InstanceClass為類引用。
Instance: TComponent;
begin
Instance := TComponent(InstanceClass.NewInstance);
{NewInstance方法的聲明:class function NewInstance: TObject; virtual; (system單元 432行)是一個類方法,同時也是虛方法,我們把它稱之為虛類方法。InstanceClass是一個類引用,實現(xiàn)了類一級的多態(tài),從而實現(xiàn)了創(chuàng)建組件的接口重用}
TComponent(Reference) := Instance;
try
Instance.Create(Self);//調(diào)用構(gòu)造方法,進行初始化
except
TComponent(Reference):= nil;//消除“野“指針!good
raise;
end;
{如果創(chuàng)建的是窗口且還沒有主窗體的話,就把剛創(chuàng)建的窗體設為主窗體}
if (FMainForm = nil) and (Instance is TForm) then
begin
TForm(Instance).HandleNeeded;
FMainForm := TForm(Instance);//設置主窗體
{ 實際上,在項目選項(project->options)中設置主窗體,實際上就把工程文件中相應的窗體語句提到所有創(chuàng)建窗體語句之前。}
end;
end;
2) 析構(gòu)方法的多態(tài)請參考《剖析Delphi中的構(gòu)造和析構(gòu)》,第3.3節(jié)
2.2 Tstrings
字符串數(shù)組處理在Delphi的控件中十分常見,通常是一些Items屬性,我們用起來也特別地方便(因為都是一樣的使用接口),這得益于Delphi中字符串數(shù)組的架構(gòu)的設計。這是一個成功的設計。
由于很多控件中要用到字符串數(shù)組,如ComboBox,TstringGrid等等,但每個控件中的字符串數(shù)組又不同,Delphi由此把字符串數(shù)組但抽象出來,從而出現(xiàn)了很多與之相關的類。其中基類Tstrings只是提供為各種調(diào)用提供接口,具體實現(xiàn)完全可由其派生類中實現(xiàn),因此,把Tstrings定義為抽象類。
下面就來看看基類TStrings類的常用方法的定義(參見Classes單元第442行):
TStrings = class(TPersistent)
protected
...
function Get(Index: Integer): string; virtual; abstract;
procedure Put(Index: Integer; const S: string); virtual;
function GetCount: Integer; virtual; abstract;
…
public
function Add(const S: string): Integer; virtual; //實際調(diào)用的是Insert
{添加一字符串S到字符串列表末尾}
procedure AddStrings(Strings: TStrings); virtual;
{添加字符串列表Strings到該字符串列表末尾}
procedure Insert(Index: Integer; const S: string); virtual; abstract;
{抽象方法,在第Index位置插入一新字符串S}
procedure Clear; virtual; abstract;
{清除所有的字符串}
procedure Delete(Index: Integer); virtual; abstract;
{刪除某個位置上的字符串}
function IndexOf(const S: string): Integer; virtual;
{獲取S在字符串列表中的位置}
function IndexOfName(const Name: string): Integer; virtual;
{Returns the position of the first string with the form Name=Value with the specified name part}
function IndexOfObject(AObject: TObject): Integer; virtual;
{獲取對象名為AObject:的對象在字符串列表中的位置}
procedure LoadFromFile(const FileName: string); virtual;
{Fills the list with the lines of text in a specified file}
procedure LoadFromStream(Stream: TStream); virtual;
{Fills the list with lines of text read from a stream}
procedure SaveToStream(Stream: TStream); virtual;
{Writes the value of the Text property to a stream object}
property Strings[Index: Integer]: string read Get write Put; default;
{References the strings in the list by their positions}
property Values[const Name: string]: string read GetValue write SetValue;
{Represents the value part of a string associated with a given Name, on strings with the form Name=Value.}
…
end;
從Tstrings的定義可以看出,它的大部分Protected和Public的方法都是虛方法或是抽象方法。(請Soul來補充一些,TstringList->TstringGridString)
2.3其他(請soul來補充)
如果你對多態(tài)還不明白的話,那請你記住多態(tài)的實質(zhì):
“相同的表達式,不同的操作”(就這么簡單)
從OOP語言的實現(xiàn)來講,多態(tài)就是使用基類的指針/引用來操作(派生類)對象,在運行期根據(jù)實際的對象,來執(zhí)行不同的操作方法;或者換一種更形象的說法:由對象自己來決定自己操作方式,編譯器只需下達做什么的命令(做什么what),而不要管怎么做(how),"怎么做"由為對象自己負責。這樣就實現(xiàn)了接口和實現(xiàn)的分離,使接口重用變得可能。
其實多態(tài)也簡單!那么使用多態(tài)應該注意什么呢?下面我的兩點幾點建議:
分析業(yè)務邏輯,然后把相關的事物抽象為“對象”,再用對象方法封裝業(yè)務邏輯。把一些具有多態(tài)性的操作,在基類中聲明為虛方法(virtual Method),對于在基類沒有必要實現(xiàn)的就聲明為抽象方法(virtual Abstract Method),然后在其派生類中再覆載它(Override),在使用的時候用基類的引用/指針來調(diào)用,這樣順理成章地實現(xiàn)了現(xiàn)實世界中的多態(tài)性。記住千萬不要為了多態(tài),而去實現(xiàn)多態(tài),這是一種走形式化的做法,是沒有意義的。
由于基類與派生類有一種天然“耦合”關系,修改基類就會導致“牽一發(fā)而動全身”,這將是非常麻煩的事情!因此要盡量弱化基類的功能實現(xiàn),必要時把它設計為“抽象類”,并保證穩(wěn)定的接口,這可以通過預留一些冗余的虛函數(shù)(或抽象函數(shù))來實現(xiàn)。
相關問題
討論Delphi的多態(tài): http://www.delphibbs.com/delphibbs/dispq.asp?lid=1753965
關于多態(tài)性: http://www.delphibbs.com/delphibbs/dispq.asp?lid=1854895
什么是多態(tài)?在日常編程中有哪些運用?http://www.delphibbs.com/delphibbs/dispq.asp?lid=960465
overload 與 override有何區(qū)別,請執(zhí)教?http://www.delphibbs.com/delphibbs/dispq.asp?lid=296739
派生類的指針指向基類對象的問題 http://www.delphibbs.com/delphibbs/dispq.asp?lid=2104106
(最后一個問題是我在深入學習多態(tài)時在DelphiBBS上提的,曾引起熱烈的討論,建議看看)
新聞熱點
疑難解答
圖片精選