新手繼續,大牛可以關閉瀏覽頁了。
英文及縮寫:
面向過程:procedure oriented programming POP
面向對象:object oriented programming OOP
面向對象和面向過程的歷程:
面向過程編程采取的是時間換空間的策略,因為在早期計算機配置低,內存小,如何節省內存則成了首要任務,哪怕是運行的時間更長。隨着硬件技術的發展,硬件不再成為瓶頸,相反更好的模擬現實世界、系統的可維護性等問題凸顯出來,於是面向對象設計應運而生。當下:應用在PC機上的一般應用系統,由於不太需要考慮硬件的限制,而系統的可維護性等方面卻要求很高,一般采用面向對象方式;而在內存限制有所要求的嵌入式系統,則大多采用面向過程方式進行設計編程。
定義:
面向過程是分析解決問題的步驟,然后用函數把這些步驟一步一步的實現,然后在使用的時候一一調用則可。面向對象是把構成問題的事務分解成各個對象,而建立對象的目的也不是為了完成一個個步驟,而是為了描述某個事物在解決整個問題的過程中所發生的行為。下面舉一例說明面向過程和面向對象編程。
首先使用面向過程:
1、開始游戲,
2、黑子先走,
3、繪制畫面,
4、判斷輸贏,
5、輪到白子,
6、繪制畫面,
7、判斷輸贏,
8、返回步驟2,
9、輸出最后結果。
把上面每個步驟用分別的函數來實現,問題就解決了。
面向對象的設計則是從另外的思路來解決問題。整個五子棋可以分為:
1、黑白雙方,這兩方的行為是一模一樣的,
2、棋盤系統,負責繪制畫面,
3、規則系統,負責判定諸如犯規、輸贏等。
第一類對象(玩家對象)負責接受用戶輸入,並告知第二類對象(棋盤對象)棋子布局的變化,棋盤對象接收到了棋子的變化就要負責在屏幕上面顯示出這種變化,同時利用第三類對象(規則系統)來對棋局進行判定。
面向對象保證了功能的統一型,從而為擴展打下基礎。現在要加入悔棋的功能,如果要改動面向過程的設計,那么從輸入到判斷到顯示這一連串的步驟都要改動,甚至步驟之間的循序都要進行大規模調整。如果是面向對象的話,只用改動棋盤對象就行了,棋盤系統保存了黑白雙方的棋譜,簡單回溯就可以了,而顯示和規則判斷則不用顧及,同時整個對對象功能的調用順序都沒有變化,改動只是局部的。由此可以看出面向對象更易於擴展。
下面簡單介紹面向對象三大特征:
封裝:
封裝是指將數據與具體操作的實現代碼放在某個對象內部,使這些代碼的實現細節不被外界發現,外界只能通過接口使用該對象,而不能通過任何形式修改對象內部實現,正是由於封裝機制,程序在使用某一對象時不需要關心該對象的數據結構細節及實現操作的方法。使用封裝能隱藏對象實現細節,使代碼更易維護,同時因為不能直接調用、修改對象內部的私有信息,在一定程度上保證了系統安全性。
繼承:
繼承來源於現實世界,一個最簡單的例子就是孩子會具有父母的一些特征,即每個孩子都會繼承父親或者母親的某些特征,當然這只是最基本的繼承關系,現實世界中還存在着更復雜的繼承,面向對象之所以使用繼承機制主要是用於實現代碼的復用多個類所公用的代碼部分可以只在一個類中提供,而其他類只需要繼承即可。
多態:
多態與繼承纖細緊密,是面向對象編程中另一個突出的特征,所謂的多態是指在繼承體系中,所有派生類都從基類繼承接口,但由於每個派生類都是獨立的實體,因此在接收同一消息的時候,可能會生成不同的響應。多態的作用作為隱藏代碼實現細節,使得代碼能夠模塊化;擴展代碼模塊,實現接口重用。簡單來說:一種行為產生多種效果。
總的來說:封裝可以隱藏實現細節同時包含私有成員,使得代碼模塊化並增加安全指數;基礎可以擴展已存在的模塊,目的是為了代碼重用;多態則是為了保證:類在繼承和派生的時候,保證家譜中任何類的實例被正確調用,實現了接口重用。
多態實現:
實現多態,有二種方式,覆蓋,重載。
覆蓋,是指子類重新定義父類的虛函數的做法。
重載,是指允許存在多個同名函數,而這些函數的參數表不同(或許參數個數不同,或許參數類型不同,或許兩者都不同)。
其實,重載的概念並不屬於“面向對象編程”,重載的實現是:編譯器根據函數不同的參數表,對同名函數的名稱做修飾,然后這些同名函數就成了不同的函數(至少對於編譯器來說是這樣的)。如,有兩個同名函數:function func(p:integer):integer;和function func(p:string):integer;。那么編譯器做過修飾后的函數名稱可能是這樣的:int_func、str_func。對於這兩個函數的調用,在編譯器間就已經確定了,是靜態的(記住:是靜態)。也就是說,它們的地址在編譯期就綁定了(早綁定),因此,重載和多態無關!真正和多態相關的是“覆蓋”。當子類重新定義了父類的虛函數后,父類指針根據賦給它的不同的子類指針,動態(記住:是動態!)的調用屬於子類的該函數,這樣的函數調用在編譯期間是無法確定的(調用的子類的虛函數的地址無法給出)。因此,這樣的函數地址是在運行期綁定的(晚綁定)。結論就是:重載只是一種語言特性,與多態無關,與面向對象也無關。
扯到覆蓋和重載了,那就說說函數重載、覆蓋、隱藏的基本定義吧。
函數重載:是指允許存在多個同名函數,這些函數的參數列表不同,或許是參數個數不同,或許是參數類型不同,或許是兩者都不同。重要一點:函數重載是發生在同一個類中。調用時,根據參數類型的不同進行調用,同時編譯器在編譯期間就確定了要調用的函數。(函數的重載與多態無關)。
函數覆蓋:函數覆蓋也被稱為函數重寫(override),是子類重新定義基類虛函數的方法。
構成函數覆蓋的條件:
(1)基類的函數必須是虛函數(virtual進行聲明)
(2)發生覆蓋的兩個函數必須分別位於派生類和基類中
(3)函數名稱和參數列表必須完全相同
由於c++,c#多態性是通過虛函數來實現的,所以函數覆蓋總是和多態聯系在一起,並且是程序在運行時才確定要調用的函數,因此也成為動態綁定或者后期綁定。
函數隱藏:指子類中具有和基類同名的函數,但是並不考慮參數列表是否相同,從而在子類中隱藏了基類的同名函數。有以下兩種情況:
(1)子類函數和基類函數完全相同,只是基類的函數沒有使用virtual關鍵字,此時基類的函數將被隱藏。
(2)子類函數和基類函數名字相同,但是參數列表不同,在這種情況下,無論基類的函數是否聲明為virtual,基類的函數都將被隱藏。
一個小例子:
using System; class A { public void F() { Console.WriteLine("A.F"); } public virtual void G() { Console.WriteLine("A.G"); } } class B : A { new public void F() { Console.WriteLine("B.F"); } public override void G() { Console.WriteLine("B.G"); } } class Test { static void Main() { B b = new B(); A a = b; a.F(); b.F(); a.G(); b.G(); Console.ReadLine(); } }
上面實例運行結果為:A.F B.F B.G B.G。由此可以得出:對於非虛方法來說,無論是被其所在的類的實例調用,還是被其所派生的類的實例調用,方法的執行方式不變。但對於虛方法來說,它的執行方式可以被派生類改變,並且是通過重載來實現的。
