




版權(quán)說明:本文檔由用戶提供并上傳,收益歸屬內(nèi)容提供方,若內(nèi)容存在侵權(quán),請進行舉報或認領(lǐng)
文檔簡介
1、輕量級的面向?qū)ο驝語言編程框架LW_OOPC介紹金永華、陳國棟2010/03/02摘要:本文介紹一種輕量級的面向?qū)ο蟮腃語言編程框架:LW_OOPC。LW_OOPC是Light-Weight Object-Oriented Programming in(with) C的縮寫,總共一個.h文件,20個宏,約130行代碼,非常的輕量級,但卻很好的支持了很多面向?qū)ο蟮奶匦?,比如繼承、多態(tài),可以優(yōu)美的實現(xiàn)面向接口編程。這個框架系由臺灣的高煥堂先生以及他的MISOO團隊首創(chuàng),之后由我繼續(xù)改進優(yōu)化,最后,經(jīng)高煥堂同意以LGPL協(xié)議開源(開源網(wǎng)址參見后文)。用C語言實現(xiàn)OO?我沒聽錯嗎?這聽起來真是太瘋狂了
2、! 大家都知道,C+支持了面向?qū)ο蠛兔嫦蚍盒途幊?,比C要更強大些。那么,為什么要在C語言中實踐面向?qū)ο竽??為什么不直接使用C+呢?為什么要用面向?qū)ο??面向過程方式開發(fā)的系統(tǒng),代碼復(fù)雜,耦合性強,難以維護,隨著我們所要解決的問題越來越復(fù)雜,代碼也變得越來越復(fù)雜,越來越難以掌控,而面向?qū)ο蟾淖兞顺绦騿T的思維方式,以更加符合客觀世界的方式來認識世界,通過合理的運用抽象、封裝、繼承和多態(tài),更好的組織程序,從而很好地應(yīng)對這種復(fù)雜性。 為什么不直接使用C+?C和C+之爭由來已久,可能要持續(xù)到它們中的一種去世_。C語言以其簡潔明快,功能強大的特點,深得開發(fā)人員的喜愛,尤其是在嵌入式開發(fā)領(lǐng)域,C語言更是占據(jù)了
3、絕對老大的地位。在我看來,語言只是工具,作為程序員,我們要做的是:選擇合適的語言,解決恰當?shù)膯栴}。我們要尊重事實,考慮開發(fā)環(huán)境(軟硬件環(huán)境),考慮團隊成員的水平,從商用工程的角度講,選擇團隊成員擅長的語言進行開發(fā),風險要小很多。一些從Java/C#轉(zhuǎn)到C的程序員們,無法從面向?qū)ο笄袚Q到面向過程,但又必須與C語言同事們在遺留的C系統(tǒng)上開發(fā)軟件,他們有時會非常困惑:C語言是面向過程的編程語言,如何實踐面向?qū)ο?,甚至面向接口編程呢?此時,就非常需要在C語言中實現(xiàn)面向?qū)ο蟮氖侄?,而LW_OOPC正是應(yīng)對這一難題的解決之道。LW_OOPC是什么?簡而言之:LW_OOPC是一套C語言的宏,總共1個.h文件
4、(如果需要內(nèi)存泄漏檢測支持以及調(diào)試打印支持,那么還需要1個.c文件(lw_oopc.c,約145行),20個宏,約130行代碼。LW_OOPC是一種C語言編程框架,用于支持在C語言中進行面向?qū)ο缶幊?。LW_OOPC宏介紹下面,先通過一個簡單的示例來展示LW_OOPC這套宏的使用方法。我們要創(chuàng)建這樣一些對象:動物(Animal),魚(Fish),狗(Dog),車子(Car)。顯然,魚和狗都屬于動物,都會動,車子也會動,但是車子不是動物。會動是這些對象的共同特征,但是,顯然它們不屬于一個家族。因此,我們首先考慮抽象出一個接口(IMoveable),以描述會動這一行為特征:INTERFACE(IMo
5、veable) void (*move)(IMoveable* t); / Move行為;INTERFACE宏用于定義接口,其成員(方法)均是函數(shù)指針類型。然后,我們分析Animal,它應(yīng)該是抽象類還是接口呢?動物都會吃,都需要呼吸,如果僅僅考慮這兩個特征,顯然可以把Animal定為接口。不過,這里,為了展示抽象類在LW_OOPC中如何應(yīng)用。我們讓Animal擁有昵稱和年齡屬性,并且,讓動物和我們打招呼(sayHello方法),但,我們不允許用戶直接創(chuàng)建Animal對象,所以,這里把Animal定為抽象類:ABS_CLASS(Animal) char name128; / 動物的昵稱(假設(shè)小于
6、128個字符) int age; / 動物的年齡 void (*setName)(Animal* t, const char* name); / 設(shè)置動物的昵稱 void (*setAge)(Animal* t, int age); / 設(shè)置動物的年齡 void (*sayHello)(Animal* t); / 動物打招呼 void (*eat)(Animal* t); / 動物都會吃(抽象方法,由子類實現(xiàn)) void (*breathe)(Animal* t); / 動物都會呼吸(抽象方法,由子類實現(xiàn)) void (*init)(Animal* t, const char* name, i
7、nt age);/ 初始化昵稱和年齡;ABS_CLASS宏用于定義抽象類,允許有成員屬性。代碼的含義參見代碼注釋。緊接著,我們來定義Fish和Dog類,它們都繼承動物,然后還實現(xiàn)了IMoveable接口:CLASS(Fish) EXTENDS(Animal); / 繼承Animal抽象類 IMPLEMENTS(IMoveable);/ 實現(xiàn)IMoveable接口 void (*init)(Fish* t, const char* name, int age);/ 初始化昵稱和年齡;CLASS(Dog) EXTENDS(Animal);/ 繼承Animal抽象類 IMPLEMENTS(IMove
8、able);/ 實現(xiàn)IMoveable接口 void(*init)(Dog* t, const char* name, int age);/ 初始化昵稱和年齡;為了讓Fish對象或Dog對象在創(chuàng)建之后,能夠很方便地初始化昵稱和年齡,F(xiàn)ish和Dog類均提供了init方法。下面,我們來定義Car,車子不是動物,但可以Move,因此,讓Car實現(xiàn)IMoveable 接口即可:CLASS(Car) IMPLEMENTS(IMoveable); / 實現(xiàn)IMoveable接口(車子不是動物,但可以Move);接口,抽象類,具體類的定義都已經(jīng)完成了。下面,我們開始實現(xiàn)它們。接口是不需要實現(xiàn)的,所以IMo
9、veable沒有對應(yīng)的實現(xiàn)代碼。Animal是抽象動物接口,是半成品,所以需要提供半成品的實現(xiàn):/* 設(shè)置動物的昵稱*/void Animal_setName(Animal* t, const char* name) / 這里假定name不會超過128個字符,為簡化示例代碼,不做保護(產(chǎn)品代碼中不要這樣寫) strcpy(t-name, name);/* 設(shè)置動物的年齡*/void Animal_setAge(Animal* t, int age) t-age = age;/* 動物和我們打招呼*/void Animal_sayHello(Animal* t) printf(Hello! 我是
10、%s,今年%d歲了!n, t-name, t-age);/* 初始化動物的昵稱和年齡*/void Animal_init(Animal* t, const char* name, int age) t-setName(t, name); t-setAge(t, age);ABS_CTOR(Animal)FUNCTION_SETTING(setName, Animal_setName);FUNCTION_SETTING(setAge, Animal_setAge);FUNCTION_SETTING(sayHello, Animal_sayHello);FUNCTION_SETTING(init,
11、 Animal_init);END_ABS_CTOR這里出現(xiàn)了幾個新的宏,我們逐個進行講解。ABS_CTOR表示抽象類的定義開始,ABS_CTOR(Animal)的含義是Animal抽象類的“構(gòu)造函數(shù)”開始。在C語言里邊其實是沒有C+中的構(gòu)造函數(shù)的概念的。LW_OOPC中的CTOR系列宏(CTOR/END_CTOR,ABS_CTOR/END_ABS_CTOR)除了給對象(在C語言中是struct實例)分配內(nèi)存,然后,緊接著要為結(jié)構(gòu)體中的函數(shù)指針成員賦值,這一過程,也可以稱為函數(shù)綁定(有點類似C+中的動態(tài)聯(lián)編)。函數(shù)綁定的過程由FUNCTION_SETTING宏來完成。對于Fish和Dog類的實
12、現(xiàn),與Animal基本上是類似的,除了將ABS_CTOR換成了CTOR,直接參見代碼:/* 魚的吃行為 */void Fish_eat(Animal* t) printf(魚吃水草!n);/* 魚的呼吸行為 */void Fish_breathe(Animal* t) printf(魚用鰓呼吸!n);/* 魚的移動行為 */void Fish_move(IMoveable* t) printf(魚在水里游!n);/* 初始化魚的昵稱和年齡 */void Fish_init(Fish* t, const char* name, int age) Animal* animal = SUPER_PT
13、R(t, Animal); animal-setName(animal, name); animal-setAge(animal, age);CTOR(Fish)SUPER_CTOR(Animal);FUNCTION_SETTING(Animal.eat, Fish_eat);FUNCTION_SETTING(Animal.breathe, Fish_breathe);FUNCTION_SETTING(IMoveable.move, Fish_move);FUNCTION_SETTING(init, Fish_init);END_CTOR上面是Fish的實現(xiàn),下面看Dog的實現(xiàn):/* 狗的吃行
14、為 */void Dog_eat(Animal* t) printf(狗吃骨頭!n);/* 狗的呼吸行為 */void Dog_breathe(Animal* t) printf(狗用肺呼吸!n);/* 狗的移動行為 */void Dog_move(IMoveable* t) printf(狗在地上跑!n);/* 初始化狗的昵稱和年齡 */void Dog_init(Dog* t, const char* name, int age) Animal* animal = SUPER_PTR(t, Animal); animal-setName(animal, name); animal-setA
15、ge(animal, age);CTOR(Dog)SUPER_CTOR(Animal);FUNCTION_SETTING(Animal.eat, Dog_eat);FUNCTION_SETTING(Animal.breathe, Dog_breathe);FUNCTION_SETTING(IMoveable.move, Dog_move);FUNCTION_SETTING(init, Dog_init);END_CTOR細心的朋友可能已經(jīng)注意到了,這里又有一個陌生的宏:SUPER_CTOR未介紹。這個宏是提供給子類用的,用于調(diào)用其直接父類的構(gòu)造函數(shù)(類似Java語言中的super()調(diào)用,在這
16、里,其實質(zhì)是要先調(diào)用父類的函數(shù)綁定過程,再調(diào)用自身的函數(shù)綁定過程),類似Java那樣,SUPER_CTOR如果要出現(xiàn),需要是ABS_CTOR或者CTOR下面緊跟的第一條語句。最后,我們把Car類也實現(xiàn)了:void Car_move(IMoveable* t) printf(汽車在開動!n);CTOR(Car)FUNCTION_SETTING(IMoveable.move, Car_move);END_CTOR下面,我們實現(xiàn)main方法,以展示LW_OOPC的威力:#include animal.hint main() Fish* fish = Fish_new(); / 創(chuàng)建魚對象 Dog*
17、dog = Dog_new(); / 創(chuàng)建狗對象 Car* car = Car_new(); / 創(chuàng)建車子對象 Animal* animals2 = 0 ; / 初始化動物容器(這里是Animal指針數(shù)組) IMoveable* moveObjs3 = 0 ; / 初始化可移動物體容器(這里是IMoveable指針數(shù)組) int i = 0; / i和j是循環(huán)變量 int j = 0; / 初始化魚對象的昵稱為:小鯉魚,年齡為:1歲 fish-init(fish, 小鯉魚, 1); / 將fish指針轉(zhuǎn)型為Animal類型指針,并賦值給animals數(shù)組的第一個成員 animals0 = SU
18、PER_PTR(fish, Animal); / 初始化狗對象的昵稱為:牧羊犬,年齡為:2歲 dog-init(dog, 牧羊犬, 2); / 將dog指針轉(zhuǎn)型為Animal類型指針,并賦值給animals數(shù)組的第二個成員 animals1 = SUPER_PTR(dog, Animal); / 將fish指針轉(zhuǎn)型為IMoveable接口類型指針,并賦值給moveOjbs數(shù)組的第一個成員 moveObjs0 = SUPER_PTR(fish, IMoveable); / 將dog指針轉(zhuǎn)型為IMoveable接口類型指針,并賦值給moveOjbs數(shù)組的第二個成員 moveObjs1 = SUPE
19、R_PTR(dog, IMoveable); / 將car指針轉(zhuǎn)型為IMoveable接口類型指針,并賦值給moveOjbs數(shù)組的第三個成員 moveObjs2 = SUPER_PTR(car, IMoveable); / 循環(huán)打印動物容器內(nèi)的動物信息 for(i=0; ieat(animal); animal-breathe(animal); animal-sayHello(animal); / 循環(huán)打印可移動物體容器內(nèi)的可移動物體移動方式的信息 for(j=0; jmove(moveObj); lw_oopc_delete(fish); lw_oopc_delete(dog); lw_oo
20、pc_delete(car); return 0;從上邊的代碼中,我們驚喜地發(fā)現(xiàn),在C語言中,借助LW_OOPC,我們實現(xiàn)了將不同的動物(Fish和Dog對象)裝入Animal容器,然后可以用完全相同的方式調(diào)用Animal的方法(比如eat和breathe方法),而實際調(diào)用的是具體的實現(xiàn)類(Fish和Dog)的對應(yīng)方法。這正是面向?qū)ο笾械亩鄳B(tài)的概念。同樣,我們可以將Fish對象,Dog對象,以及Car對象均視為可移動物體,均裝入IMoveable容器,然后用完全相同的方式調(diào)用IMoveable接口的move方法??吹搅藛幔拷柚鶯W_OOPC,在C語言下我們竟然可以輕松地實現(xiàn)面向?qū)ο蠛兔嫦蚪涌诰?/p>
21、程! 下面,再舉一個稍微復(fù)雜的例子,它的覆蓋面是足夠全面的,足以一瞥面向?qū)ο缶幊痰?個要素:數(shù)據(jù)抽象、繼承和多態(tài)。通過這個例子,我們期望展現(xiàn)出LW_OOPC在遭遇問題本身比較復(fù)雜的情形下,是如何從容應(yīng)對的,以加深讀者對LW_OOPC的認識。(備注:該問題來自C+沉思錄第八章的例子,有興趣的讀者可以對照參閱)問題描述:此程序涉及的內(nèi)容是用來表示算術(shù)表達式的樹。例如,表達式(-5)*(3+4)對應(yīng)的樹為:一個表達式樹包括代表常數(shù)、一元運算符和二元運算符的節(jié)點。這樣的樹結(jié)構(gòu)在編譯器和計算器程序中都可能用到。我們希望能通過調(diào)用合適的函數(shù)來創(chuàng)建這樣的樹,然后打印該樹的完整括號化形式。例如,我們希望#in
22、clude stdio.h#include Expr.hint main()Expr* expr1 = Expr_new();Expr* expr2 = Expr_new();Expr* expr3 = Expr_new();Expr* expr = Expr_new();expr1-initUnaryX(expr1, -, 5);expr2-initBinaryX(expr2, +, 3, 4);expr3-initBinary(expr3, *, expr1, expr2);expr-initBinary(expr, *, expr3, expr3);expr3-print(expr3);
23、printf(n);expr-print(expr);printf(n); Expr_delete(expr);Expr_delete(expr3);Expr_delete(expr2);Expr_delete(expr1);return 0;打印(-5)*(3+4)(-5)*(3+4)*(-5)*(3+4)作為輸出。此外,我們不想為這些表達式的表示形式操心,更不想關(guān)心有關(guān)它們內(nèi)存分配和回收的事宜。這個程序所做的事情在很多需要處理復(fù)雜輸入的大型程序中是很典型的,例如編譯器、編輯器、CAD/CAM系統(tǒng)等。此類程序中通常要花費很大的精力來處理類似樹、圖和類似的數(shù)據(jù)結(jié)構(gòu)。這些程序的開發(fā)者永遠需要面對
24、諸如內(nèi)存分配、靈活性和效率之類的問題。面向?qū)ο蠹夹g(shù)可以把這些問題局部化,從而確保今后發(fā)生的一系列變化不會要求整個程序中的其他各個部分隨之做相應(yīng)調(diào)整。解決方案:通過考查這個樹結(jié)構(gòu),會發(fā)現(xiàn)這里有3種節(jié)點。一種表示整數(shù)表達式,包含一個整數(shù)值,無子節(jié)點。另外兩個分別表示一元表達式和二元表達式,包含一個操作符,分別有一個或兩個子節(jié)點。我們希望打印各種節(jié)點,但是具體方式需要視要打印節(jié)點的類型而定。這就是動態(tài)綁定的用武之地了:我們可以定義一個虛函數(shù)(print)來指明應(yīng)當如何打印各種節(jié)點。動態(tài)綁定將會負責在運行時基于打印節(jié)點的實際類型調(diào)用正確的函數(shù)。首先,我們抽象出“節(jié)點”的概念,抽象類的名字定為Expr_
25、node,它提供了打印的抽象接口,所有的實際節(jié)點類型均從它派生:ABS_CLASS(Expr_node) void (*print)(Expr_node* t);具體類的情形怎樣?這些具體類型中最簡單的一類是包含一個整數(shù),沒有子節(jié)點的節(jié)點:CLASS(Int_node) EXTENDS(Expr_node); int n; void (*init)(Int_node* t, int k);其他類型又如何呢?每個類中都必須存儲一個操作符(這倒簡單,本文中假定操作符最長不超過2個字符,所以,可以用長度為3的字符數(shù)組來保存),但是如何存儲子節(jié)點呢?在運行時之前,我們并不知道子節(jié)點的類型會是什么,所以
26、我們不能按值存儲子節(jié)點,必須存儲指針。這樣,一元和二元節(jié)點類如下所示:CLASS(Unary_node) EXTENDS(Expr_node);char op3;/假設(shè)操作符最長不超過2個字符 Expr_node* opnd; void (*init)(Unary_node* t, const char* a, Expr_node* b);CLASS(Binary_node)EXTENDS(Expr_node); char op3;/假設(shè)操作符最長不超過2個字符 Expr_node* left; Expr_node* right;void (*init)(Binary_node* t, con
27、st char* a, Expr_node* b, Expr_node * c);這個設(shè)計方案可以用,不過有一個問題。用戶要處理的不是值,而是指針,所以必須記住分配和釋放對象。例如,我們需要這么創(chuàng)建表達式樹:Int_node* int_node1 = Int_node_new();Int_node* int_node2 = Int_node_new();Int_node* int_node3 = Int_node_new();Unary_node* unary_node = Unary_node_new();Binary_node* binary_node1 = Binary_node_new
28、();Binary_node* binary_node = Binary_node_new();int_node1-init(int_node1, 5);int_node2-init(int_node2, 3);int_node3-init(int_node3, 4);unary_node-init(unary_node, -, int_node1);binary_node1-init(binary_node1, +, int_node2, int_node3);binary_node-init(binary_node, *, unary_node, binary_node1);lw_oopc
29、_delete(int_node1);/ 刪除創(chuàng)建的其他節(jié)點也就是說,我們需要去關(guān)心每一個節(jié)點的創(chuàng)建和釋放。我們不僅把內(nèi)存管理這類煩心事推給了用戶,而且對用戶來說也沒有什么方便的辦法來處理這些事情。我們得好好想想辦法了。這里,提供一種解決內(nèi)存管理問題的思路:引用計數(shù),這里是針對指針,對指針的狀況進行計數(shù),對象創(chuàng)建的時候,引用計數(shù)為1,凡是指針被賦值了,該指針所指對象的引用計數(shù)就自增一,每次指針要釋放,都先檢查對象的引用計數(shù),讓引用計數(shù)自減一,如果引用計數(shù)為0,則釋放該對象。另外,原先的設(shè)計不夠高層,用戶只能直接針對節(jié)點進行操作,沒有提供操作子樹的概念(這也是用戶代碼之所以復(fù)雜的原因之一),我們
30、發(fā)現(xiàn),通過提供子樹的概念,我們不但能夠隱藏Expr_node繼承層次,而且,對于每一個節(jié)點,我們具備了操縱左子樹和右子樹的能力(原來只能操作左子節(jié)點和右子節(jié)點)。而這種功能增強完全是建立在面向?qū)ο蟮臋C制之上,我們并沒有引入耦合,在非常自然和輕松的情形下,我們獲得了更好的軟件組件之間協(xié)作的能力,這正是面向?qū)ο蟮镊攘λ凇_@里,我們把子樹的概念用類Expr來表示,由于子樹此時成了Expr_node具體類的成員,同樣,左右子樹在Expr_node中同樣是以指針的方式保存,所以,對Expr也需要進行引用計數(shù),代碼直接貼上來,細節(jié)解說參見注釋:/ expr.h#ifndef EXPR_H_INCLUDE
31、D_#define EXPR_H_INCLUDED_#include lw_oopc.h/ 表達式節(jié)點ABS_CLASS(Expr_node)int use; / 引用計數(shù) void (*print)(Expr_node* t); / 打印表達式節(jié)點void (*finalize)(Expr_node* t); / 子類通過覆寫finalize方法,實現(xiàn)對資源清理行為的定制;/ 表達式(子樹的概念),其中,init*方法族提供了構(gòu)建子樹的高層API,方便用戶使用CLASS(Expr)int use; / 引用計數(shù)Expr_node* p; / 子樹的根節(jié)點 / 構(gòu)建整數(shù)表達式(包含一個整數(shù)值,
32、無子表達式)void (*initInt)(Expr* t, int); / 構(gòu)建一元表達式(包含一個操作符,一個子表達式)void (*initUnary)(Expr* t, const char*, Expr*); / 構(gòu)建一元表達式的重載形式(通過傳入個整型值參數(shù),構(gòu)造一個子表達式為整數(shù)表達式的一元表達式)void (*initUnaryX)(Expr* t, const char*, int); / 構(gòu)建二元表達式(包含一個操作符,二個子表達式)void (*initBinary)(Expr* t, const char*, Expr*, Expr*); / 構(gòu)建二元表達式的重載形式(
33、通過傳入個整型值參數(shù),構(gòu)造兩個子表達式均為整數(shù)表達式的二元表達式)void (*initBinaryX)(Expr* t, const char*, int, int);void (*print)(Expr* t); / 打印子樹;/ 整數(shù)表達式節(jié)點CLASS(Int_node) EXTENDS(Expr_node); / 繼承Expr_nodeint n; / 整數(shù)值 / 初始化整數(shù)表達式節(jié)點(傳入整數(shù)值)void (*init)(Int_node* t, int k); ;/ 一元表達式節(jié)點CLASS(Unary_node) EXTENDS(Expr_node); / 繼承Expr_nod
34、echar op3; / 假設(shè)操作符最長不超過2個字符 Expr* opnd; / 子表達式 / 初始化一元表達式節(jié)點(傳入一個操作符和一個子表達式)void (*init)(Unary_node* t, const char* a, Expr* b);/ 二元表達式節(jié)點CLASS(Binary_node)EXTENDS(Expr_node); / 繼承Expr_node char op3; / 假設(shè)操作符最長不超過2個字符 Expr* left; / 左子表達式 Expr* right; / 右子表達式 / 初始化二元表達式節(jié)點(傳入一個操作符和兩個子表達式)void (*init)(Bin
35、ary_node* t, const char* a, Expr* b, Expr* c);#endif/expr.c/ 包含所需頭文件ABS_CTOR(Expr_node)cthis-use = 1; / 構(gòu)造函數(shù)中,將引用計數(shù)初始化為END_ABS_CTOR/ Expr_node的析構(gòu)函數(shù)(DTOR/END_DTOR用于實現(xiàn)析構(gòu)函數(shù)語義)DTOR(Expr_node)if (-cthis-use = 0) / 遞減引用計數(shù),如果計數(shù)為,釋放自己cthis-finalize(cthis); / 釋放內(nèi)存之前先清理資源(其他需要釋放的對象)return lw_oopc_true;/ 返回tru
36、e,表示析構(gòu)成功,可以釋放內(nèi)存return lw_oopc_false;/ 返回false,表示析構(gòu)失敗,不能釋放內(nèi)存END_DTOR/ 構(gòu)建整數(shù)表達式(包含一個整數(shù)值,無子表達式),n為整數(shù)值void Expr_initInt(Expr* expr, int n)Int_node* intNode = Int_node_new(lw_oopc_file_line);intNode-init(intNode, n);expr-p = SUPER_PTR(intNode, Expr_node);/ 因篇幅所限,構(gòu)建一元表達式、二元表達式以及對應(yīng)的重載形式的函數(shù)實現(xiàn)代碼省略/ 打印表達式(子樹)v
37、oid Expr_print(Expr* t)Expr_node* p = t-p;p-print(p);CTOR(Expr)FUNCTION_SETTING(initInt, Expr_initInt);FUNCTION_SETTING(initUnary, Expr_initUnary);FUNCTION_SETTING(initUnaryX, Expr_initUnaryX);FUNCTION_SETTING(initBinary, Expr_initBinary);FUNCTION_SETTING(initBinaryX, Expr_initBinaryX);FUNCTION_SETT
38、ING(print, Expr_print);cthis-use = 1; / 構(gòu)造函數(shù)中,將引用計數(shù)初始化為END_CTOR/ Expr的析構(gòu)函數(shù)(DTOR/END_DTOR用于實現(xiàn)析構(gòu)函數(shù)語義)DTOR(Expr)if (-cthis-use = 0) / 遞減引用計數(shù),如果計數(shù)為,釋放自己Expr_node_delete(cthis-p);return lw_oopc_true;return lw_oopc_false;END_DTOR/ 整數(shù)表達式節(jié)點的初始化void Int_node_init(Int_node* t, int k)t-n = k;/ 整數(shù)表達式節(jié)點的打印void I
39、nt_node_print(Expr_node* t) Int_node* cthis = SUB_PTR(t, Expr_node, Int_node);printf(%d, cthis-n); / 整數(shù)表達式節(jié)點的資源清理void Int_node_finalize(Expr_node* t)/ 什么都不需要做CTOR(Int_node)SUPER_CTOR(Expr_node);FUNCTION_SETTING(init, Int_node_init);FUNCTION_SETTING(Expr_node.print, Int_node_print);FUNCTION_SETTING(E
40、xpr_node.finalize, Int_node_finalize);END_CTOR/ 因篇幅所限,一(二)元表達式節(jié)點的初始化、打印、資源清理、構(gòu)造等函數(shù)的實現(xiàn)代碼省略/main.c#include stdio.h#include Expr.hint main()Expr* expr = Expr_new();/ 創(chuàng)建expr1、expr2、expr3的代碼expr1-initUnaryX(expr1, -, 5);expr2-initBinaryX(expr2, +, 3, 4);expr3-initBinary(expr3, *, expr1, expr2);expr-initB
41、inary(expr, *, expr3, expr3);expr3-print(expr3);printf(n);expr-print(expr);printf(n);Expr_delete(expr);/ 刪除expr3、expr2、expr1的代碼return 0;程序運行的效果如下:怎么樣?效果還不錯吧,最重要的是,我們的C語言代碼現(xiàn)在已經(jīng)完全是面向?qū)ο蟮?。方案的可擴展性如何?假設(shè)我們希望添加一種Ternary_node類型來表示三元操作符,如?:(也就是if-then-else操作符),看看,難度有多大?事實上,正是因為前面的設(shè)計是面向?qū)ο蟮?,要增加一種節(jié)點類型易如反掌:/ 三元表達
42、式節(jié)點CLASS(Ternary_node)EXTENDS(Expr_node);char op3; / 假設(shè)操作符最長不超過2個字符Expr* left;Expr* middle;Expr* right; / 初始化三元表達式節(jié)點(傳入一個操作符和三個子表達式)void (*init)(Ternary_node* t, const char* op, Expr* left, Expr* middle, Expr* right);在Expr中添加創(chuàng)建三元表達式的方法:/ 表達式(子樹的概念),其中,init*方法族提供了構(gòu)建子樹的高層API,方便用戶使用CLASS(Expr)int use;
43、/ 引用計數(shù)Expr_node* p; / 子樹的根節(jié)點/ 既有實現(xiàn) / 構(gòu)建三元表達式(包含一個操作符,三個子表達式)void (*initTernary)(Expr* t, const char*, Expr*, Expr*, Expr*); / 構(gòu)建三元表達式的重載形式(通過傳入一個整型值參數(shù),構(gòu)造三個子表達式均為整數(shù)表達式的三元表達式)void (*initTernaryX)(Expr* t, const char*, int, int, int);/ 既有實現(xiàn);請讀者參照Binary_node的現(xiàn)有實現(xiàn),實現(xiàn)出Ternary_node,這里不再贅述。一旦實現(xiàn)出Ternary_node
44、,我們就可以這樣創(chuàng)建表達式樹并打?。? 創(chuàng)建expr1、expr2、expr3、expr對象(指針)expr1-initUnaryX(expr1, -, 0);expr2-initUnaryX(expr2, -, 5);expr3-initBinaryX(expr3, +, 3, 4);expr-initTernary(expr, ?:, expr1, expr2, expr3);expr-print(expr);printf(n);為了支持新的節(jié)點類型,對原有代碼的更動很少(僅對Expr類有增加方法),而且只有新增操作(新增類,新增方法),但沒有修改操作(指修改原有方法),面向?qū)ο蟮脑O(shè)計賦予
45、了系統(tǒng)極大的彈性,讓程序在應(yīng)對變化時,更加從容。在這個例子中,LW_OOPC幫助我們在C語言的世界里營造出OO的天地,帶領(lǐng)我們再一次領(lǐng)略了面向?qū)ο蟮娘L采。LW_OOPC最佳實踐說得簡單一點,要想使用好LW_OOPC這套宏,還得首先懂面向?qū)ο?,要遵循面向?qū)ο笤O(shè)計的那些大原則,比如開閉原則等。在C語言中使用面向?qū)ο螅鶕?jù)實際使用的情況,給出如下建議:繼承層次不宜過深,建議最多三層(接口、抽象類、具體類,參見 REF _Ref253847604 h 圖 1和 REF _Ref253847612 h 圖 2)繼承層次過深,在Java/C#/C+中均不推崇,在C語言中實踐面向?qū)ο蟮臅r候,尤其要遵循這一點
46、,只有這樣,代碼才能簡單清爽。盡量避免多重繼承盡可能使用單線繼承,但可實現(xiàn)多個接口(與Java中的單根繼承類似)。盡量避免具體類繼承具體類具體類繼承具體類,不符合抽象的原則,要盡量避免。各繼承層次分別維護好自己的數(shù)據(jù)子類盡量不要直接訪問祖先類的數(shù)據(jù),如果確實需要訪問,應(yīng)當通過祖先類提供的函數(shù),以函數(shù)調(diào)用的方式間接訪問。圖 SEQ 圖 * ARABIC 1圖 SEQ 圖 * ARABIC 2LW_OOPC的優(yōu)點:輕量級廣泛的適應(yīng)性,能夠適應(yīng)各種平臺,各種編譯器(能支持C的地方,基本上都能支持)幫助懂OO的Java/C+程序員寫出面向?qū)ο蟮腃程序。使用C,也能引入OO的設(shè)計思想和方法,在團隊的C/
47、C+分歧嚴重時可能非常有用。LW_OOPC的缺點:無法支持重載(C語言不支持所致)不完全的封裝(無法區(qū)分私有、保護和公有)LW_OOPC的INTERFACE/ABS_CLASS/CLASS三個宏展開后都是C語言的struct,其成員全是公有的,宏本身并無能力提供良好地封裝層次的支持,所以,只能從編程規(guī)范和編程風格上進行引導。不支持RTTI 既然不支持RTTI,那么顯然也無法支持安全的向下轉(zhuǎn)型(C+中的dynamic_cast的轉(zhuǎn)型功能)不支持拷貝構(gòu)造以及賦值語義轉(zhuǎn)換成接口的表述有點麻煩,表達形式相比C+要啰嗦很多。有學習成本,需要用戶學習并習慣這套宏前四條缺點,實質(zhì)上并非是LW_OOPC的缺點
48、,而是C相對C+而言的缺點,在這里,之所以也一并列上,是希望用戶不要對LW_OOPC抱太高的期望,畢竟它也只是一套C語言的宏而已,C語言有的缺點,LW_OOPC并不能夠解決??偨Y(jié):盡管如此,在使用C語言編程的時候,在某些情形下,你可能想要通過面向?qū)ο髞砀玫慕M織代碼。偶爾,你也想要用用某個設(shè)計模式,此時,這套宏能夠幫得上忙,使用它,有助于寫出相對易于理解和維護的面向?qū)ο蟮拇a。因篇幅所限,本文中沒有介紹LW_OOPC的高級特性,譬如對內(nèi)存泄漏檢測的支持。示例的完整代碼、LW_OOPC的最新版本以及關(guān)于這套宏的更加詳細的使用指南,請讀者訪問 HYPERLINK 獲取。最后,期望有興趣的讀者,發(fā)揮
49、聰明才智,提出改進建議,讓LW_OOPC變得越來越好!幕后花絮:在完善LW_OOPC宏的過程中,我也認真研究了參考資料中列出的材料。最初V1.0版本有將近25個宏,后來,收到一些同事的反饋,認為少量宏晦澀難記,而且不易理解,經(jīng)過認真考慮,刪除掉5個宏,形成現(xiàn)在的20個宏。相比其他用C實現(xiàn)面向?qū)ο蟮姆桨?,LW_OOPC簡潔優(yōu)雅,每個宏的命名都經(jīng)過仔細地推敲,盡可能做到簡單易懂,便于記憶。但愿LW_OOPC真的能夠幫助到奮斗在一線的C程序員們。參考資料:1 高煥堂。UML+OOPC嵌入式C語言開發(fā)精講,電子工業(yè)出版社,2008 年9月。2 Object-oriented Programming w
50、ith ANSI-C,下載地址: HYPERLINK /codecuts/pdfs/ooc.pdf /codecuts/pdfs/ooc.pdf。3 C實現(xiàn)面向?qū)ο蟮姆椒ǎ?RealtimeMantra/basics/object_oriented_programming_in_c.htm附錄資料:不需要的可以自行刪除C語言編譯環(huán)境中的調(diào)試功能及常見錯誤提示調(diào)試功能1常用健 : 激活系統(tǒng)菜單: 將光標在編輯窗口和、信息窗口之間切換: 加載一個文件+: 查看程序運行結(jié)果: 得到有關(guān)編輯器在線幫助+: 得到有關(guān)C語言的在線幫助+: 終止正在運行的程序2塊操作 KB: 定義塊首 KK: 定義塊尾 K
51、V: 塊移動 KC: 塊復(fù)制 KY: 塊刪除 KH: 取消塊定義3查找、替換和刪除操作 QF: 查找字符串 QA: 查找并替換字符串 Option: G(全程), B(向文件頭), N(直接替換) Y : 刪除一行 QY: 刪除從光標位置到行末的所有字符編譯中的常見錯誤例析(1)警告類錯誤 XXXdeclare but never used 變量XXX已定義但從未用過。 XXXis assigned a value which is never used 變量XXX已賦值但從未用過。 Code has no effect 程序中含有沒有實際作用的代碼。 Non-portable pointer
52、 conversion 不適當?shù)闹羔樲D(zhuǎn)換,可能是在應(yīng)該使用指針的地方用了一個非0的數(shù)值。 Possible use of XXXbefore definition 表達式中使用了未賦值的變量 Redeclaration of main 一個程序文件中主函數(shù)main不止一個。 Suspicious pointer conversion 可疑的指針轉(zhuǎn)換。通常是使用了基本類型不匹配的指針。 Unreachable code 程序含有不能執(zhí)行到的代碼。(2)錯誤或致命錯誤 Compound statement missing in function main 程序結(jié)尾缺少括號。 “”expected;
53、 “(”expected等 復(fù)合語句或數(shù)組初始化的結(jié)尾缺少“)”;“(”。 Case outside of switch case 不屬于Switch結(jié)構(gòu),多由于switch結(jié)構(gòu)中的花括號不配對所致。 Case statement missing : switch結(jié)構(gòu)中的某個case之后缺少冒號。 Constant expression required 定義數(shù)組時指定的數(shù)組長度不是常量表達式。 Declaration syntax error 結(jié)構(gòu)體或聯(lián)合類型的定義后缺少分號。 Declaration was expected 缺少說明,通常是因為缺少分界符如逗號、分號、右圓括號等所引起的。
54、 Default outside switch Default部分放到了switch結(jié)構(gòu)之外,一般是因為花括號不匹配而引起的。 do statement must have while do語句中缺少相應(yīng)的while部分。 Expression syntax 表達式語法錯。如表達式中含有兩個連續(xù)的運算符 Extra parameter in call fun 調(diào)用函數(shù)fun時給出了多余的實參。 Function should return a value 函數(shù)應(yīng)該返回一個值,否則與定義時的說明類型不匹配。 Illegal use of pointer 指針被非法引用,一般是使用了非法的指針運算
55、。 Invalid pointer addition 指針相加非法。一個指針(地址)可以和一個整數(shù)相加,但兩個指針不能相加。 Lvalue required 賦值運算的左邊是不能尋址的表達式。 Misplaced else 程序遇到了沒有配對的else No matching 表達式中的括號不配對。 Pointer required on left side of_ 在“_”運算的左邊只能允許一個指針而不能是一個一般的結(jié)構(gòu)體變量或聯(lián)合類型的變量。 Statement missing; 程序遇到了后面沒有分號的語句。 Too few parameters in call 調(diào)用某個函數(shù)時實參數(shù)目不
56、夠。 Unable to open include file XXXXXXXXXXX 頭文件找不到。 Unexpected 或:或 在不希望的地方使用了或:。 Undefined symbol Xin function fun 函數(shù)fun中的變量X沒有定義。5.連接中的常見錯誤主要錯誤類似于“undefined symbol _print in modula xxx”(print沒有定義),通常是函數(shù)名書寫錯誤。6.運行中的常見錯誤Abnormal program termination 程序異常終止。通常是由于內(nèi)存使用不當所致。Floating point error : Domain 或D
57、ivide by 0 運算結(jié)果不是一個數(shù)或被0 除Null pointer assignment 對未初始化的指針賦值,程序有嚴重錯誤。User break 在運行程序時終止。7.程序的跟蹤調(diào)試利用Run菜單可以進行程序的跟蹤調(diào)試(1)GO to Cursor ()選擇該選項使程序執(zhí)行到光標所在行首先將光標移到某行(一般為可執(zhí)行),選擇該功能項,則程序執(zhí)行到該行的前一行暫停。此時程序處于跟蹤調(diào)試狀態(tài),并有亮條顯示在暫停處,此時可以查詢變量或表達式的值。(2)Trace into ()執(zhí)行一條語句或一行暫停此時程序處于跟蹤調(diào)試狀態(tài),并有亮條顯示在暫停處。該選項可跟蹤到被調(diào)函數(shù)的內(nèi)部。(3)Ste
58、p over ()執(zhí)行一條語句或一行暫停此時程序處于跟蹤調(diào)試狀態(tài),并有亮條顯示在暫停處。該選項將自定義函數(shù)當作一個語句執(zhí)行,不跟蹤到函程序的內(nèi)部。(4)Debug 菜單程序處于跟蹤狀態(tài)時,可使用該菜單的選項。其主要是使用Evaluate目的是查詢或更新變量或表達式的值。選擇Evaluate功能后,系統(tǒng)彈出一個對話框。該對話框包含三個選項區(qū)域:Evaluate域可以輸入一個含有目前代碼中(程序暫停區(qū)的作用域)正在使用的變量名、或含變量的表達式、或常量表達式。按回車鍵后,在Result域中顯示變量或表達式的值。還可以用New value域進行調(diào)試。如果調(diào)試程序時發(fā)現(xiàn)Result域顯示的某變量或表達
59、式的值不正確,并能估計出該變量或表達式的值,則可以將該值輸入到New value域,繼續(xù)執(zhí)行程序,其目的是肯定錯誤發(fā)生處是否在當前位置之前。如果輸入這個正確的值并將程序繼續(xù)執(zhí)行完畢而結(jié)果正確,說明在目前暫停處之前已經(jīng)發(fā)生錯誤而之后無錯誤。(5)Break/Watch用于設(shè)置斷點和監(jiān)視表達式。選擇Add Watch功能選項,系統(tǒng)將彈出一個菜單,在Add Watch框中輸入變量名或表達式,按回車鍵后,系統(tǒng)在屏幕底部開辟一個窗口并顯示 該變量或表達式的值。【常見錯誤信息語句索引】Ambiguous operators need parentheses:不明確的運算需要用括號括起 Ambiguous
60、symbol xxx :不明確的符號 Argument list syntax error:參數(shù)表語法錯誤 Array bounds missing in function main 缺少數(shù)組界限符 Array bounds missing :丟失數(shù)組界限符 Array size too large :數(shù)組尺寸太大 Bad character in paramenters :參數(shù)中有不適當?shù)淖址?Bad file name format in include directive :包含命令中文件名格式不正確 Bad ifdef directive synatax :編譯預(yù)處理ifdef有語法錯
溫馨提示
- 1. 本站所有資源如無特殊說明,都需要本地電腦安裝OFFICE2007和PDF閱讀器。圖紙軟件為CAD,CAXA,PROE,UG,SolidWorks等.壓縮文件請下載最新的WinRAR軟件解壓。
- 2. 本站的文檔不包含任何第三方提供的附件圖紙等,如果需要附件,請聯(lián)系上傳者。文件的所有權(quán)益歸上傳用戶所有。
- 3. 本站RAR壓縮包中若帶圖紙,網(wǎng)頁內(nèi)容里面會有圖紙預(yù)覽,若沒有圖紙預(yù)覽就沒有圖紙。
- 4. 未經(jīng)權(quán)益所有人同意不得將文件中的內(nèi)容挪作商業(yè)或盈利用途。
- 5. 人人文庫網(wǎng)僅提供信息存儲空間,僅對用戶上傳內(nèi)容的表現(xiàn)方式做保護處理,對用戶上傳分享的文檔內(nèi)容本身不做任何修改或編輯,并不能對任何下載內(nèi)容負責。
- 6. 下載文件中如有侵權(quán)或不適當內(nèi)容,請與我們聯(lián)系,我們立即糾正。
- 7. 本站不保證下載資源的準確性、安全性和完整性, 同時也不承擔用戶因使用這些下載資源對自己和他人造成任何形式的傷害或損失。
最新文檔
- 【正版授權(quán)】 ISO 14687:2025 EN Hydrogen fuel quality - Product specification
- 2025年度大型企業(yè)運營總監(jiān)職位聘用合同書
- 2025年度旅游項目開發(fā)貸款合同范本
- 2025年度體育產(chǎn)業(yè)運動員雇傭合同范本
- 2025年度養(yǎng)豬場附屬設(shè)施建筑承包服務(wù)協(xié)議
- 2025高考作文預(yù)測:“讓”與“不讓”
- 秋季學習資源整合計劃
- 生物網(wǎng)絡(luò)教學資源建設(shè)計劃
- 制定餐飲行業(yè)安保措施計劃
- 幼兒園創(chuàng)新課程的開發(fā)與實踐計劃
- 2024中智集團招聘重要崗位高頻500題難、易錯點模擬試題附帶答案詳解
- 八年級美術(shù)下冊第1課文明之光省公開課一等獎新名師課獲獎?wù)n件
- 食品安全管理制度可打印【7】
- 2024年山東省東營市中考數(shù)學試題 (原卷版)
- 2024全國能源行業(yè)火力發(fā)電集控值班員理論知識技能競賽題庫(多選題)
- 2024年山東新華書店集團限公司臨沂市縣分公司招聘錄取人員(高頻重點提升專題訓練)共500題附帶答案詳解
- 公司員工外派協(xié)議書范文
- 2024年四川省南充市中考物理試卷真題(含官方答案)
- 信息科技重大版 七年級上冊 互聯(lián)網(wǎng)應(yīng)用與創(chuàng)新 第二單元教學設(shè)計 互聯(lián)網(wǎng)原理
- 2024年學位法學習解讀課件
- 【基于PLC的停車場車位控制系統(tǒng)設(shè)計11000字(論文)】
評論
0/150
提交評論