|
為16、32、64位架構(gòu)編寫可移植代碼
與16位相比,32位意味著程序更快、可直接尋址訪問(wèn)更多的內(nèi)存和更好的處理器架構(gòu)。鑒于此,越來(lái)越多的程序員已經(jīng)開(kāi)始考慮利用64位處理器所帶來(lái)的巨大優(yōu)勢(shì)了。 克雷研究(Cray Research 應(yīng)為品牌名)計(jì)算機(jī)已經(jīng)開(kāi)始使用64位字,并可訪問(wèn)更大的內(nèi)存地址。然而,作為正在向開(kāi)發(fā)標(biāo)準(zhǔn)軟件和與其他操作系統(tǒng)相互努力的一部分,我們已經(jīng)停止了移植那些原本基于32位處理器的代碼。事實(shí)上,我們不斷遇到我們稱之為“32位主義”的代碼,這些代碼都是在假定機(jī)器字長(zhǎng)為32位的情況下編寫的,因而很難移植這種代碼,所以必須確立一些簡(jiǎn)單的指導(dǎo)方針,以便助于編寫跨16、32、64位處理器平臺(tái)的代碼。 由于有一些遺留問(wèn)題, C語(yǔ)言在數(shù)據(jù)類型和數(shù)據(jù)構(gòu)造方面,顯得有點(diǎn)過(guò)剩了。可以使用的不僅僅是char、short、int和long類型,還有相應(yīng)的unsigned(無(wú)符號(hào))類型,當(dāng)然你可在結(jié)構(gòu)(structure)和聯(lián)合(union)中混合使用,可以在聯(lián)合中包含結(jié)構(gòu),再在結(jié)構(gòu)中包含聯(lián)合,如果還嫌數(shù)據(jù)類型不夠復(fù)雜,還可以轉(zhuǎn)換成比特位,當(dāng)然也可以把一種數(shù)據(jù) 類型轉(zhuǎn)換成另一種你想要的數(shù)據(jù)類型。正是因?yàn)檫@些工具太強(qiáng)大,如果不安全地使用它們,就有可能會(huì)傷到自己了。 高級(jí)代碼的高級(jí)結(jié)構(gòu) 在Kernighan和Plauger經(jīng)典的《The Elements of Programming Style》一書(shū)中,他們的建議是“選擇使程序看上去簡(jiǎn)單的數(shù)據(jù)表示法”。對(duì)個(gè)人而言,這意味著為高級(jí)編程使用高級(jí) 數(shù)據(jù)結(jié)構(gòu),而低級(jí)編程使用低級(jí)數(shù)據(jù)結(jié)構(gòu)。 在我們移植的程序中,有一個(gè)常見(jiàn)的32位主義bug,不如拿它來(lái)做個(gè)例子,科內(nèi)爾大學(xué)編寫的用于網(wǎng)間互訪的路由協(xié)議引擎,在伯克利網(wǎng)絡(luò)環(huán)境下(指TCP/IP),自然是使用inet_addr( )來(lái)把表示Internet地址的 字符串轉(zhuǎn)換成二進(jìn)制形式。Internet地址碰巧也是32位的,就如運(yùn)行伯克利網(wǎng)絡(luò)系統(tǒng)的其他計(jì)算機(jī)一樣,都是有著同樣的字寬。 但也存在著有關(guān)Internet地址的高級(jí)定義:in_ addr結(jié)構(gòu)。這個(gè)結(jié)構(gòu)定義包括了子域s_ addr,它是一個(gè)包含了Internet地址的unsigned long標(biāo)量。inet_addr()接受一個(gè)指向字符的指針,并且返回一個(gè)unsigned long,在轉(zhuǎn)換地址字符串過(guò)程中如果發(fā)生錯(cuò)誤,inet_addr將返回-1。 程序Gated讀取以文本格式存放Internet地址的配置文件,并把它們放入sockaddr_in(這是一個(gè)包含了結(jié)構(gòu)in_addr的高級(jí)結(jié)構(gòu))。例1(a)中的代碼可以在32位電腦上正常運(yùn)行,但移植到克雷研究計(jì)算機(jī)上,卻無(wú)法運(yùn)行,為什么呢? 例1:高級(jí)代碼的高級(jí)結(jié)構(gòu) (a) struct sockaddr_in saddrin char *str;
if ((saddrin.sin_addr.s_addr = inet_addr(str)) == (unsigned long)-1) { do_some_error_handling; } |
(b) struct sockaddr_in saddrin char *str;
if (inet_aton(str, &saddrin.sin_addr) ! = OK) { do_some_error_handling; } |
因?yàn)橹灰猧net_addr能夠正確地解析字符串,那么一切OK。當(dāng)inet_addr在64位計(jì)算機(jī)上返回一個(gè)錯(cuò)誤時(shí),這段代碼卻未能捕捉到。你必須要考慮比較語(yǔ)句中的數(shù)據(jù)位寬,來(lái)確定到底是哪出了錯(cuò)。 首先,inet_addr返回錯(cuò)誤值——unsigned long -1,在64位中表示為比特位全為1,這個(gè)值被存儲(chǔ)在結(jié)構(gòu)in_addr下的子域s_addr中,而in_addr必須是32位來(lái)匹配Internet地址,所以它是一個(gè)32比特位的unsigned int(在我們的編譯器上,int是64位)。現(xiàn)在我們存儲(chǔ)進(jìn)32個(gè)1,存儲(chǔ)進(jìn)的值將與unsigned long -1比較。當(dāng)我們存儲(chǔ)32個(gè)1于unsigned int時(shí),編譯器自動(dòng)把32位提升為64位;這樣,數(shù)值0x00000000 ffffffff與0xffffffff ffffffff的比較當(dāng)然就失敗了。這是一個(gè)很難發(fā)現(xiàn)的bug,特別是在這種因?yàn)?2位到64位的隱式提升上。 那我們對(duì)這個(gè)bug怎么辦呢?一個(gè)解決方法是在語(yǔ)句中比較0xffffffff,而不是-1,但這又會(huì)使代碼更加依賴于特定大小的對(duì)象。另一個(gè)方法是,使用一個(gè)中間的unsigned long變量,從而在把結(jié)果存入sockaddr_in前,執(zhí)行比較,但這會(huì)讓程序代碼更復(fù)雜。 真正的問(wèn)題所在是,我們期望一個(gè)unsigned long值與一個(gè)32位量(如Internet地址)相等。Internet地址必須以32位形式進(jìn)行存儲(chǔ),但有些時(shí)候用一個(gè)標(biāo)量,來(lái)訪問(wèn)這個(gè)地址的一部分,是非常方便的。在32位字長(zhǎng)的電腦中,用一個(gè)long數(shù)值(常被當(dāng)作32位)來(lái)訪問(wèn)這個(gè)地址,看上去沒(méi)什么問(wèn)題。讓我們暫時(shí)不想一個(gè)低級(jí)的數(shù)據(jù)項(xiàng)(32位Internet地址)是否與一個(gè)機(jī)器字相等,那么高級(jí)數(shù)據(jù)類型結(jié)構(gòu)in_addr就應(yīng)該被一直使用。因?yàn)閕n_addr中沒(méi)有無(wú)效值,那么應(yīng)有一個(gè)單獨(dú)的狀態(tài)用作返回值。 解決方案是定義一個(gè)新的函數(shù),就像inet_addr那樣,但返回一個(gè)狀態(tài)值,而且接受一個(gè)結(jié)構(gòu)in_addr作為參數(shù),參見(jiàn)例1(b)。因?yàn)楦呒?jí)的數(shù)據(jù)元素可以一直使用,而返回的值是沒(méi)有溢出的,所以這個(gè)代碼是可以跨架構(gòu)移植的,而不管字長(zhǎng)是多少。雖然伯克利發(fā)布了NET2,其中的確定義了一個(gè)新的函數(shù)inet_aton(),但如果試著改變inet_addr()中的代碼,將會(huì)損壞許多程序。 低級(jí)代碼的低級(jí)結(jié)構(gòu) 低級(jí)編程意味著直接操縱物理設(shè)備或者特定協(xié)議的通訊格式,例如,設(shè)備驅(qū)動(dòng)程序經(jīng)常使用非常精確的位模式來(lái)操縱控制寄存器。此外, 網(wǎng)絡(luò)協(xié)議通過(guò)特定的比特位模式傳輸數(shù)據(jù)項(xiàng)時(shí),也必須適當(dāng)?shù)剞D(zhuǎn)譯。 為了操縱物理數(shù)據(jù)項(xiàng),此處的數(shù)據(jù)結(jié)構(gòu)必須準(zhǔn)確地反映被操縱的內(nèi)容。比特位是一個(gè)不錯(cuò)的選擇,因?yàn)樗鼈冋弥付吮忍氐奈粩?shù)及排列。事實(shí)上,正是這種精確度,使比特位相對(duì)于short、int、long,更好地映像了物理結(jié)構(gòu)(short、int、long會(huì)因?yàn)殡娔X的不同而改變,而比特位不會(huì))。 當(dāng)映像一個(gè)物理結(jié)構(gòu)時(shí),是通過(guò)定義格式來(lái)使比特位達(dá)到這種精度的,這就使你必須一直使用一種編碼風(fēng)格來(lái)訪問(wèn)結(jié)構(gòu)。此時(shí)的每個(gè)位域都是命名的,你寫出的代碼可直接訪問(wèn)這些位域。當(dāng)訪問(wèn)物理結(jié)構(gòu)時(shí),有件事可能你并不想去做,那就是使用標(biāo)量數(shù)組(short、int、or long),訪問(wèn)這些數(shù)組的代碼都假定存在一個(gè)特定的比特位寬,當(dāng)移植這些代碼到一臺(tái)使用不同字寬的電腦上時(shí),就可能不正確了。 在我們移植PEX圖像庫(kù)時(shí)遇到的一個(gè)問(wèn)題,就涉及到其映像的協(xié)議消息結(jié)構(gòu)。在某臺(tái)電腦上,如果int整型的長(zhǎng)度與消息中的元素一樣,那例2(a)中的代碼就會(huì)工作得很正常。32位的數(shù)據(jù)元素在32字長(zhǎng)的電腦上沒(méi)有問(wèn)題,拿到64位的克雷計(jì)算機(jī)上,它就出錯(cuò)了。對(duì)例2(b)而言,不單要改變結(jié)構(gòu)定義,還要改變所有涉及到coord數(shù)組的代碼。這樣,擺在我們面前就有兩個(gè)選擇,要么重寫涉及此消息的所有代碼,要么定義一個(gè)低級(jí)的結(jié)構(gòu)和一個(gè)高級(jí)的結(jié)構(gòu),然后用一段特殊的代碼把數(shù)據(jù)從一個(gè)拷貝到另一個(gè)當(dāng)中,不過(guò)我也不期望可以找出每個(gè)對(duì)zcoord = draw_ msg.coord[2]的引用,而且,當(dāng)現(xiàn)在需要移植到一個(gè)新架構(gòu)上時(shí),把所有的代碼都改寫成如例2(c)所示無(wú)疑是一項(xiàng)艱苦的工作。這個(gè)特殊的問(wèn)題是由于忽視字長(zhǎng)的不同而帶來(lái)的,所以不能假設(shè)在可移植的代碼中機(jī)器字長(zhǎng)、short、int、long都是具有同樣的大小。 例2:低級(jí)代碼的低級(jí)結(jié)構(gòu) (a) struct draw_msg { int objectid; int coord[3]; } |
(b) struct draw_msg { int objectid:32; int coord1:32; int coord2:32; int coord3:32; } |
(c) int *iptr, *optr, *limit; int xyz[3];
iptr = draw_msg.coord; limit = draw_msg.coord + sizeof(draw_msg.coord);
optr = xyz; while (iptr < limit) *optr++ = *iptr++; | 結(jié)構(gòu)打包和字對(duì)齊
正是因?yàn)榫幾g器會(huì)對(duì)結(jié)構(gòu)進(jìn)行打包,所以不同計(jì)算機(jī)上字長(zhǎng)的變化,還導(dǎo)致了另一個(gè)問(wèn)題。C編譯器在字(word)的邊界上對(duì)齊字長(zhǎng),當(dāng)具有一個(gè)字長(zhǎng)的數(shù)據(jù)后面緊接著一個(gè)較小的數(shù)據(jù)時(shí),這種方法會(huì)產(chǎn)生內(nèi)存空缺(不過(guò)也有例外,比如說(shuō)當(dāng)有足夠多的小數(shù)據(jù)剛好可以填充一個(gè)字時(shí))。 一些聰明的程序員在聲明聯(lián)合時(shí),往往在其中會(huì)帶有兩個(gè)或更多的結(jié)構(gòu),其中一個(gè)結(jié)構(gòu)剛好填充聯(lián)合,另一個(gè)則可以用來(lái)從不同的角度來(lái)看待這個(gè)聯(lián)合,參見(jiàn)例3(a)。假設(shè)這段代碼是為16位字長(zhǎng)的計(jì)算機(jī)所寫,int為16位,long為32位,那么存取這個(gè)結(jié)構(gòu)的代碼將會(huì)得到正常的映射關(guān)系(如圖1),而例3(b)中的代碼也會(huì)按預(yù)期的那樣工作。可是,如果這段代碼一旦移植到另一臺(tái)具有32位字長(zhǎng)的計(jì)算機(jī)上時(shí),映射關(guān)系就改變了。如果新計(jì)算機(jī)上的編譯器允許你使用16位的int,那么字的對(duì)齊就會(huì)像圖2所示了,或者如果編譯器遵循K&R約定,那么int將會(huì)和一個(gè)字(32比特)一樣長(zhǎng),對(duì)齊就如圖3所示,在任一情況下,這都將導(dǎo)致問(wèn)題。 例3:結(jié)構(gòu)打包和字對(duì)齊 (a) union parse_hdr { struct hdr { char data1; char data2; int data3; int data4; } hdr; struct tkn { int class; long tag; } tkn; } parse_item; |
(b) char *ptr = msgbuf;
parse_item.hdr.data1 = *ptr++; parse_item.hdr.data2 = *ptr++; parse_item.hdr.data3 = (*ptr++ << 8 | *ptr++); parse_item.hdr.data4 = (*ptr++ << 8 | *ptr++);
if (parse.tkn.class >= MIN_TOKEN_CLASS && parse.tkn.class <= MAX_TOKEN_CLASS) { interpret_tag(parse.tkn.tag); } |
在第一個(gè)情況中(圖2),tag域不是像期望的那樣線性拉長(zhǎng),而被填充了一些垃圾。而在第二個(gè)情況中(圖3),無(wú)論是class還是tag域,都不再有意義,兩個(gè)char值因?yàn)楸淮虬梢粋€(gè)int,所以也都不再正確。再次強(qiáng)調(diào),首先不要假設(shè)標(biāo)準(zhǔn)數(shù)據(jù)類型大小一樣,其次還要了解它們是怎樣被映射成其他數(shù)據(jù)類型的,這才是書(shū)寫可移植代碼的最好方法。 機(jī)器尋址特性 幾乎所有的處理器都在字邊界上以字為單位進(jìn)行尋址,而且通常都為此作了一些優(yōu)化。另有一些的處理器允許其他類型的尋址,如以字節(jié)為單位尋址、或在半個(gè)字邊界上以半字為單位尋址,甚至還有一些處理器有輔助硬件允許在奇數(shù)邊界上同時(shí)以字和半字進(jìn)行尋址。 尋址機(jī)制在不同計(jì)算機(jī)上會(huì)有所變化,最快的尋址模式是在字邊界上以字為單位進(jìn)行尋址。其他方式的尋址需要輔助硬件,通常都會(huì)對(duì)內(nèi)存訪問(wèn)增加了一些時(shí)鐘周期。而這些過(guò)多的模式和特殊硬件的支持,是與RISC處理器的設(shè)計(jì)初衷背道而馳的,就拿克雷計(jì)算機(jī)來(lái)說(shuō),就只支持在字邊界上以字為單位進(jìn)行尋址。 在那些不提供多種數(shù)據(jù)類型尋址方式的計(jì)算機(jī)上,編譯器可以提供一些模擬。例如:編譯器可以生成一些指令,當(dāng)讀取一個(gè)字時(shí),通過(guò)移位和屏蔽,來(lái)找到所想要的位置,以此來(lái)模擬在字中的半字尋址,但這會(huì)需要額外的時(shí)鐘周期,并且代碼體積會(huì)更大。 從這點(diǎn)上來(lái)說(shuō),位域的效率是非常低的,在以位域來(lái)取出一個(gè)字時(shí),它們產(chǎn)生的代碼體積最大。當(dāng)你存取同一個(gè)字中的其他位域時(shí),又需要對(duì)包含這個(gè)位域字的內(nèi)存,重新進(jìn)行一遍尋址,這就是典型的以空間換時(shí)間。 當(dāng)在設(shè)計(jì)數(shù)據(jù)結(jié)構(gòu)時(shí),我們總是想用可以保存數(shù)據(jù)的最小數(shù)據(jù)類型,來(lái)達(dá)到節(jié)省空間的目的。我們有時(shí)小氣得經(jīng)常使用char和short,來(lái)存取位特域,這就像是為了節(jié)省一角錢,而花了一元錢。儲(chǔ)存空間上的高效,會(huì)付出在程序速度和體積上隱藏的代價(jià)。 試想你只為一個(gè)緊湊結(jié)構(gòu)分配了一小點(diǎn)的空間,但卻產(chǎn)生了大量的代碼來(lái)存取結(jié)構(gòu)中的域,而且這段代碼還是經(jīng)常執(zhí)行的,那么,會(huì)因?yàn)榉亲謱ぶ罚鴮?dǎo)致代碼運(yùn)行緩慢,而且充斥了大量用于提取域的代碼,程序體積也因此增大。這些額外代碼所占的空間,會(huì)讓你前面為節(jié)省空間所做的努力付之東流。 在高級(jí)數(shù)據(jù)結(jié)構(gòu)中,特定的比特定位已不是必須的了,應(yīng)在所有的域中都使用字(word),而不要操心它們所占用的空間。特別是在程序某些依賴于機(jī)器的部分,應(yīng)該為字準(zhǔn)備一個(gè)typedef,如下: /*在這臺(tái)計(jì)算機(jī)上,int是一個(gè)字長(zhǎng)*/ typedef word int; |
在高級(jí)結(jié)構(gòu)中,對(duì)所有域都使用字有如下好處: a.. 對(duì)其他計(jì)算機(jī)架構(gòu)的可移植性 b.. 編譯器可能生成最快的代碼 c.. 處理器可能最快地訪問(wèn)到所需內(nèi)存 d.. 絕對(duì)沒(méi)有結(jié)構(gòu)對(duì)齊的意外發(fā)生 必須也承認(rèn),在某些時(shí)候,是不能做到全部使用字的。例如,有一個(gè)很大的結(jié)構(gòu),但不會(huì)被經(jīng)常存取,如果使用了數(shù)千個(gè)字的話,體積將會(huì)增大25%,但使用字通常會(huì)節(jié)省空間、提高執(zhí)行速度,而且更具移植性。 以下是我們的結(jié)論: 書(shū)寫跨平臺(tái)移植的代碼,其實(shí)是件簡(jiǎn)單的事情。最基本的規(guī)則是,盡可能地隱藏機(jī)器字長(zhǎng)的細(xì)節(jié),用非常精確的數(shù)據(jù)元素位大小來(lái)映射物理數(shù)據(jù)結(jié)構(gòu)。或者像前面所建議的,為高級(jí)編程使用高級(jí)數(shù)據(jù)結(jié)構(gòu),而低級(jí)編程使用低級(jí)數(shù)據(jù)結(jié)構(gòu),當(dāng)闡明高級(jí)數(shù)據(jù)結(jié)構(gòu)時(shí),對(duì)標(biāo)準(zhǔn)C的標(biāo)量類型,不要作任何假設(shè)
使用宏定義
在C語(yǔ)言中,宏是產(chǎn)生內(nèi)嵌代碼的唯一方法。對(duì)于嵌入式系統(tǒng)而言,為了能達(dá)到性能要求,宏是一種很好的代替函數(shù)的方法。 寫一個(gè)"標(biāo)準(zhǔn)"宏MIN ,這個(gè)宏輸入兩個(gè)參數(shù)并返回較小的一個(gè): 錯(cuò)誤做法: #define MIN(A,B) ( A <= B ? A : B ) |
正確做法: #define MIN(A,B) ((A)<= (B) ? (A) : (B) ) |
對(duì)于宏,我們需要知道三點(diǎn): (1)宏定義"像"函數(shù); (2)宏定義不是函數(shù),因而需要括上所有"參數(shù)"; (3)宏定義可能產(chǎn)生副作用。 下面的代碼: 將被替換為: ( (*p++) <= (b) ?(*p++):(b) ) |
發(fā)生的事情無(wú)法預(yù)料。 因而不要給宏定義傳入有副作用的"參數(shù)"。 使用寄存器變量 當(dāng)對(duì)一個(gè)變量頻繁被讀寫時(shí),需要反復(fù)訪問(wèn)內(nèi)存,從而花費(fèi)大量的存取時(shí)間。為此,C語(yǔ)言提供了一種變量,即寄存器變量。這種變量存放在CPU的寄存器中,使用時(shí),不需要訪問(wèn)內(nèi)存,而直接從寄存器中讀寫,從而提高效率。寄存器變量的說(shuō)明符是register。對(duì)于循環(huán)次數(shù)較多的循環(huán)控制變量及循環(huán)體內(nèi)反復(fù)使用的變量均可定義為寄存器變量,而循環(huán)計(jì)數(shù)是應(yīng)用寄存器變量的最好候選者。 (1) 只有局部自動(dòng)變量和形參才可以定義為寄存器變量。因?yàn)榧拇嫫髯兞繉儆趧?dòng)態(tài)存儲(chǔ)方式,凡需要采用靜態(tài)存儲(chǔ)方式的量都不能定義為寄存器變量,包括:模塊間全局變量、模塊內(nèi)全局變量、局部static變量; (2) register是一個(gè)"建議"型關(guān)鍵字,意指程序建議該變量放在寄存器中,但最終該變量可能因?yàn)闂l件不滿足并未成為寄存器變量,而是被放在了存儲(chǔ)器中,但編譯器中并不報(bào)錯(cuò)(在C++語(yǔ)言中有另一個(gè)"建議"型關(guān)鍵字:inline)。 下面是一個(gè)采用寄存器變量的例子: /* 求1+2+3+….+n的值 */ WORD Addition(BYTE n) { register i,s=0; for(i=1;i<=n;i++) { s=s+i; } return s; } |
本程序循環(huán)n次,i和s都被頻繁使用,因此可定義為寄存器變量。 內(nèi)嵌匯編 程序中對(duì)時(shí)間要求苛刻的部分可以用內(nèi)嵌匯編來(lái)重寫,以帶來(lái)速度上的顯著提高。但是,開(kāi)發(fā)和測(cè)試匯編代碼是一件辛苦的工作,它將花費(fèi)更長(zhǎng)的時(shí)間,因而要慎重選擇要用匯編的部分。 在程序中,存在一個(gè)80-20原則,即20%的程序消耗了80%的運(yùn)行時(shí)間,因而我們要改進(jìn)效率,最主要是考慮改進(jìn)那20%的代碼。 嵌入式C程序中主要使用在線匯編,即在C程序中直接插入_asm{ }內(nèi)嵌匯編語(yǔ)句: /* 把兩個(gè)輸入?yún)?shù)的值相加,結(jié)果存放到另外一個(gè)全局變量中 */ int result; void Add(long a, long *b) { _asm { MOV AX, a MOV BX, b ADD AX, [BX] MOV result, AX } } | 利用硬件特性 首先要明白CPU對(duì)各種存儲(chǔ)器的訪問(wèn)速度,基本上是: CPU內(nèi)部RAM > 外部同步RAM > 外部異步RAM > FLASH/ROM 對(duì)于程序代碼,已經(jīng)被燒錄在FLASH或ROM中,我們可以讓CPU直接從其中讀取代碼執(zhí)行,但通常這不是一個(gè)好辦法,我們最好在系統(tǒng)啟動(dòng)后將FLASH或ROM中的目標(biāo)代碼拷貝入RAM中后再執(zhí)行以提高取指令速度; 對(duì)于UART等設(shè)備,其內(nèi)部有一定容量的接收BUFFER,我們應(yīng)盡量在BUFFER被占滿后再向CPU提出中斷。例如計(jì)算機(jī)終端在向目標(biāo)機(jī)通過(guò)RS-232傳遞數(shù)據(jù)時(shí),不宜設(shè)置UART只接收到一個(gè)BYTE就向CPU提中斷,從而無(wú)謂浪費(fèi)中斷處理時(shí)間; 如果對(duì)某設(shè)備能采取DMA方式讀取,就采用DMA讀取,DMA讀取方式在讀取目標(biāo)中包含的存儲(chǔ)信息較大時(shí)效率較高,其數(shù)據(jù)傳輸?shù)幕締挝皇菈K,而所傳輸?shù)臄?shù)據(jù)是從設(shè)備直接送入內(nèi)存的(或者相反)。DMA方式較之中斷驅(qū)動(dòng)方式,減少了CPU 對(duì)外設(shè)的干預(yù),進(jìn)一步提高了CPU與外設(shè)的并行操作程度。 活用位操作 使用C語(yǔ)言的位操作可以減少除法和取模的運(yùn)算。在計(jì)算機(jī)程序中數(shù)據(jù)的位是可以操作的最小數(shù)據(jù)單位,理論上可以用"位運(yùn)算"來(lái)完成所有的運(yùn)算和操作,因而,靈活的位操作可以有效地提高程序運(yùn)行的效率。舉例如下: /* 方法1 */ int i,j; i = 879 / 16; j = 562 % 32; /* 方法2 */ int i,j; i = 879 >> 4; j = 562 - (562 >> 5 << 5); |
對(duì)于以2的指數(shù)次方為"*"、"/"或"%"因子的數(shù)學(xué)運(yùn)算,轉(zhuǎn)化為移位運(yùn)算"<< >>"通常可以提高算法效率。因?yàn)槌顺\(yùn)算指令周期通常比移位運(yùn)算大。 C語(yǔ)言位運(yùn)算除了可以提高運(yùn)算效率外,在嵌入式系統(tǒng)的編程中,它的另一個(gè)最典型的應(yīng)用,而且十分廣泛地正在被使用著的是位間的與(&)、或(|)、非(~)操作,這跟嵌入式系統(tǒng)的編程特點(diǎn)有很大關(guān)系。我們通常要對(duì)硬件寄存器進(jìn)行位設(shè)置,譬如,我們通過(guò)將AM186ER型80186處理器的中斷屏蔽控制寄存器的第低6位設(shè)置為0(開(kāi)中斷2),最通用的做法是: #define INT_I2_MASK 0x0040 wTemp = inword(INT_MASK); outword(INT_MASK, wTemp &~INT_I2_MASK); |
而將該位設(shè)置為1的做法是: #define INT_I2_MASK 0x0040 wTemp = inword(INT_MASK); outword(INT_MASK, wTemp | INT_I2_MASK); |
判斷該位是否為1的做法是: #define INT_I2_MASK 0x0040 wTemp = inword(INT_MASK); if(wTemp & INT_I2_MASK) { … /* 該位為1 */ } |
上述方法在嵌入式系統(tǒng)的編程中是非常常見(jiàn)的,我們需要牢固掌握。 總結(jié) 在性能優(yōu)化方面永遠(yuǎn)注意80-20準(zhǔn)備,不要優(yōu)化程序中開(kāi)銷不大的那80%,這是勞而無(wú)功的。 宏定義是C語(yǔ)言中實(shí)現(xiàn)類似函數(shù)功能而又不具函數(shù)調(diào)用和返回開(kāi)銷的較好方法,但宏在本質(zhì)上不是函數(shù),因而要防止宏展開(kāi)后出現(xiàn)不可預(yù)料的結(jié)果,對(duì)宏的定義和使用要慎而處之。很遺憾,標(biāo)準(zhǔn)C至今沒(méi)有包括C++中inline函數(shù)的功能,inline函數(shù)兼具無(wú)調(diào)用開(kāi)銷和安全的優(yōu)點(diǎn)。 使用寄存器變量、內(nèi)嵌匯編和活用位操作也是提高程序效率的有效方法。 除了編程上的技巧外,為提高系統(tǒng)的運(yùn)行效率,我們通常也需要最大可能地利用各種硬件設(shè)備自身的特點(diǎn)來(lái)減小其運(yùn)轉(zhuǎn)開(kāi)銷,例如減小中斷次數(shù)、利用DMA傳輸方式等。
Posted on 2006-06-24 16:55 Gin 閱讀(125) 評(píng)論(0)? 編輯 收藏 摘自網(wǎng)上的,呵,這幾個(gè)是自己比較少用到的
F2 ??? 當(dāng)你選中一個(gè)文件的話,這意味著“重命名” ALT+ ENTER或 ALT+雙擊????? 查看項(xiàng)目的屬性 F10或ALT?????????????????????????????????? 激活當(dāng)前程序的菜單欄 CTRL+ ESC??????????????????????????????? 顯示“開(kāi)始”菜單 ALT+空格鍵?????????????????????????????? 顯示當(dāng)前窗口的系統(tǒng)菜單 CTRL+F5 ????? 強(qiáng)行刷新 ALT+RIGHT ARROW 顯示前一頁(yè)(前進(jìn)鍵) ALT+LEFT ARROW 顯示后一頁(yè)(后退鍵) ALT+ESC 切換當(dāng)前程序 CTRL+N 新建一個(gè)新的文件 CTRL+O 打開(kāi)“打開(kāi)文件”對(duì)話框 CTRL+P 打開(kāi)“打印”對(duì)話框
SHIFT+F10??????????????????????????? 顯示某個(gè)鏈接的快捷菜單 CTRL+W?????????????????????????????? 關(guān)閉當(dāng)前窗口 ALT+D????????????????????????????????? 選擇地址欄中的文字 CTRL+ENTER???????????????????? 在地址欄中將"www."添加到鍵入的文本的前面,將".com"添加到文本的后面 CTRL+D??????????????????????????????? 將當(dāng)前Web 頁(yè)添加到收藏夾中 CTRL+B?????????????????????????????? 打開(kāi)"整理收藏夾"對(duì)話框 CTRL+I?????????????????????????????? 在瀏覽欄中打開(kāi)收藏夾 ALT+ENTER 將 Windows 下運(yùn)行的命令行窗口在窗口和全屏幕狀態(tài)間切換;查看選定的文件的屬性;選定任務(wù)欄時(shí)打開(kāi)"任務(wù)欄和開(kāi)始菜單"屬性 Alt+空格→X?????????????????????? 最大化當(dāng)前窗口 Alt+空格→N?????????????????????? 最小化當(dāng)前窗口
在用C++寫要導(dǎo)出類的庫(kù)時(shí),我們經(jīng)常只想暴露接口,而隱藏類的實(shí)現(xiàn)細(xì)節(jié)。也就是說(shuō)我們提供的頭文件里只提供要暴露的公共成員函數(shù)的聲明,類的其他所有信息都不會(huì)在這個(gè)頭文件里面顯示出來(lái)。這個(gè)時(shí)候就要用到接口與實(shí)現(xiàn)分離的技術(shù)。 下面用一個(gè)最簡(jiǎn)單的例子來(lái)說(shuō)明。 類ClxExp是我們要導(dǎo)出的類,其中有一個(gè)私有成員變量是ClxTest類的對(duì)象,各個(gè)文件內(nèi)容如下: lxTest.h文件內(nèi)容: class ClxTest { public: ClxTest(); virtual ~ClxTest(); void DoSomething(); }; |
lxTest.cpp文件內(nèi)容: #include "lxTest.h"
#include <iostream> using namespace std;
ClxTest::ClxTest() {}
ClxTest::~ClxTest() {}
void ClxTest::DoSomething() { cout << "Do something in class ClxTest!" << endl; }
//////////////////////////////////////////////////////////////////////////// |
lxExp.h文件內(nèi)容: #include "lxTest.h"
class ClxExp { public: ClxExp(); virtual ~ClxExp(); void DoSomething(); private: ClxTest m_lxTest; void lxTest(); }; |
lxExp.cpp文件內(nèi)容: #include "lxExp.h"
ClxExp::ClxExp() {}
ClxExp::~ClxExp() {}
// 其實(shí)該方法在這里并沒(méi)有必要,我這樣只是為了說(shuō)明調(diào)用關(guān)系 void ClxExp::lxTest() { m_lxTest.DoSomething(); }
void ClxExp::DoSomething() { lxTest(); } |
為了讓用戶能使用我們的類ClxExp,我們必須提供lxExp.h文件,這樣類ClxExp的私有成員也暴露給用戶了。而且,僅僅提供lxExp.h文件是不夠的,因?yàn)閘xExp.h文件include了lxTest.h文件,在這種情況下,我們還要提供lxTest.h文件。那樣ClxExp類的實(shí)現(xiàn)細(xì)節(jié)就全暴露給用戶了。另外,當(dāng)我們對(duì)類ClxTest做了修改(如添加或刪除一些成員變量或方法)時(shí),我們還要給用戶更新lxTest.h文件,而這個(gè)文件是跟接口無(wú)關(guān)的。如果類ClxExp里面有很多像m_lxTest那樣的對(duì)象的話,我們就要給用戶提供N個(gè)像lxTest.h那樣的頭文件,而且其中任何一個(gè)類有改動(dòng),我們都要給用戶更新頭文件。還有一點(diǎn)就是用戶在這種情況下必須進(jìn)行重新編譯! 上面是非常小的一個(gè)例子,重新編譯的時(shí)間可以忽略不計(jì)。但是,如果類ClxExp被用戶大量使用的話,那么在一個(gè)大項(xiàng)目中,重新編譯的時(shí)候我們就有時(shí)間可以去喝杯咖啡什么的了。當(dāng)然上面的種種情況不是我們想看到的!你也可以想像一下用戶在自己程序不用改動(dòng)的情況下要不停的更新頭文件和編譯時(shí),他們心里會(huì)罵些什么。其實(shí)對(duì)用戶來(lái)說(shuō),他們只關(guān)心類ClxExp的接口DoSomething()方法。那我們?cè)趺床拍苤槐┞额怌lxExp的DoSomething()方法而不又產(chǎn)生上面所說(shuō)的那些問(wèn)題呢?答案就是--接口與實(shí)現(xiàn)的分離。我可以讓類ClxExp定義接口,而把實(shí)現(xiàn)放在另外一個(gè)類里面。下面是具體的方法: 首先,添加一個(gè)實(shí)現(xiàn)類ClxImplement來(lái)實(shí)現(xiàn)ClxExp的所有功能。注意:類ClxImplement有著跟類ClxExp一樣的公有成員函數(shù),因?yàn)樗麄兊慕涌谝耆恢隆?br /> lxImplement.h文件內(nèi)容: #include "lxTest.h"
class ClxImplement { public: ClxImplement(); virtual ~ClxImplement();
void DoSomething(); private: ClxTest m_lxTest; void lxTest(); }; |
lxImplement.cpp文件內(nèi)容: #include "lxImplement.h"
ClxImplement::ClxImplement() {}
ClxImplement::~ClxImplement() {}
void ClxImplement::lxTest() { m_lxTest.DoSomething(); }
void ClxImplement::DoSomething() { lxTest(); } |
然后,修改類ClxExp。 修改后的lxExp.h文件內(nèi)容: // 前置聲明 class ClxImplement;
class ClxExp { public: ClxExp(); virtual ~ClxExp(); void DoSomething(); private: // 聲明一個(gè)類ClxImplement的指針,不需要知道類ClxImplement的定義 ClxImplement *m_pImpl; }; |
修改后的lxExp.cpp文件內(nèi)容: // 在這里包含類ClxImplement的定義頭文件 #include "lxImplement.h"
ClxExp::ClxExp() { m_pImpl = new ClxImplement; }
ClxExp::~ClxExp() { delete m_pImpl; }
void ClxExp::DoSomething() { m_pImpl->DoSomething(); } |
通過(guò)上面的方法就實(shí)現(xiàn)了類ClxExp的接口與實(shí)現(xiàn)的分離。請(qǐng)注意兩個(gè)文件中的注釋。類ClxExp里面聲明的只是接口而已,而真正的實(shí)現(xiàn)細(xì)節(jié)被隱藏到了類ClxImplement里面。為了能在類ClxExp中使用類ClxImplement而不include頭文件lxImplement.h,就必須有前置聲明class ClxImplement,而且只能使用指向類ClxImplement對(duì)象的指針,否則就不能通過(guò)編譯。 在發(fā)布庫(kù)文件的時(shí)候,我們只需給用戶提供一個(gè)頭文件lxExp.h就行了,不會(huì)暴露類ClxExp的任何實(shí)現(xiàn)細(xì)節(jié)。而且我們對(duì)類ClxTest的任何改動(dòng),都不需要再給用戶更新頭文件(當(dāng)然,庫(kù)文件是要更新的,但是這種情況下用戶也不用重新編譯!)。這樣做還有一個(gè)好處就是,可以在分析階段由系統(tǒng)分析員或者高級(jí)程序員來(lái)先把類的接口定義好,甚至可以把接口代碼寫好(例如上面修改后的lxExp.h文件和lxExp.cpp文件),而把類的具體實(shí)現(xiàn)交給其他程序員開(kāi)發(fā)。
前些日子開(kāi)始看《C++ Primer》,順便做一些筆記,既有書(shū)上的,也有自己理解的。 因?yàn)閯倢W(xué)C++不久,筆下難免有謬誤之處,行文更是凌亂; 所幸不是用來(lái)顯配的東西,發(fā)在linuxsir只是為了方便自己閱讀記憶,以防只顧上網(wǎng)忘了正事。 書(shū)看了不到一半,所以大約才寫了一半,慢慢補(bǔ)充。 ========================================= ========================================== 轉(zhuǎn)載務(wù)必注明原作者 neplusultra 2005.2.3 ========================================== const要注意的問(wèn)題 1、下面是一個(gè)幾乎所有人剛開(kāi)始都會(huì)搞錯(cuò)的問(wèn)題: 已知:typedef char *cstring; 在以下聲明中,cstr的類型是什么? extern const cstring cstr; 錯(cuò)誤答案:const char *cstr; 正確答案:char *const cstr; 錯(cuò)誤在于將typedef當(dāng)作宏擴(kuò)展。const 修飾cstr的類型。cstr是一個(gè)指針,因此,這個(gè)定義聲明了cstr是一個(gè)指向字符的const指針。 2、指針是const還是data為const? 辨別方法很簡(jiǎn)單,如下: 代碼: char *p="hello"; //non-const pointer, non-const data;
const char *p="hello"; // non-const pointer, const data;
char * const p="hello"; // const pointer , non-const data;
const char * const p="hello"; // const pointer, const data;
要注意的是,"hello"的類型是const char * ,按C++standard規(guī)則,char *p="hello" 是非法的(右式的const char* 不能轉(zhuǎn)換為左式的char *),違反了常量性。但是這種行為在C中實(shí)在太頻繁,因此C++standard對(duì)于這種初始化動(dòng)作給予豁免。盡管如此,還是盡量避免這種用法。 3、const初始化的一些問(wèn)題 const 對(duì)象必須被初始化: 代碼: const int *pi=new int; // 錯(cuò)誤,沒(méi)有初始化
const int *pi=new int(100); //正確
const int *pci=new const int[100]; //編譯錯(cuò)誤,無(wú)法初始化用new表達(dá)式創(chuàng)建的內(nèi)置類型數(shù)組元素。
什么時(shí)候需要copy constructor,copy assignment operator,destructor 注意,若class需要三者之一,那么它往往需要三者。 當(dāng)class的copy constructor內(nèi)分配有一塊指向hcap的內(nèi)存,需要由destructor釋放,那么它也往往需要三者。 為什么需要protected 訪問(wèn)級(jí)別 有人認(rèn)為,protected訪問(wèn)級(jí)別允許派生類直接訪問(wèn)基類成員,這破壞了封裝的概念,因此所有基類的實(shí)現(xiàn)細(xì)節(jié)都應(yīng)該是private的;另外一些人認(rèn)為,如果派生類不能直接訪問(wèn)基類的成員,那么派生類的實(shí)現(xiàn)將無(wú)法有足夠的效率供用戶使用,如果沒(méi)有protected,類的設(shè)計(jì)者將被迫把基類成員設(shè)置為public。 事實(shí)上,protected正是在高純度的封裝與效率之間做出的一個(gè)良好折衷方案。 為什么需要virtual member function又不能濫用virtual 若基類設(shè)計(jì)者把本應(yīng)設(shè)計(jì)成virtual的成員函數(shù)設(shè)計(jì)成非virtual,則繼承類將無(wú)法實(shí)現(xiàn)改寫(overridden),給繼承類的實(shí)現(xiàn)帶來(lái)不便; 另一方面,一旦成員函數(shù)被設(shè)計(jì)成virtual,則該類的對(duì)象將額外增加虛擬指針(vptr)和虛擬表格(vtbl),所以倘若出于方便繼承類overridden的目的而使所有成員函數(shù)都為virtual,可能會(huì)影響效率,因?yàn)槊總€(gè)virtual成員函數(shù)都需付出動(dòng)態(tài)分派的成本。而且virtual成員函數(shù)不能內(nèi)聯(lián)(inline),我們知道,內(nèi)聯(lián)發(fā)生在編譯時(shí)刻,而虛擬函數(shù)在運(yùn)行時(shí)刻才處理。對(duì)于那些小巧而被頻繁調(diào)用、與類型無(wú)關(guān)的函數(shù),顯然不應(yīng)該被設(shè)置成virtual。 關(guān)于引用的一些注意點(diǎn) 1、把函數(shù)參數(shù)聲明為數(shù)組的引用:當(dāng)函數(shù)參數(shù)是一個(gè)數(shù)組類型的引用時(shí),數(shù)組長(zhǎng)度成為參數(shù)和實(shí)參類型的一部分,編譯器檢查數(shù)組實(shí)參的長(zhǎng)度和與在函數(shù)參數(shù)類型中指定的長(zhǎng)度是否匹配。 代碼: //參數(shù)為10個(gè)int數(shù)組
void showarr(int (&arr)[10]);
void func()
{
int i,j[2],k[10];
showarr(i); //錯(cuò)誤!實(shí)參必須是10個(gè)int的數(shù)組
showarr(j); //錯(cuò)誤!實(shí)參必須是10個(gè)int的數(shù)組
showarr(k); //正確!
}
//更靈活的實(shí)現(xiàn),借助函數(shù)模板。下面是一個(gè)顯示數(shù)組內(nèi)容的函數(shù)。
template <typename Type , int size>
void printarr(const Type (& r_array)[size])
{
for(int i=0;i<size;i++) std::cout<< r_array[i] <<' ';
std::cout << std::endl;
}
void caller()
{
int ar[5]={1,2,5,3,4}; //數(shù)組可以任意大小。
printarr(ar); //正確!自動(dòng)正確調(diào)用printarr()
}
2、 3、 goto語(yǔ)句的一些要注意的地方 1、label語(yǔ)句只能用作goto的目標(biāo),且label語(yǔ)句只能用冒號(hào)結(jié)束,且label語(yǔ)句后面不能緊接右花括號(hào)'}',如 辦法是在冒號(hào)后面加一個(gè)空語(yǔ)句(一個(gè)';'即可),如 2、goto語(yǔ)句不能向前跳過(guò)如下聲明語(yǔ)句: 代碼: goto label6;
int x=1; //錯(cuò)誤,不能跳過(guò)該聲明!
cout<<x<<endl; //使用x
label6:
//其他語(yǔ)句
但是,把int x=1; 改為int x; 則正確了。另外一種方法是: 代碼: goto label6;
{
int x=1; //正確,使用了語(yǔ)句快
cout<<x<<endl;
}
label6:
//其他語(yǔ)句
3、goto語(yǔ)句可以向后(向程序開(kāi)頭的方向)跳過(guò)聲明定義語(yǔ)句。 代碼: begin:
int i=22;
cout<< i <<endl;
goto begin; //非常蹩腳,但它是正確的
變量作用域 1、花括號(hào)可以用來(lái)指明局部作用域。 2、在for、if、switch、while語(yǔ)句的條件/循環(huán)條件中可以聲明變量,該變量?jī)H在相應(yīng)語(yǔ)句塊內(nèi)有效。 3、extern為聲明但不定義一個(gè)對(duì)象提供了一種方法;它類似于函數(shù)聲明,指明該對(duì)象會(huì)在其他地方被定義:或者在此文本的其他地方,或者在程序的其他文本文件中。例如extern int i; 表示在其他地方存在聲明 int i; extern 聲明不會(huì)引起內(nèi)存分配,他可以在同一個(gè)文件或同一個(gè)程序中出現(xiàn)多次。因此在全局作用域中,以下語(yǔ)句是正確的: 代碼: extern int c;
int c=1; //沒(méi)錯(cuò)
extern int c; //沒(méi)錯(cuò)
但是,extern聲明若指定了一個(gè)顯式初始值的全局對(duì)象,將被視為對(duì)該對(duì)象的定義,編譯器將為其分配存儲(chǔ)區(qū);對(duì)該對(duì)象的后續(xù)定義將出錯(cuò)。如下: 代碼: extern int i=1;
int i=2; //出錯(cuò)!重復(fù)定義
auto_ptr若干注意點(diǎn) 1、auto_ptr的主要目的是支持普通指針類型相同的語(yǔ)法,并為auto_ptr所指對(duì)象的釋放提供自動(dòng)管理,而且auto_ptr的安全性幾乎不會(huì)帶來(lái)額外的代價(jià)(因?yàn)槠洳僮髦С侄际莾?nèi)聯(lián)的)。定義形式有三種: 代碼: auto_ptr<type_pointed_to>identifier(ptr_allocated_by_new);
auto_ptr<type_pointed_to>identifier(auto_ptr_of_same_type);
auto_ptr<type_pointed_to>identifier;
2、所有權(quán)概念。auto_ptr_p1=auto_ptr_p2的后果是,auto_ptr_p2喪失了其原指向?qū)ο蟮乃袡?quán),并且auto_ptr_p2.get()==0。不要讓兩個(gè)auto_ptr對(duì)象擁有空閑存儲(chǔ)區(qū)內(nèi)同一對(duì)象的所有權(quán)。注意以下兩種種初始化方式的區(qū)別: 代碼: auto_ptr<string>auto_ptr_str1(auto_ptr_str2.get()); //注意!用str2指針初始化str1, 兩者同時(shí)擁有所有權(quán),后果未定義。
auto_ptr<string>auto_ptr_str1(auto_ptr_str2.release());//OK!str2釋放了所有權(quán)。
3、不能用一個(gè)指向“內(nèi)存不是通過(guò)應(yīng)用new表達(dá)式分配的”指針來(lái)初始化或者賦值auto_ptr。如果這樣做了,delete表達(dá)式會(huì)被應(yīng)用在不是動(dòng)態(tài)分配的指針上,這將導(dǎo)致未定義的程序行為。 C風(fēng)格字符串結(jié)尾空字符問(wèn)題代碼: char *str="hello world!"; //str末尾自動(dòng)加上一個(gè)結(jié)尾空字符,但strlen不計(jì)該空字符。
char *str2=new char[strlen(str)+1] // +1用來(lái)存放結(jié)尾空字符。
定位new表達(dá)式 頭文件:<new> 形式:new (place_address) type-specifier 該語(yǔ)句可以允許程序員將對(duì)象創(chuàng)建在已經(jīng)分配好的內(nèi)存中,允許程序員預(yù)分配大量的內(nèi)存供以后通過(guò)這種形式的new表達(dá)式創(chuàng)建對(duì)象。其中place_address必須是一個(gè)指針。例如: 代碼: char *buf=new char[sizeof(myclass-type)*16];
myclass-type *pb=new (buf) myclass-type; //使用預(yù)分配空間來(lái)創(chuàng)建對(duì)象
// ...
delete [] buf; // 無(wú)須 delete pb。
名字空間namespace
1、namespace的定義可以是不連續(xù)的(即namespace的定義是可以積累的),即,同一個(gè)namespace可以在不同的文件中定義,分散在不同文件中的同一個(gè)namespace中的內(nèi)容彼此可見(jiàn)。這對(duì)生成一個(gè)庫(kù)很有幫助,可以使我們更容易將庫(kù)的源代碼組織成接口和實(shí)現(xiàn)部分。如:在頭文件(.h文件)的名字空間部分定義庫(kù)接口;在實(shí)現(xiàn)文件(如.c或.cpp文件)的名字空間部分定義庫(kù)實(shí)現(xiàn)。名字空間定義可積累的特性是“向用戶隱藏實(shí)現(xiàn)細(xì)節(jié)”必需的,它允許把不同的實(shí)現(xiàn)文件(如.c或.cpp文件)編譯鏈接到一個(gè)程序中,而不會(huì)有編譯錯(cuò)誤和鏈接錯(cuò)誤。 2、全局名字空間成員,可以用“::member_name”的方式引用。當(dāng)全局名字空間的成員被嵌套的局部域中聲明的名字隱藏時(shí),就可以采用這種方法引用全局名字空間成員。 3、名字空間成員可以被定義在名字空間之外。但是,只有包圍該成員聲明的名字空間(也就是該成員聲明所在的名字空間及其 外圍名字空間)才可以包含它的定義。 尤其要注意的是#include語(yǔ)句的次序。假定名字空間成員mynamespace::member_i的聲明在文件dec.h中,且#include "dec.h"語(yǔ)句置于 全局名字空間,那么在include語(yǔ)句之后定義的其他名字空間內(nèi),mynamespace::member_i的聲明均可見(jiàn)。即,mynamespace::member_i可以在#include "dec.h"之后的任何地方任何名字空間內(nèi)定義。 4、未命名的名字空間。我們可以用未命名的名字空間聲明一個(gè)局部于某一文件的實(shí)體。未命名的名字空間可以namespace開(kāi)頭,其后不需名字,而用一對(duì)花括號(hào)包含名字空間聲明塊。如: 代碼: // 其他代碼略
namespace
{
void mesg()
{
cout<<"**********\n";
}
}
int main()
{
mesg(); //正確
//...
return 0;
}
由于未命名名字空間的成員是程序?qū)嶓w,所以mesg()可以在程序整個(gè)執(zhí)行期間被調(diào)用。但是,未命名名字空間成員只在特定的文件中可見(jiàn),在構(gòu)成程序的其他文件中是不可以見(jiàn)的。未命名名字空間的成員與被聲明為static的全局實(shí)體具有類似的特性。在C中,被聲明為static的全局實(shí)體在聲明它的文件之外是不可見(jiàn)的。 using關(guān)鍵字 1、using聲明與using指示符:前者是聲明某名字空間內(nèi)的一個(gè)成員,后者是使用整個(gè)名字空間。例如: 代碼: using cpp_primer::matrix; // ok,using聲明
using namespace cpp_primer; //ok,using指示符
2、 該using指示符語(yǔ)句可以加在程序文件的幾乎任何地方,包括文件開(kāi)頭(#include語(yǔ)句之前)、函數(shù)內(nèi)部。不過(guò)用using指定的名字空間作用域(生命周期)受using語(yǔ)句所在位置的生命周期約束。如,函數(shù)內(nèi)部使用“using namespace myspacename;”則 myspacename僅在該函數(shù)內(nèi)部可見(jiàn)。 3、可以用using語(yǔ)句指定多個(gè)名字空間,使得多個(gè)名字空間同時(shí)可見(jiàn)。但這增加了名字污染的可能性,而且只有在 使用各名字空間相同成員時(shí)由多個(gè)using指示符引起的二義性錯(cuò)誤才能被檢測(cè)到,這將給程序的檢測(cè)、擴(kuò)展、移植帶來(lái)很大的隱患。因此,因該盡量使用using聲明而不是濫用using指示符。 重載函數(shù) 1、如果兩個(gè)函數(shù)的參數(shù)表中參數(shù)的個(gè)數(shù)或者類型不同,則認(rèn)為這兩個(gè)函數(shù)是重載的。 如果兩個(gè)函數(shù)的返回類型和參數(shù)表精確匹配,則第二個(gè)聲明被視為第一個(gè)的重復(fù)聲明,與參數(shù)名無(wú)關(guān)。如 void print(string& str)與void print(string&)是一樣的。 如果兩個(gè)函數(shù)的參數(shù)表相同,但是返回類型不同,則第二個(gè)聲明被視為第一個(gè)的錯(cuò)誤重復(fù)聲明,會(huì)標(biāo)記為編譯錯(cuò)誤。 如果在兩個(gè)函數(shù)的參數(shù)表中,只有缺省實(shí)參不同,則第二個(gè)聲明被視為第一個(gè)的重復(fù)聲明。如int max(int *ia,int sz)與int max(int *, int=10)。 參數(shù)名類型如果是由typedef提供的,并不算作新類型,而應(yīng)該當(dāng)作typedef的原類型。 當(dāng)參數(shù)類型是const或者volatile時(shí),分兩種情況:對(duì)于實(shí)參按值傳遞時(shí),const、volatile修飾符可以忽略;對(duì)于把const、volatile應(yīng)用在指針或者引用參數(shù)指向的類型時(shí),const、volatile修飾符對(duì)于重載函數(shù)的聲明是有作用的。例如: 代碼: //OK,以下兩個(gè)聲明其實(shí)一樣
void func(int i);
void func(const int i);
//Error,無(wú)法通過(guò)編譯,因?yàn)閒unc函數(shù)被定義了兩次。
void func(int i){}
void func(const int i){}
//OK,聲明了不同的函數(shù)
void func2(int *);
void func2(const int *);
//OK,聲明了不同的函數(shù)
void func3(int&);
void func3(const int&);
2、鏈接指示符extern "C"只能指定重載函數(shù)集中的一個(gè)函數(shù)。原因與內(nèi)部名編碼有關(guān),在大多數(shù)編譯器內(nèi)部,每個(gè)函數(shù)明及其相關(guān)參數(shù)表都被作為一個(gè)惟一的內(nèi)部名編碼,一般的做法是把參數(shù)的個(gè)數(shù)和類型都進(jìn)行編碼,然后將其附在函數(shù)名后面。但是這種編碼不使用于用鏈接指示符extern "C"聲明的函數(shù),這就是為什么在重載函數(shù)集合中只有一個(gè)函數(shù)可以被聲明為extern "C"的原因,具有不同的參數(shù)表的兩個(gè)extern "C"的函數(shù)會(huì)被鏈接編輯器視為同一函數(shù)。例如,包含以下兩個(gè)聲明的程序是非法的。 代碼: //error:一個(gè)重載函數(shù)集中有兩個(gè)extern "C"函數(shù)
extern "C" void print(const char*);
extern "C" void print(int);
函數(shù)模板 1、定義函數(shù)模板: 代碼: template <typename/class identifier, ...>
[inline/extern]
ReturnType FunctionName(FuncParameters...)
{
//definition of a funciton template...
}
?
條款28: 劃分全局名字空間全局空間最大的問(wèn)題在于它本身僅有一個(gè)。在大的軟件項(xiàng)目中,經(jīng)常會(huì)有不少人把他們定義的名字都放在這個(gè)單一的空間中,從而不可避免地導(dǎo)致名字沖突。例如,假設(shè)library1.h定義了一些常量,其中包括: const double lib_version = 1.204; 類似的,library2.h也定義了: const int lib_version = 3; 很顯然,如果某個(gè)程序想同時(shí)包含library1.h和library2.h就會(huì)有問(wèn)題。對(duì)于這類問(wèn)題,你除了嘴里罵幾句,或給作者發(fā)報(bào)復(fù)性郵件,或自己編輯頭文件來(lái)消除名字沖突外,也沒(méi)其它什么辦法。 但是,作為程序員,你可以盡力使自己寫的程序庫(kù)不給別人帶來(lái)這些問(wèn)題。例如,可以預(yù)先想一些不大可能造成沖突的某種前綴,加在每個(gè)全局符號(hào)前。當(dāng)然得承認(rèn),這樣組合起來(lái)的標(biāo)識(shí)符看起來(lái)不是那么令人舒服。 另一個(gè)比較好的方法是使用c++ namespace。namespace本質(zhì)上和使用前綴的方法一樣,只不過(guò)避免了別人總是看到前綴而已。所以,不要這么做: const double sdmbook_version = 2.0;????? // 在這個(gè)程序庫(kù)中, ???????????????????????????????????????? // 每個(gè)符號(hào)以"sdm"開(kāi)頭 class sdmhandle { ... };???????????????? sdmhandle& sdmgethandle();???????????? // 為什么函數(shù)要這樣聲明? ?????????????????????????????????????? // 參見(jiàn)條款47 而要這么做: namespace sdm { ? const double book_version = 2.0; ? class handle { ... }; ? handle& gethandle(); } 用戶于是可以通過(guò)三種方法來(lái)訪問(wèn)這一名字空間里的符號(hào):將名字空間中的所有符號(hào)全部引入到某一用戶空間;將部分符號(hào)引入到某一用戶空間;或通過(guò)修飾符顯式地一次性使用某個(gè)符號(hào): void f1() { ? using namespace sdm;?????????? // 使得sdm中的所有符號(hào)不用加 ???????????????????????????????? // 修飾符就可以使用 ? cout << book_version;????????? // 解釋為sdm::book_version ? ... ? handle h = gethandle();??????? // handle解釋為sdm::handle, ???????????????????????????????? // gethandle解釋為sdm::gethandle ? ...??????????????????????????? } void f2() { ? using sdm::book_version;??????? // 使得僅book_version不用加 ???????????????????????????????? // 修飾符就可以使用 ? cout << book_version;?????????? // 解釋為 ????????????????????????????????? // sdm::book_version ? ... ? handle h = gethandle();???????? // 錯(cuò)誤! handle和gethandle ????????????????????????????????? // 都沒(méi)有引入到本空間 ? ...???????????????????????????? } void f3() { ? cout << sdm::book_version;????? // 使得book_version ????????????????????????????????? // 在本語(yǔ)句有效 ? ...???????????????????????????? ? double d = book_version;??????? // 錯(cuò)誤! book_version ????????????????????????????????? // 不在本空間 ? handle h = gethandle();???????? // 錯(cuò)誤! handle和gethandle ????????????????????????????????? // 都沒(méi)有引入到本空間 ? ...??????????????????????????? } (有些名字空間沒(méi)有名字。這種沒(méi)命名的名字空間一般用于限制名字空間內(nèi)部元素的可見(jiàn)性。詳見(jiàn)條款m31。) 名字空間帶來(lái)的最大的好處之一在于:潛在的二義不會(huì)造成錯(cuò)誤(參見(jiàn)條款26)。所以,從多個(gè)不同的名字空間引入同一個(gè)符號(hào)名不會(huì)造成沖突(假如確實(shí)真的從不使用這個(gè)符號(hào)的話)。例如,除了名字空間sdm外,假如還要用到下面這個(gè)名字空間: namespace acmewindowsystem { ? ... ? typedef int handle; ? ... } 只要不引用符號(hào)handle,使用sdm和acmewindowsystem時(shí)就不會(huì)有沖突。假如真的要引用,可以明確地指明是哪個(gè)名字空間的handle: void f() { ? using namespace sdm;???????????????? // 引入sdm里的所有符號(hào) ? using namespace acmewindowsystem;??? // 引入acme里的所有符號(hào) ? ...????????????????????????????????? // 自由地引用sdm ?????????????????????????????????????? // 和acme里除handle之外 ?????????????????????????????????????? // 的其它符號(hào) ? handle h;??????????????????????????? // 錯(cuò)誤! 哪個(gè)handle? ? sdm::handle h1;????????????????????? // 正確, 沒(méi)有二義 ? acmewindowsystem::handle h2;???????? // 也沒(méi)有二義 ? ... } 假如用常規(guī)的基于頭文件的方法來(lái)做,只是簡(jiǎn)單地包含sdm.h和acme.h,這樣的話,由于handle有多個(gè)定義,編譯將不能通過(guò)。 名字空間的概念加入到c++標(biāo)準(zhǔn)的時(shí)間相對(duì)較晚,所以有些人會(huì)認(rèn)為它不太重要,可有可無(wú)。但這種想法是錯(cuò)誤的,因?yàn)閏++標(biāo)準(zhǔn)庫(kù)(參見(jiàn)條款49)里幾乎所有的東西都存在于名字空間std之中。這可能令你不以為然,但它卻以一種直接的方式影響到你:這就是為什么c++提供了那些看起來(lái)很有趣的、沒(méi)有擴(kuò)展名的頭文件,如<iostream>, <string>等。詳細(xì)介紹參見(jiàn)條款49。 由于名字空間的概念引入的時(shí)間相對(duì)較晚,有些編譯器可能不支持。就算是這樣,那也沒(méi)理由污染全局名字空間,因?yàn)榭梢杂胹truct來(lái)近似實(shí)現(xiàn)namespace。可以這樣做:先創(chuàng)建一個(gè)結(jié)構(gòu)用以保存全局符號(hào)名,然后將這些全局符號(hào)名作為靜態(tài)成員放入結(jié)構(gòu)中: // 用于模擬名字空間的一個(gè)結(jié)構(gòu)的定義 struct sdm { ? static const double book_version; ? class handle { ... }; ? static handle& gethandle(); }; const double sdm::book_version = 2.0;????? // 靜態(tài)成員的定義 現(xiàn)在,如果有人想訪問(wèn)這些全局符號(hào)名,只用簡(jiǎn)單地在它們前面加上結(jié)構(gòu)名作為前綴: void f() { ? cout << sdm::book_version; ? ... ? sdm::handle h = sdm::gethandle(); ? ... } 但是,如果全局范圍內(nèi)實(shí)際上沒(méi)有名字沖突,用戶就會(huì)覺(jué)得加修飾符麻煩而多余。幸運(yùn)的是,還是有辦法來(lái)讓用戶選擇使用它們或忽略它們。 對(duì)于類型名,可以用類型定義(typedef)來(lái)顯式地去掉空間引用。例如,假設(shè)結(jié)構(gòu)s(模擬的名字空間)內(nèi)有個(gè)類型名t,可以這樣用typedef來(lái)使得t成為s::t的同義詞: typedef sdm::handle handle; 對(duì)于結(jié)構(gòu)中的每個(gè)(靜態(tài))對(duì)象x,可以提供一個(gè)(全局)引用x,并初始化為s::x: const double& book_version = sdm::book_version; 老實(shí)說(shuō),如果讀了條款47,你就會(huì)不喜歡定義一個(gè)象book_version這樣的非局部靜態(tài)對(duì)象。(你就會(huì)用條款47中所介紹的函數(shù)來(lái)取代這樣的對(duì)象) 處理函數(shù)的方法和處理對(duì)象一樣,但要注意,即使定義函數(shù)的引用是合法的,但代碼的維護(hù)者會(huì)更喜歡你使用函數(shù)指針: sdm::handle& (* const gethandle)() =????? // gethandle是指向sdm::gethandle ? sdm::gethandle;???????????????????????? // 的const 指針 (見(jiàn)條款21) 注意gethandle是一個(gè)常指針。因?yàn)槟惝?dāng)然不想讓你的用戶將它指向別的什么東西,而不是sdm::gethandle,對(duì)不對(duì)? (如果真想知道怎么定義一個(gè)函數(shù)的引用,看看下面: sdm::handle& (&gethandle)() =????? // gethandle是指向 ? sdm::gethandle;????????????????? // sdm::gethandle的引用 我個(gè)人認(rèn)為這樣的做法也很好,但你可能以前從沒(méi)見(jiàn)到過(guò)。除了初始化的方式外,函數(shù)的引用和函數(shù)的常指針在行為上完全相同,只是函數(shù)指針更易于理解。) 有了上面的類型定義和引用,那些不會(huì)遭遇全局名字沖突的用戶就會(huì)使用沒(méi)有修飾符的類型和對(duì)象名;相反,那些有全局名字沖突的用戶就會(huì)忽略類型和引用的定義,代之以帶修飾符的符號(hào)名。還要注意的是,不是所有用戶都想使用這種簡(jiǎn)寫名,所以要把類型定義和引用放在一個(gè)單獨(dú)的頭文件中,不要把它和(模擬namespace的)結(jié)構(gòu)的定義混在一起。 struct是namespace的很好的近似,但實(shí)際上還是相差很遠(yuǎn)。它在很多方面很欠缺,其中很明顯的一點(diǎn)是對(duì)運(yùn)算符的處理。如果運(yùn)算符被定義為結(jié)構(gòu)的靜態(tài)成員,它就只能通過(guò)函數(shù)調(diào)用來(lái)使用,而不能象常規(guī)的運(yùn)算符所設(shè)計(jì)的那樣,可以通過(guò)自然的中綴語(yǔ)法來(lái)使用: // 定義一個(gè)模擬名字空間的結(jié)構(gòu),結(jié)構(gòu)內(nèi)部包含widgets的類型 // 和函數(shù)。widgets對(duì)象支持operator+進(jìn)行加法運(yùn)算 struct widgets { ? class widget { ... }; ? // 參見(jiàn)條款21:為什么返回const ? static const widget operator+(const widget& lhs, ??????????????????????????????? const widget& rhs);
? ... }; // 為上面所述的widge和operator+ // 建立全局(無(wú)修飾符的)名稱 typedef widgets::widget widget; const widget (* const operator+)(const widget&,??????? // 錯(cuò)誤! ???????????????????????????????? const widget&);?????? // operator+不能是指針名 ?
widget w1, w2, sum; sum = w1 + w2;?????????????????????????? // 錯(cuò)誤! 本空間沒(méi)有聲明 ???????????????????????????????????????? // 參數(shù)為widgets 的operator+ sum = widgets::operator+(w1, w2);??????? // 合法, 但不是 ???????????????????????????????????????? // "自然"的語(yǔ)法 正因?yàn)檫@些限制,所以一旦編譯器支持,就要盡早使用真正的名字空間。
編寫高效簡(jiǎn)潔的C語(yǔ)言代碼,是許多軟件工程師追求的目標(biāo)。本文就工作中的一些體會(huì)和經(jīng)驗(yàn)做相關(guān)的闡述,不對(duì)的地方請(qǐng)各位指教。
第1招:以空間換時(shí)間
計(jì)算機(jī)程序中最大的矛盾是空間和時(shí)間的矛盾,那么,從這個(gè)角度出發(fā)逆向思維來(lái)考慮程序的效率問(wèn)題,我們就有了解決問(wèn)題的第1招——以空間換時(shí)間。 例如:字符串的賦值。 方法A,通常的辦法: #define LEN 32 char string1 [LEN]; memset (string1,0,LEN); strcpy (string1,“This is a example!!”); 方法B: const char string2[LEN] =“This is a example!”; char * cp; cp = string2 ; (使用的時(shí)候可以直接用指針來(lái)操作。)
從上面的例子可以看出,A和B的效率是不能比的。在同樣的存儲(chǔ)空間下,B直接使用指針就可以操作了,而A需要調(diào)用兩個(gè)字符函數(shù)才能完成。B的缺點(diǎn)在于靈活性沒(méi)有A好。在需要頻繁更改一個(gè)字符串內(nèi)容的時(shí)候,A具有更好的靈活性;如果采用方法B,則需要預(yù)存許多字符串,雖然占用了大量的內(nèi)存,但是獲得了程序執(zhí)行的高效率。
如果系統(tǒng)的實(shí)時(shí)性要求很高,內(nèi)存還有一些,那我推薦你使用該招數(shù)。
該招數(shù)的變招——使用宏函數(shù)而不是函數(shù)。舉例如下: 方法C: #define bwMCDR2_ADDRESS 4 #define bsMCDR2_ADDRESS 17 int BIT_MASK(int __bf) { return ((1U << (bw ## __bf)) - 1) << (bs ## __bf); } void SET_BITS(int __dst, int __bf, int __val) { __dst = ((__dst) & ~(BIT_MASK(__bf))) | \ (((__val) << (bs ## __bf)) & (BIT_MASK(__bf)))) }
SET_BITS(MCDR2, MCDR2_ADDRESS, RegisterNumber); 方法D: #define bwMCDR2_ADDRESS 4 #define bsMCDR2_ADDRESS 17 #define bmMCDR2_ADDRESS BIT_MASK(MCDR2_ADDRESS) #define BIT_MASK(__bf) (((1U << (bw ## __bf)) - 1) << (bs ## __bf)) #define SET_BITS(__dst, __bf, __val) \ ((__dst) = ((__dst) & ~(BIT_MASK(__bf))) | \ (((__val) << (bs ## __bf)) & (BIT_MASK(__bf))))
SET_BITS(MCDR2, MCDR2_ADDRESS, RegisterNumber);
函數(shù)和宏函數(shù)的區(qū)別就在于,宏函數(shù)占用了大量的空間,而函數(shù)占用了時(shí)間。大家要知道的是,函數(shù)調(diào)用是要使用系統(tǒng)的棧來(lái)保存數(shù)據(jù)的,如果編譯器里有棧檢查選項(xiàng),一般在函數(shù)的頭會(huì)嵌入一些匯編語(yǔ)句對(duì)當(dāng)前棧進(jìn)行檢查;同時(shí),CPU也要在函數(shù)調(diào)用時(shí)保存和恢復(fù)當(dāng)前的現(xiàn)場(chǎng),進(jìn)行壓棧和彈棧操作,所以,函數(shù)調(diào)用需要一些CPU時(shí)間。而宏函數(shù)不存在這個(gè)問(wèn)題。宏函數(shù)僅僅作為預(yù)先寫好的代碼嵌入到當(dāng)前程序,不會(huì)產(chǎn)生函數(shù)調(diào)用,所以僅僅是占用了空間,在頻繁調(diào)用同一個(gè)宏函數(shù)的時(shí)候,該現(xiàn)象尤其突出。
D方法是我看到的最好的置位操作函數(shù),是ARM公司源碼的一部分,在短短的三行內(nèi)實(shí)現(xiàn)了很多功能,幾乎涵蓋了所有的位操作功能。C方法是其變體,其中滋味還需大家仔細(xì)體會(huì)。
第2招:數(shù)學(xué)方法解決問(wèn)題
現(xiàn)在我們演繹高效C語(yǔ)言編寫的第二招——采用數(shù)學(xué)方法來(lái)解決問(wèn)題。
數(shù)學(xué)是計(jì)算機(jī)之母,沒(méi)有數(shù)學(xué)的依據(jù)和基礎(chǔ),就沒(méi)有計(jì)算機(jī)的發(fā)展,所以在編寫程序的時(shí)候,采用一些數(shù)學(xué)方法會(huì)對(duì)程序的執(zhí)行效率有數(shù)量級(jí)的提高。 舉例如下,求 1~100的和。 方法E int I , j; for (I = 1 ;I<=100; I ++){ j += I; } 方法F int I; I = (100 * (1+100)) / 2
這個(gè)例子是我印象最深的一個(gè)數(shù)學(xué)用例,是我的計(jì)算機(jī)啟蒙老師考我的。當(dāng)時(shí)我只有小學(xué)三年級(jí),可惜我當(dāng)時(shí)不知道用公式 N×(N+1)/ 2 來(lái)解決這個(gè)問(wèn)題。方法E循環(huán)了100次才解決問(wèn)題,也就是說(shuō)最少用了100個(gè)賦值,100個(gè)判斷,200個(gè)加法(I和j);而方法F僅僅用了1個(gè)加法,1次乘法,1次除法。效果自然不言而喻。所以,現(xiàn)在我在編程序的時(shí)候,更多的是動(dòng)腦筋找規(guī)律,最大限度地發(fā)揮數(shù)學(xué)的威力來(lái)提高程序運(yùn)行的效率。
第3招:使用位操作
實(shí)現(xiàn)高效的C語(yǔ)言編寫的第三招——使用位操作,減少除法和取模的運(yùn)算。
在計(jì)算機(jī)程序中,數(shù)據(jù)的位是可以操作的最小數(shù)據(jù)單位,理論上可以用“位運(yùn)算”來(lái)完成所有的運(yùn)算和操作。一般的位操作是用來(lái)控制硬件的,或者做數(shù)據(jù)變換使用,但是,靈活的位操作可以有效地提高程序運(yùn)行的效率。舉例如下: 方法G int I,J; I = 257 /8; J = 456 % 32; 方法H int I,J; I = 257 >>3; J = 456 - (456 >> 4 << 4);
在字面上好像H比G麻煩了好多,但是,仔細(xì)查看產(chǎn)生的匯編代碼就會(huì)明白,方法G調(diào)用了基本的取模函數(shù)和除法函數(shù),既有函數(shù)調(diào)用,還有很多匯編代碼和寄存器參與運(yùn)算;而方法H則僅僅是幾句相關(guān)的匯編,代碼更簡(jiǎn)潔,效率更高。當(dāng)然,由于編譯器的不同,可能效率的差距不大,但是,以我目前遇到的MS C ,ARM C 來(lái)看,效率的差距還是不小。相關(guān)匯編代碼就不在這里列舉了。 運(yùn)用這招需要注意的是,因?yàn)镃PU的不同而產(chǎn)生的問(wèn)題。比如說(shuō),在PC上用這招編寫的程序,并在PC上調(diào)試通過(guò),在移植到一個(gè)16位機(jī)平臺(tái)上的時(shí)候,可能會(huì)產(chǎn)生代碼隱患。所以只有在一定技術(shù)進(jìn)階的基礎(chǔ)下才可以使用這招。
第4招:匯編嵌入
高效C語(yǔ)言編程的必殺技,第四招——嵌入?yún)R編。
“在熟悉匯編語(yǔ)言的人眼里,C語(yǔ)言編寫的程序都是垃圾”。這種說(shuō)法雖然偏激了一些,但是卻有它的道理。匯編語(yǔ)言是效率最高的計(jì)算機(jī)語(yǔ)言,但是,不可能靠著它來(lái)寫一個(gè)操作系統(tǒng)吧?所以,為了獲得程序的高效率,我們只好采用變通的方法 ——嵌入?yún)R編,混合編程。
舉例如下,將數(shù)組一賦值給數(shù)組二,要求每一字節(jié)都相符。 char string1[1024],string2[1024]; 方法I int I; for (I =0 ;I<1024;I++) *(string2 + I) = *(string1 + I) 方法J #ifdef _PC_ int I; for (I =0 ;I<1024;I++) *(string2 + I) = *(string1 + I); #else #ifdef _ARM_ __asm { MOV R0,string1 MOV R1,string2 MOV R2,#0 loop: LDMIA R0!, [R3-R11] STMIA R1!, [R3-R11] ADD R2,R2,#8 CMP R2, #400 BNE loop } #endif
方法I是最常見(jiàn)的方法,使用了1024次循環(huán);方法J則根據(jù)平臺(tái)不同做了區(qū)分,在ARM平臺(tái)下,用嵌入?yún)R編僅用128次循環(huán)就完成了同樣的操作。這里有朋友會(huì)說(shuō),為什么不用標(biāo)準(zhǔn)的內(nèi)存拷貝函數(shù)呢?這是因?yàn)樵谠磾?shù)據(jù)里可能含有數(shù)據(jù)為0的字節(jié),這樣的話,標(biāo)準(zhǔn)庫(kù)函數(shù)會(huì)提前結(jié)束而不會(huì)完成我們要求的操作。這個(gè)例程典型應(yīng)用于LCD數(shù)據(jù)的拷貝過(guò)程。根據(jù)不同的CPU,熟練使用相應(yīng)的嵌入?yún)R編,可以大大提高程序執(zhí)行的效率。
雖然是必殺技,但是如果輕易使用會(huì)付出慘重的代價(jià)。這是因?yàn)椋褂昧饲度雲(yún)R編,便限制了程序的可移植性,使程序在不同平臺(tái)移植的過(guò)程中,臥虎藏龍,險(xiǎn)象環(huán)生!同時(shí)該招數(shù)也與現(xiàn)代軟件工程的思想相違背,只有在迫不得已的情況下才可以采用。切記,切記。
使用C語(yǔ)言進(jìn)行高效率編程,我的體會(huì)僅此而已。在此以本文拋磚引玉,還請(qǐng)各位高手共同切磋。希望各位能給出更好的方法,大家一起提高我們的編程技巧。
一.系統(tǒng)環(huán)境 2 二.gSOAP的簡(jiǎn)要使用例子 2 三.圖示說(shuō)明 6 四.要注意的問(wèn)題 6 五.參考文檔 7 六.備注 7
一.系統(tǒng)環(huán)境 linux操作系統(tǒng)kernel2.4.2,安裝gsoap2.6到目錄/usr/local/gsoap 二.gSOAP的簡(jiǎn)要使用例子 下面是一個(gè)簡(jiǎn)單的例子,實(shí)現(xiàn)一個(gè)加法運(yùn)算的WebService,具體功能是cli端輸入num1和num2,server端返回一個(gè)num1和num2相加的結(jié)果sum。
1. 首先,我們需要做的是寫一個(gè)函數(shù)聲明文件,來(lái)定義接口函數(shù)ns__add,文件名字為add.h,內(nèi)容如下:
//gsoap ns service name: add //gsoap ns service namespace: http://mail.263.net/add.wsdl //gsoap ns service location: http://mail.263.net //gsoap ns service executable: add.cgi //gsoap ns service encoding: encoded //gsoap ns schema namespace: urn:add
int ns__add( int num1, int num2, int* sum );
2. 然后我們需要?jiǎng)?chuàng)建文件Makefile,從而利用gsoapcpp2工具由add.h生成一些.xml文件、.c文件和.h文件,這些文件均為自動(dòng)生成,Makefile的內(nèi)容如下:
GSOAP_ROOT=/usr/local/gsoap WSNAME=add CC=g++ -g -DWITH_NONAMESPACES INCLUDE=-I $(GSOAP_ROOT) SERVER_OBJS=$(WSNAME)C.o $(WSNAME)Server.o stdsoap2.o CLIENT_OBJS=$(GSOAP_ROOT)/env/envC.o $(WSNAME)ClientLib.o stdsoap2.o ALL_OBJS=${WSNAME}server.o $(WSNAME)C.o $(WSNAME)Server.o ${WSNAME}test.o ${WSNAME}client.o $(WSNAME)ClientLib.o
#總的目標(biāo) all:server
${WSNAME}.wsdl:${WSNAME}.h $(GSOAP_ROOT)/soapcpp2 -p$(WSNAME) -i -n -c ${WSNAME}.h
stdsoap2.o:$(GSOAP_ROOT)/stdsoap2.c $(CC) -c $?
#編譯一樣生成規(guī)則的.o文件 $(ALL_OBJS):%.o:%.c $(CC) -c $? $(INCLUDE)
#編譯服務(wù)器端 server:Makefile ${WSNAME}.wsdl ${WSNAME}server.o $(SERVER_OBJS) $(CC) ${WSNAME}server.o $(SERVER_OBJS) -o ${WSNAME}server
#編譯客戶端 client:Makefile ${WSNAME}.wsdl ${WSNAME}client.c ${WSNAME}test.c $(ALL_OBJS) stdsoap2.o $(CC) ${WSNAME}test.o ${WSNAME}client.o $(CLIENT_OBJS) -o ${WSNAME}test
cl: rm -f *.o *.xml *.a *.wsdl *.nsmap $(WSNAME)H.h $(WSNAME)C.c $(WSNAME)Server.c $(WSNAME)Client.c $(WSNAME)Stub.* $(WSNAME)$(WSNAME)Proxy.* $(WSNAME)$(WSNAME)Object.* $(WSNAME)ServerLib.c $(WSNAME)ClientLib.c $(WSNAME)server ns.xsd $(WSNAME)test
3.我們先來(lái)做一個(gè)server端,創(chuàng)建文件addserver.c文件,內(nèi)容如下:
#include "addH.h" #include "add.nsmap"
int main(int argc, char **argv) { int m, s; /* master and slave sockets */ struct soap add_soap; soap_init(&add_soap); soap_set_namespaces(&add_soap, add_namespaces); if (argc < 2) { printf("usage: %s <server_port> \n", argv[0]); exit(1); } else { m = soap_bind(&add_soap, NULL, atoi(argv[1]), 100); if (m < 0) { soap_print_fault(&add_soap, stderr); exit(-1); } fprintf(stderr, "Socket connection successful: master socket = %d\n", m); for ( ; ; ) { s = soap_accept(&add_soap); if (s < 0) { soap_print_fault(&add_soap, stderr); exit(-1); } fprintf(stderr, "Socket connection successful: slave socket = %d\n", s); add_serve(&add_soap);//該句說(shuō)明該server的服務(wù) soap_end(&add_soap); } } return 0; } //server端的實(shí)現(xiàn)函數(shù)與add.h中聲明的函數(shù)相同,但是多了一個(gè)當(dāng)前的soap連接的參數(shù) int ns__add(struct soap *add_soap, int num1, int num2, int *sum) { *sum = num1 + num2; return 0; }
4.讓我們的server跑起來(lái)吧: shell>make shell>./addserver 8888 如果終端打印出“Socket connection successful: master socket = 3”,那么你的server已經(jīng)在前臺(tái)run起來(lái)了,應(yīng)該是值得高興的。 打開(kāi)IE,鍵入http://本機(jī)IP:8888,顯示XML,服務(wù)已經(jīng)啟動(dòng),終端打印出“Socket connection successful: slave socket = 4”,表示服務(wù)接收到了一次soap的連接。
5.讓我們?cè)賮?lái)寫個(gè)客戶端(這個(gè)只是將soap的客戶端函數(shù)封裝一下,具體的調(diào)用參見(jiàn)下面的addtest.c),創(chuàng)建文件addclient.c,內(nèi)容如下:
#include "addStub.h" #include "add.nsmap" /** * 傳入?yún)?shù):server:server的地址 * num1,num2:需要相加的數(shù) * 傳出參數(shù):sum:num1和num2相加的結(jié)果 * 返回值:0為成功,其他為失敗 */ int add( const char* server, int num1, int num2, int *sum ) { struct soap add_soap; int result = 0; soap_init(&add_soap); soap_set_namespaces(&add_soap, add_namespaces);
//該函數(shù)是客戶端調(diào)用的主要函數(shù),后面幾個(gè)參數(shù)和add.h中聲明的一樣,前面多了3個(gè)參數(shù),函數(shù)名是接口函數(shù)名ns__add前面加上soap_call_ soap_call_ns__add( &add_soap, server, "", num1, num2, sum ); if(add_soap.error) { printf("soap error:%d,%s,%s\n", add_soap.error, *soap_faultcode(&add_soap), *soap_faultstring(&add_soap) ); result = add_soap.error; } soap_end(&add_soap); soap_done(&add_soap); return result; }
6.我們最終寫一個(gè)可以運(yùn)行的客戶端調(diào)用程序,創(chuàng)建文件addtest.c,內(nèi)容如下:
#include <stdio.h> #include <stdlib.h>
int add(const char* server, int num1, int num2, int *sum);
int main(int argc, char **argv) { int result = -1; char* server="http://localhost:8888"; int num1 = 0; int num2 = 0; int sum = 0; if( argc < 3 ) { printf("usage: %s num1 num2 \n", argv[0]); exit(0); }
num1 = atoi(argv[1]); num2 = atoi(argv[2]);
result = add(server, num1, num2, &sum); if (result != 0) { printf("soap err,errcode = %d\n", result); } else { printf("%d+%d=%d\n", num1, num2, sum ); } return 0; }
7.讓我們的client端和server端通訊 shell>make client shell>./addtest 7 8 當(dāng)然,你的server應(yīng)該還在run,這樣得到輸出結(jié)果7+8=15,好了,你成功完成了你的第一個(gè)C寫的WebService,恭喜。 三.圖示說(shuō)明
四.要注意的問(wèn)題 1. add.h文件前面的幾句注釋不能刪除,為soapcpp2需要識(shí)別的標(biāo)志 2. 接口函數(shù)的返回值只能是int,是soap調(diào)用的結(jié)果,一般通過(guò)soap.error來(lái)判斷soap的連接情況,這個(gè)返回值沒(méi)有用到。 3. 接口函數(shù)的最后一個(gè)參數(shù)為傳出參數(shù),如果需要傳出多個(gè)參數(shù),需要自己定義一個(gè)結(jié)構(gòu)將返回項(xiàng)封裝。 4. 在.h文件中不能include別的.h文件,可能不能生效,需要用到某些結(jié)構(gòu)的時(shí)候需要在該文件中直接聲明。 5. 如果客戶端的調(diào)用不需要返回值,那么最后一個(gè)參數(shù) 五.參考文檔 1.gsoap主頁(yè) http://gsoap2.sourceforge.net
2.跟我一起寫Makefile http://dev.csdn.net/develop/article/20/20025.shtm
3.Web Services: A Technical Introduction(機(jī)械工業(yè)出版社) 六.備注 192.168.18.233和192.168.18.234的/usr/local/gsoap目錄下的3個(gè)需要的文件及一個(gè)env目錄,不是編譯安裝的,是在別的地方編譯好了直接copy過(guò)來(lái)的(實(shí)際編譯結(jié)果中還有wsdl2h工具及其他一些文件,但是我們的實(shí)際開(kāi)發(fā)中只是用到了這3個(gè)文件及env目錄)。因?yàn)闀r(shí)間倉(cāng)促,本人還沒(méi)有時(shí)間研究編譯的問(wèn)題,相關(guān)細(xì)節(jié)可以查看參考文檔1。 在192.168.18.233的/home/weiqiong/soap/sample目錄下及192.168.18.234的/tmp/soap/sample目錄下有本文講到的加法運(yùn)算的例子。
|
全文結(jié)束 |
#ifndef MSG_H #define MSG_H //msgid #define LISTEN_THREAD??7 #define CENTER_THREAD??0 #define SEND_THREAD???2 #define REV_THREAD???3 #define TIME_THREAD???4 //lp #define EXIT????0 #define SEND_SGIP_SUBMIT?1 #define SEND_SGIP_BIND #define SEND_SGIP_R #define SEND_SGIP_UNBIND #define SEND_SGIP_UNBIND_R #define REV_SGIP_SOCKET //wp #define SEND_SUCCESS #define PACK_FAIL #define SEND_FAIL enum mgnt_cmd_type { ?event_login???????? = 0, ??event_logout, ??event_sip_init_para, ??event_log_init_para, ??event_sip_clean, ??event_set_dtmf_mode, ??event_set_dhcp, ??event_set_pppoe, ?? ??event_pstn_call_out, ??event_sip_call_out, ??event_answer_sipcall, ??event_release_sipcall, ??event_loadBMP_init, ?? ?? ??event_pstn_call_in=20, ??event_sip_call_in, ??event_remote_release_call, ??event_remote_establish_call, ??event_remote_cancelcall, ??event_login_return, ??event_remote_ignore,
??event_set_pstn_ring, ??event_set_sip_ring, ??event_set_alarm_ring, ??event_set_ring_volume ?? };
typedef struct msgbuf { ?long???msgtype; ?unsigned long?msgid; ?unsigned long?lp; ?unsigned long?wp; }MSGBuf, *pMSGBuf;
int vvMSGSend(long thread_id, unsigned long msgid, unsigned long lp, unsigned long wp); int vvMSGRecv(long thread_id, struct msgbuf *msg, int is_wait);
#ifndef _WINDOWS
#include <sys/types.h> #include <sys/ipc.h> #include <sys/msg.h> #include <unistd.h>
#define?MSG_FILE_NAME??? "/rw/"????? //"/mnt/" #define MSG_FLAG???(IPC_CREAT? | 00666) //| IPC_EXCL ?
typedef struct sendMsg { ?int sd; ?void *content; }SendMsg, *pSendMsg;
#endif
#endif //MSG_H
#include "vvmsg.h" #include <ps_log.h>
#ifndef _WINDOWS #include <phone_Interface.h> #include <pthread.h> #include <basegdi.h> #include <keyboard.h> //#include "hash.h" extern? pthread_t g_incomingthread; //extern? hash_table table; #endif
?
int vvMSGSend(long thread_id, unsigned long msgid, unsigned long lp, unsigned long wp) {? ?struct msgbuf bmsg; #ifndef _WINDOWS ?key_t key;
?int msg_id; ?bmsg.msgtype = thread_id; ?bmsg.msgid = msgid; ?bmsg.lp = lp; ?bmsg.wp = wp;
?if((key = ftok(MSG_FILE_NAME,'a')) == -1) ?{ ??return -1; ?}
?if((msg_id = msgget(key,MSG_FLAG)) == -1) ?{ ??return -1; ?}
?if (msgsnd(msg_id, &bmsg, sizeof(struct msgbuf), IPC_NOWAIT) == -1) ?{ ??return -1; ?} #endif ?return 1;
}
int vvMSGRecv(long thread_id, struct msgbuf *msg, int is_wait) { ?#ifndef _WINDOWS ?key_t key;?? ?int msg_id; ?if((key = ftok(MSG_FILE_NAME,'a')) == -1) ?{ ??printf("Recv msg error 1!\n"); ??return -1; ?} ?if((msg_id = msgget(key,MSG_FLAG)) == -1) ?{ ??printf("Recv msg error 2!\n"); ??return -1; ?} ?if (is_wait != 1) ?{ ??if (msgrcv(msg_id, msg, sizeof(struct msgbuf), thread_id, IPC_NOWAIT) == -1) ??{ ???printf("Recv msg error 3!\n"); ???return -1; ??}? ?} ?else ?{ ??if (msgrcv(msg_id, msg, sizeof(struct msgbuf), thread_id, 0) == -1) ??{ ???//printf("Recv msg error 4!\n"); ???return -1; ??} ?} ??#endif ?return 1;
}
void *skype_thread_start(void *arg) { ?#ifndef _WINDOWS ?MSGBuf msg; ?pthread_setcancelstate(PTHREAD_CANCEL_ENABLE,0);//設(shè)置線程屬性 ?pthread_setcanceltype(PTHREAD_CANCEL_ASYNCHRONOUS,0); ?for (;;) ?{ ??pthread_testcancel();//設(shè)置取消點(diǎn) ??if (vvMSGRecv((long)g_incomingthread, &msg, 1) == -1) ???continue; ?? ??// analysis the message ??switch (msg.msgid) ??{ ???ps_show_str(log_DEBUG, "vvmsg event!!!!!!!!!!!!!!!%d\r\n", msg.msgid); ??case event_login: ???{ ????userLogin(); ???? ???} ???break; ??case event_logout: ???{ ????userLogout(); ???} ??case event_sip_clean: ???{ ????SipClean(); ???} ???break; ??case event_sip_init_para: ???{ ????ps_show_str(log_DEBUG, "event before################UpdateSipInitPara\r\n"); ????UpdateSipInitPara(); ????ps_show_str(log_DEBUG, "event after##################UpdateSipInitPara\r\n"); ???} ???break; ??case event_log_init_para: ???{ ????UpdateLogInitPara(); ???} ???break; ??case event_set_dtmf_mode: ???{ ????int i = (int)msg.lp; ????ps_show_str(log_DEBUG, "event_set_dtmf_mode########################%d\r\n", i); ????SetDTMFMode(i); ???} ???break; ??case event_set_dhcp: ???{ ????SetDHCP(); ???} ???break; ??case event_set_pppoe: ???{ ????SetPPPOE(); ???} ???break;
??case event_pstn_call_out: ???{ ????pstncall((char*)msg.lp); ???} ???break; ??case event_sip_call_out: ???{ ???? ????sipcall((char*)msg.lp); ???} ???break;
??case event_answer_sipcall: ???{ ????callmgr_answercall((LINE_ID_T *)msg.lp); ???} ???break; ??????? case event_release_sipcall: ???{ ????callmgr_releasecall((LINE_ID_T *)msg.lp); ???} ???break; ??case event_loadBMP_init: ???{ ????CreateSysBmp(); ???? ???} ???break; ???
?
?
?
?
?
?
?
??case event_pstn_call_in: ???{ ????LINE_ID_T *line = (LINE_ID_T *)msg.wp; ????sipcome_create(line); ???} ???break; ??case event_sip_call_in: ???{ ????LINE_ID_T *line = (LINE_ID_T *)msg.wp; ????sipcome_create(line); ???} ???break; ? ??case event_remote_establish_call: ???{ ???? ????LINE_ID_T *line = (LINE_ID_T *)msg.wp; ????pstnchat_create(line); ????if(g_Hwnd[HWND_CALLOUT]!=0) ????calling_destroy(g_Hwnd[HWND_CALLOUT]); ???? ???} ???break; ??case event_remote_cancelcall: ???{ ????if(g_Hwnd[HWND_CALLIN]!=0) ???SendMessage(g_Hwnd[HWND_CALLIN],MSG_KEYDOWN,KEY_SW_RSK,0); ???} ???break; ??case event_remote_release_call: ???{ ????if(g_Hwnd[HWND_CHAT]!=0) ????SendMessage(g_Hwnd[HWND_CHAT],MSG_KEYDOWN,KEY_SW_RSK,0); ???} ???break; ??case event_login_return: ???{ ????printf("sfds0000000000000000000000000000000dssssssss^^^^^^^^^^^^^^^^^\r\n"); ????if(g_Hwnd[HWND_MAINSCREEN]!=0) ????{ ?????UpdateWindow(g_Hwnd[HWND_MAINSCREEN],1); ????//?SetFocusChild(g_Hwnd[HWND_MAINSCREEN]); ????//?ShowWindow(g_Hwnd[HWND_MAINSCREEN], SW_SHOW); ????} ???} ???break; ??case event_remote_ignore: ???{ ????if(g_Hwnd[HWND_CALLOUT]!=0) ????SendMessage(g_Hwnd[HWND_CALLOUT],MSG_KEYDOWN,KEY_SW_RSK,0);? ???} ???break; ??case event_set_pstn_ring: ???{ ????SetPstnRing((int)msg.lp); ???} ???break; ??case event_set_sip_ring: ???{ ????SetSipRing((int)msg.lp); ???} ???break; ??case event_set_ring_volume: ???{ ????SetRingVolume((int)msg.lp); ???} ???break; ??}
?} ?#endif }
附(創(chuàng)建線程):if (pthread_create(&g_incomingthread, NULL, skype_thread_start, NULL)) ??return -1;
一個(gè)簡(jiǎn)化的問(wèn)題示例 鏈表的難點(diǎn)在于必須復(fù)制鏈表處理函數(shù)來(lái)處理不同的對(duì)象,即便邏輯是完全相同的。例如兩個(gè)結(jié)構(gòu)類似的鏈表: struct Struct_Object_A
{
int a;
int b;
Struct_Object_A *next;
} OBJECT_A;
typedef struct Struct_Object_B
{
int a;
int b;
int c;
Struct_Object_B *next;
} OBJECT_B; |
上面定義的兩個(gè)結(jié)構(gòu)只有很小的一點(diǎn)差別。OBJECT_B 和 OBJECT_A 之間只差一個(gè)整型變量。但是,在編譯器看來(lái),它們?nèi)匀皇欠浅2煌摹1仨殲榇鎯?chǔ)在鏈表中的每個(gè)對(duì)象復(fù)制用來(lái)添加、刪除和搜索鏈表的函數(shù)。為了解決這個(gè)問(wèn)題,可以使用具有全部三個(gè)變量的一個(gè)聯(lián)合或結(jié)構(gòu),其中整數(shù) c 并不是在所有的情況下都要使用。這可能變得非常復(fù)雜,并會(huì)形成不良的編程風(fēng)格。
C 代碼解決方案:虛擬鏈表
此問(wèn)題更好的解決方案之一是虛擬鏈表。虛擬鏈表是只包含鏈表指針的鏈表。對(duì)象存儲(chǔ)在鏈表結(jié)構(gòu)背后。這一點(diǎn)是這樣實(shí)現(xiàn)的,首先為鏈表節(jié)點(diǎn)分配內(nèi)存,接著為對(duì)象分配內(nèi)存,然后將這塊內(nèi)存分配給鏈表節(jié)點(diǎn)指針,如下所示:
虛擬鏈表結(jié)構(gòu)的一種實(shí)現(xiàn)
typedef struct liststruct
{
liststruct *next;
} LIST, *pLIST;
pLIST Head = NULL;
pLIST AddToList( pLIST Head,
void * data, size_t datasize )
{
pLIST newlist=NULL;
void *p;
// 分配節(jié)點(diǎn)內(nèi)存和數(shù)據(jù)內(nèi)存
newlist = (pLIST) malloc
( datasize + sizeof( LIST ) );
// 為這塊數(shù)據(jù)緩沖區(qū)指定一個(gè)指針
p = (void *)( newlist + 1 );
// 復(fù)制數(shù)據(jù)
memcpy( p, data, datasize );
// 將這個(gè)節(jié)點(diǎn)指定給鏈表的表頭
if( Head )
{
newlist->next = Head;
}
else
newlist->next = NULL;
Head = newlist;
return Head;
} |
鏈表節(jié)點(diǎn)現(xiàn)在建立在數(shù)據(jù)值副本的基本之上。這個(gè)版本能很好地處理標(biāo)量值,但不能處理帶有用 malloc 或 new 分配的元素的對(duì)象。要處理這些對(duì)象,LIST 結(jié)構(gòu)需要包含一個(gè)一般的解除函數(shù)指針,這個(gè)指針可用來(lái)在將節(jié)點(diǎn)從鏈表中刪除并解除它之前釋放內(nèi)存(或者關(guān)閉文件,或者調(diào)用關(guān)閉方法)。
一個(gè)帶有解除函數(shù)的鏈表
typedef void (*ListNodeDestructor)( void * );
typedef struct liststruct
{
ListNodeDestructor DestructFunc;
liststruct *next;
} LIST, *pLIST;
pLIST AddToList( pLIST Head, void * data,
size_t datasize,
ListNodeDestructor Destructor )
{
pLIST newlist=NULL;
void *p;
// 分配節(jié)點(diǎn)內(nèi)存和數(shù)據(jù)內(nèi)存
newlist = (pLIST) malloc
( datasize + sizeof( LIST ) );
// 為這塊數(shù)據(jù)緩沖區(qū)指定一個(gè)指針
p = (void *)( newlist + 1 );
// 復(fù)制數(shù)據(jù)
memcpy( p, data, datasize );
newlist->DestructFunc = Destructor;
// 將這個(gè)節(jié)點(diǎn)指定給鏈表的表頭
if( Head )
{
newlist->next = Head;
}
else
newlist->next = NULL;
Head = newlist;
return Head;
}
void DeleteList( pLIST Head )
{
pLIST Next;
while( Head )
{
Next = Head->next;
Head->DestructFunc(
(void *) Head );
free( Head );
Head = Next;
}
}
typedef struct ListDataStruct
{
LPSTR p;
} LIST_DATA, *pLIST_DATA;
void ListDataDestructor( void *p )
{
// 對(duì)節(jié)點(diǎn)指針進(jìn)行類型轉(zhuǎn)換
pLIST pl = (pLIST)p;
// 對(duì)數(shù)據(jù)指針進(jìn)行類型轉(zhuǎn)換
pLIST_DATA pLD = (pLIST_DATA)
( pl + 1 );
delete pLD->p;
}
pLIST Head = NULL;
void TestList()
{
pLIST_DATA d = new LIST_DATA;
d->p = new char[24];
strcpy( d->p, "Hello" );
Head = AddToList( Head, (void *) d,
sizeof( pLIST_DATA ),
ListDataDestructor );
// 該對(duì)象已被復(fù)制,現(xiàn)在刪除原來(lái)的對(duì)象
delete d;
d = new LIST_DATA;
d->p = new char[24];
strcpy( d->p, "World" );
Head = AddToList( Head, (void *) d,
sizeof( pLIST_DATA ),
ListDataDestructor );
delete d;
// 釋放鏈表
DeleteList( Head );
} |
在每個(gè)鏈表節(jié)點(diǎn)中包含同一個(gè)解除函數(shù)的同一個(gè)指針?biāo)坪跏抢速M(fèi)內(nèi)存空間。確實(shí)如此,但只有鏈表始終包含相同的對(duì)象才屬于這種情況。按這種方式編寫鏈表允許您將任何對(duì)象放在鏈表中的任何位置。大多數(shù)鏈表函數(shù)要求對(duì)象總是相同的類型或類。
虛擬鏈表則無(wú)此要求。它所需要的只是將對(duì)象彼此區(qū)分開(kāi)的一種方法。要實(shí)現(xiàn)這一點(diǎn),您既可以檢測(cè)解除函數(shù)指針的值,也可以在鏈表中所用的全部結(jié)構(gòu)前添加一個(gè)類型值并對(duì)它進(jìn)行檢測(cè)。
當(dāng)然,如果要將鏈表編寫為一個(gè) C++ 類,則對(duì)指向解除函數(shù)的指針的設(shè)置和存儲(chǔ)只能進(jìn)行一次。
C++ 解決方案:類鏈表
本解決方案將 CList 類定義為從 LIST 結(jié)構(gòu)導(dǎo)出的一個(gè)類,它通過(guò)存儲(chǔ)解除函數(shù)的單個(gè)值來(lái)處理單個(gè)存儲(chǔ)類型。請(qǐng)注意添加的 GetCurrentData() 函數(shù),該函數(shù)完成從鏈表節(jié)點(diǎn)指針到數(shù)據(jù)偏移指針的數(shù)學(xué)轉(zhuǎn)換。一個(gè)虛擬鏈表對(duì)象
// 定義解除函數(shù)指針
typedef void (*ListNodeDestructor)
( void * );
// 未添加解除函數(shù)指針的鏈表
typedef struct ndliststruct
{
ndliststruct *next;
} ND_LIST, *pND_LIST;
// 定義處理一種數(shù)據(jù)類型的鏈表類
class CList : public ND_LIST
{
public:
CList(ListNodeDestructor);
~CList();
pND_LIST AddToList
( void * data, size_t datasize );
void *GetCurrentData();
void DeleteList( pND_LIST Head );
private:
pND_LIST m_HeadOfList;
pND_LIST m_CurrentNode;
ListNodeDestructor
m_DestructFunc;
};
// 用正確的起始值構(gòu)造這個(gè)鏈表對(duì)象
CList::CList(ListNodeDestructor Destructor)
: m_HeadOfList(NULL),
m_CurrentNode(NULL)
{
m_DestructFunc = Destructor;
}
// 在解除對(duì)象以后刪除鏈表
CList::~CList()
{
DeleteList(m_HeadOfList);
}
// 向鏈表中添加一個(gè)新節(jié)點(diǎn)
pND_LIST CList::AddToList
( void * data, size_t datasize )
{
pND_LIST newlist=NULL;
void *p;
// 分配節(jié)點(diǎn)內(nèi)存和數(shù)據(jù)內(nèi)存
newlist = (pND_LIST) malloc
( datasize + sizeof( ND_LIST ) );
// 為這塊數(shù)據(jù)緩沖區(qū)指定一個(gè)指針
p = (void *)( newlist + 1 );
// 復(fù)制數(shù)據(jù)
memcpy( p, data, datasize );
// 將這個(gè)節(jié)點(diǎn)指定給鏈表的表頭
if( m_HeadOfList )
{
newlist->next = m_HeadOfList;
}
else
newlist->next = NULL;
m_HeadOfList = newlist;
return m_HeadOfList;
}
// 將當(dāng)前的節(jié)點(diǎn)數(shù)據(jù)作為 void 類型返回,
以便調(diào)用函數(shù)能夠?qū)⑺D(zhuǎn)換為任何類型
void * CList::GetCurrentData()
{
return (void *)(m_CurrentNode+1);
}
// 刪除已分配的鏈表
void CList::DeleteList( pND_LIST Head )
{
pND_LIST Next;
while( Head )
{
Next = Head->next;
m_DestructFunc( (void *) Head );
free( Head );
Head = Next;
}
}
// 創(chuàng)建一個(gè)要在鏈表中創(chuàng)建和存儲(chǔ)的結(jié)構(gòu)
typedef struct ListDataStruct
{
LPSTR p;
} LIST_DATA, *pND_LIST_DATA;
// 定義標(biāo)準(zhǔn)解除函數(shù)
void ClassListDataDestructor( void *p )
{
// 對(duì)節(jié)點(diǎn)指針進(jìn)行類型轉(zhuǎn)換
pND_LIST pl = (pND_LIST)p;
// 對(duì)數(shù)據(jù)指針進(jìn)行類型轉(zhuǎn)換
pND_LIST_DATA pLD = (pND_LIST_DATA)
( pl + 1 );
delete pLD->p;
}
// 測(cè)試上面的代碼
void MyCListClassTest()
{
// 創(chuàng)建鏈表類
CList* pA_List_of_Data =
new CList(ClassListDataDestructor);
// 創(chuàng)建數(shù)據(jù)對(duì)象
pND_LIST_DATA d = new LIST_DATA;
d->p = new char[24];
strcpy( d->p, "Hello" );
// 創(chuàng)建指向鏈表頂部的局部指針
pND_LIST Head = NULL;
//向鏈表中添加一些數(shù)據(jù)
Head = pA_List_of_Data->AddToList
( (void *) d,
sizeof( pND_LIST_DATA ) );
// 該對(duì)象已被復(fù)制,現(xiàn)在刪除原來(lái)的對(duì)象
delete d;
// 確認(rèn)它已被存儲(chǔ)
char * p = ((pND_LIST_DATA) pA_List_of_Data->GetCurrentData())->p;
d = new LIST_DATA;
d->p = new char[24];
strcpy( d->p, "World" );
Head = pA_List_of_Data->AddToList
( (void *) d, sizeof( pND_LIST_DATA ) );
// 該對(duì)象已被復(fù)制,現(xiàn)在刪除原來(lái)的對(duì)象
delete d;
// 確認(rèn)它已被存儲(chǔ)
p = ((pND_LIST_DATA)
pA_List_of_Data->GetCurrentData())->p;
// 刪除鏈表類,析構(gòu)函數(shù)將刪除鏈表
delete pA_List_of_Data;
} |
小結(jié)
從前面的討論來(lái)看,似乎僅編寫一個(gè)簡(jiǎn)單的鏈表就要做大量的工作,但這只須進(jìn)行一次。很容易將這段代碼擴(kuò)充為一個(gè)處理排序、搜索以及各種其他任務(wù)的 C++ 類,并且這個(gè)類可以處理任何數(shù)據(jù)對(duì)象或類(在一個(gè)項(xiàng)目中,它處理大約二十個(gè)不同的對(duì)象)。您永遠(yuǎn)不必重新編寫這段代碼。
|