對(duì)文件進(jìn)行讀寫(xiě)是常碰到操作,文件在進(jìn)行讀寫(xiě)操作之前要先打開(kāi),使用完畢要關(guān)閉。所謂打開(kāi)文件,實(shí)際上是建立文件的各種有關(guān)信息,并使文件指針指向該文件,以便進(jìn)行其它操作。通過(guò)c語(yǔ)言基礎(chǔ)培訓(xùn)可以基本掌握文件進(jìn)行讀寫(xiě)操作。
文件的打開(kāi)(fopen函數(shù))
fopen函數(shù)用來(lái)打開(kāi)一個(gè)文件,其調(diào)用的一般形式為:文件指針名=fopen(文件名,使用文件方式); 其中,"文件指針名"必須是被說(shuō)明為FILE 類(lèi)型的指針變量;"文件名"是被打開(kāi)文件的文件名;"使用文件方式"是指文件的類(lèi)型和操作要求。 "文件名"是字符串常量或字符串?dāng)?shù)組。
相關(guān)函數(shù) :open,fclose
表頭文件 :#include<stdio.h>
定義函數(shù) :FILE * fopen(const char * path,const char * mode);
函數(shù)說(shuō)明
參數(shù)path字符串包含欲打開(kāi)的文件路徑及文件名,參數(shù)mode字符串則代表著流形態(tài)。
mode有下列幾種形態(tài)字符串:
r 打開(kāi)只讀文件,該文件必須存在。
r+ 打開(kāi)可讀寫(xiě)的文件,該文件必須存在。
w 打開(kāi)只寫(xiě)文件,若文件存在則文件長(zhǎng)度清為0,即該文件內(nèi)容會(huì)消失。若文件不存在則建立該文件。
w+ 打開(kāi)可讀寫(xiě)文件,若文件存在則文件長(zhǎng)度清為零,即該文件內(nèi)容會(huì)消失。若文件不存在則建立該文件。
a 以附加的方式打開(kāi)只寫(xiě)文件。若文件不存在,則會(huì)建立該文件,如果文件存在,寫(xiě)入的數(shù)據(jù)會(huì)被加到文件尾,即文件原先的內(nèi)容會(huì)被保留。
a+ 以附加方式打開(kāi)可讀寫(xiě)的文件。若文件不存在,則會(huì)建立該文件,如果文件存在,寫(xiě)入的數(shù)據(jù)會(huì)被加到文件尾后,即文件原先的內(nèi)容會(huì)被保留。
上述的形態(tài)字符串都可以再加一個(gè)b字符,如rb、w+b或ab+等組合,加入b 字符用來(lái)告訴函數(shù)庫(kù)打開(kāi)的文件為二進(jìn)制文件,而非純文字文件。不過(guò)在POSIX系統(tǒng),包含Linux都會(huì)忽略該字符。由fopen()所建立的新文件會(huì)具有S_IRUSR|S_IWUSR|S_IRGRP|S_IWGRP|S_IROTH|S_IWOTH(0666)權(quán)限,此文件權(quán)限也會(huì)參考umask 值。
返回值
文件順利打開(kāi)后,指向該流的文件指針就會(huì)被返回。若果文件打開(kāi)失敗則返回NULL,并把錯(cuò)誤代碼存在errno 中。
附加說(shuō)明
一般而言,開(kāi)文件后會(huì)作一些文件讀取或?qū)懭氲膭?dòng)作,若開(kāi)文件失敗,接下來(lái)的讀寫(xiě)動(dòng)作也無(wú)法順利進(jìn)行,所以在fopen()后請(qǐng)作錯(cuò)誤判斷及處理。
范例
#include<stdio.h>
main()
{
FILE * fp;
fp=fopen("noexist","a+");
if(fp= =NULL) return;
fclose(fp);
}
一般程序如果要進(jìn)行優(yōu)化,通常情況下是指優(yōu)化程序代碼或程序執(zhí)行速度。優(yōu)化代碼和優(yōu)化速度實(shí)際上是一個(gè)予盾的統(tǒng)一,一般是優(yōu)化了代碼的尺寸,就會(huì)帶來(lái)執(zhí)行時(shí)間的增加,如果優(yōu)化了程序的執(zhí)行速度,通常會(huì)帶來(lái)代碼增加的副作用,很難魚(yú)與熊掌兼得,只能在設(shè)計(jì)時(shí)掌握一個(gè)平衡點(diǎn)。
一、程序結(jié)構(gòu)的優(yōu)化
1、表達(dá)式
對(duì)于一個(gè)表達(dá)式中各種運(yùn)算執(zhí)行的優(yōu)先順序不太明確或容易混淆的地方,應(yīng)當(dāng)采用圓括號(hào)明確指定它們的優(yōu)先順序。一個(gè)表達(dá)式通常不能寫(xiě)得太復(fù)雜,如果表達(dá)式太復(fù)雜,時(shí)間久了以后,自己也不容易看得懂,不利于以后的維護(hù)。
2、程序的書(shū)寫(xiě)結(jié)構(gòu)
雖然書(shū)寫(xiě)格式并不會(huì)影響生成的代碼質(zhì)量,但是在實(shí)際編寫(xiě)程序時(shí)還是應(yīng)該尊循一定的書(shū)寫(xiě)規(guī)則,一個(gè)書(shū)寫(xiě)清晰、明了的程序,有利于以后的維護(hù)。在書(shū)寫(xiě)程序時(shí),特別是對(duì)于While、for、do…while、if…elst、switch…case等語(yǔ)句或這些語(yǔ)句嵌套組合時(shí),應(yīng)采用"縮格"的書(shū)寫(xiě)形式,
3、減少判斷語(yǔ)句
能夠使用條件編譯(ifdef)的地方就使用條件編譯而不使用if語(yǔ)句,有利于減少編譯生成的代碼的長(zhǎng)度,能夠不用判斷語(yǔ)句則少用判斷用語(yǔ)句。
4、標(biāo)識(shí)符
程序中使用的用戶(hù)標(biāo)識(shí)符除要遵循標(biāo)識(shí)符的命名規(guī)則以外,一般不要用代數(shù)符號(hào)(如a、b、x1、y1)作為變量名,應(yīng)選取具有相關(guān)含義的英文單詞(或縮寫(xiě))或漢語(yǔ)拼音作為標(biāo)識(shí)符,以增加程序的可讀性,如:count、number1、red、work等。
5、定義常數(shù)
在程序化設(shè)計(jì)過(guò)程中,對(duì)于經(jīng)常使用的一些常數(shù),如果將它直接寫(xiě)到程序中去,一旦常數(shù)的數(shù)值發(fā)生變化,就必須逐個(gè)找出程序中所有的常數(shù),并逐一進(jìn)行修改,這樣必然會(huì)降低程序的可維護(hù)性。因此,應(yīng)盡量當(dāng)采用預(yù)處理命令方式來(lái)定義常數(shù),而且還可以避免輸入錯(cuò)誤。
二、代碼的優(yōu)化
1、使用自加、自減指令
通常使用自加、自減指令和復(fù)合賦值表達(dá)式(如a-=1及a+=1等)都能夠生成高質(zhì)量的程序代碼,編譯器通常都能夠生成inc和dec之類(lèi)的指令,而使用a=a+1或a=a-1之類(lèi)的指令,有很多C編譯器都會(huì)生成二到三個(gè)字節(jié)的指令。在AVR單片適用的ICCAVR、GCCAVR、IAR等C編譯器以上幾種書(shū)寫(xiě)方式生成的代碼是一樣的,也能夠生成高質(zhì)量的inc和dec之類(lèi)的的代碼。
2、查表
在程序中一般不進(jìn)行非常復(fù)雜的運(yùn)算,如浮點(diǎn)數(shù)的乘除及開(kāi)方等,以及一些復(fù)雜的數(shù)學(xué)模型的插補(bǔ)運(yùn)算,對(duì)這些即消耗時(shí)間又消費(fèi)資源的運(yùn)算,應(yīng)盡量使用查表的方式,并且將數(shù)據(jù)表置于程序存儲(chǔ)區(qū)。如果直接生成所需的表比較困難,也盡量在啟動(dòng)時(shí)先計(jì)算,然后在數(shù)據(jù)存儲(chǔ)器中生成所需的表,后以在程序運(yùn)行直接查表就可以了,減少了程序執(zhí)行過(guò)程中重復(fù)計(jì)算的工作量。
3、使用盡量小的數(shù)據(jù)類(lèi)型
能夠使用字符型(char)定義的變量,就不要使用整型(int)變量來(lái)定義;能夠使用整型變量定義的變量就不要用長(zhǎng)整型(long int),能不使用浮點(diǎn)型(float)變量就不要使用浮點(diǎn)型變量。當(dāng)然,在定義變量后不要超過(guò)變量的作用范圍,如果超過(guò)變量的范圍賦值,C編譯器并不報(bào)錯(cuò),但程序運(yùn)行結(jié)果卻錯(cuò)了,而且這樣的錯(cuò)誤很難發(fā)現(xiàn)。在ICCAVR中,可以在Options中設(shè)定使用printf參數(shù),盡量使用基本型參數(shù)(%c、%d、%x、%X、%u和%s格式說(shuō)明符),少用長(zhǎng)整型參數(shù)(%ld、%lu、%lx和%lX格式說(shuō)明符),至于浮點(diǎn)型的參數(shù)(%f)則盡量不要使用,其它C編譯器也一樣。在其它條件不變的情況下,使用%f參數(shù),會(huì)使生成的代碼的數(shù)量增加很多,執(zhí)行速度降低。
4、選擇合適的算法和數(shù)據(jù)結(jié)構(gòu)
應(yīng)該熟悉算法語(yǔ)言,知道各種算法的優(yōu)缺點(diǎn),具體資料請(qǐng)參見(jiàn)相應(yīng)的參考資料,有很多計(jì)算機(jī)書(shū)籍上都有介紹。將比較慢的順序查找法用較快的二分查找或亂序查找法代替,插入排序或冒泡排序法用快速排序、合并排序或根排序代替,都可以大大提高程序執(zhí)行的效率選擇一種合適的數(shù)據(jù)結(jié)構(gòu)也很重要,比如你在一堆隨機(jī)存放的數(shù)中使用了大量的插入和刪除指令,那使用鏈表要快得多。數(shù)組與指針語(yǔ)句具有十分密碼的關(guān)系,一般來(lái)說(shuō),指針比較靈活簡(jiǎn)潔,而數(shù)組則比較直觀,容易理解。對(duì)于大部分的編譯器,使用指針比使用數(shù)組生成的代碼更短,執(zhí)行效率更高。但是在Keil中則相反,使用數(shù)組比使用的指針生成的代碼更短。
#include<stdlib.h>
#include<stdio.h>
void swap1(int x,int y)
{
int temp;
temp=x;
x=y;
y=temp;
}
void swap2(int *x,int *y)
{
int *temp;
temp=x;
x=y;
y=temp;
}
void swap3(int *x,int *y)
{
int temp;
temp=*x;
*x=*y;
*y=temp;
}
void swap4(int a[],int b[])
{
int temp;
temp=a[0];
a[0]=b[0];
b[0]=temp;
}
void swap5(int a[],int b[])
{
int temp;
temp=*a;
*a=*b;
*b=temp;
}
int main()
{
int x,y;
x=4;
y=3;
swap1(x,y);
printf("swap1: x:%d,y:%d\n",x,y);//形參傳值,不能交換,實(shí)際傳過(guò)去是拷貝的一份,沒(méi)改變主函數(shù)中x,y
swap2(&x,&y);
printf("swap2: x:%d,y:%d\n",x,y);//不能交換,函數(shù)中只是地址交換了下,地址指向的內(nèi)容沒(méi)有交換
swap3(&x,&y);
printf("swap3: x:%d,y:%d\n",x,y);//能交換,地址指向的內(nèi)容進(jìn)行了交換
swap4(&x,&y);
printf("swap4: x:%d,y:%d\n",x,y);//能交換,地址指向的內(nèi)容進(jìn)行交換
swap5(&x,&y);
printf("swap5: x:%d,y:%d\n",x,y);//能交換,地址指向的內(nèi)容進(jìn)行交換
return 0;
}
swap1: x:4,y:3
swap2: x:4,y:3
swap3: x:3,y:4
swap4: x:4,y:3
swap5: x:3,y:4
本文提供了一個(gè)用于對(duì) C/
C++ 程序進(jìn)行編譯和連接以產(chǎn)生可執(zhí)行程序的通用 Makefile.
在使用 Makefile 之前,只需對(duì)它進(jìn)行一些簡(jiǎn)單的設(shè)置即可;而且一經(jīng)設(shè)置,即使以后對(duì)源程序文件有所增減一般也不再需要改動(dòng) Makefile.因此,即便是一個(gè)沒(méi)有學(xué)習(xí)過(guò) Makefile 書(shū)寫(xiě)規(guī)則的人,也可以為自己的 C/C++ 程序快速建立一個(gè)可工作的 Makefile.
這個(gè) Makefile 可以在 GNU Make 和 GCC 編譯器下正常工作。但是不能保證對(duì)于其它版本的 Make 和編譯器也能正常工作。
如果你發(fā)現(xiàn)了本文中的錯(cuò)誤,或者對(duì)本文有什么感想或建議,可通過(guò) whyglinux AT hotmail DOT com 郵箱和作者聯(lián)系。
此 Makefile 的使用方法如下:[list=1][*]程序目錄的組織盡量將自己的源程序集中在一個(gè)目錄中,并且把 Makefile 和源程序放在一起,這樣用起來(lái)比較方便。當(dāng)然,也可以將源程序分類(lèi)存放在不同的目錄中。
在程序目錄中創(chuàng)建一個(gè)名為 Makefile 的文本文件,將后面列出的 Makefile 的內(nèi)容復(fù)制到這個(gè)文件中。(注意:在復(fù)制的過(guò)程中,Makfile 中各命令前面的 Tab 字符有可能被轉(zhuǎn)換成若干個(gè)空格。這種情況下需要把 Makefile 命令前面的這些空格替換為一個(gè) Tab.)
將當(dāng)前工作目錄切換到 Makefile 所在的目錄。目前,這個(gè) Makefile 只支持在當(dāng)前目錄中的調(diào)用,不支持當(dāng)前目錄和 Makefile 所在的路徑不是同一目錄的情況。
[*]指定可執(zhí)行文件程序編譯和連接成功后產(chǎn)生的可執(zhí)行文件在 Makefile 中的 PROGRAM 變量中設(shè)定。這一項(xiàng)不能為空。為自己程序的可執(zhí)行文件起一個(gè)有意義的名子吧。
[*]指定源程序要編譯的源程序由其所在的路徑和文件的擴(kuò)展名兩項(xiàng)來(lái)確定。由于頭文件是通過(guò)包含來(lái)使用的,所以在這里說(shuō)的源程序不應(yīng)包含頭文件。
程序所在的路徑在 SRCDIRS 中設(shè)定。如果源程序分布在不同的目錄中,那么需要在 SRCDIRS 中一一指定,并且路徑名之間用空格分隔。
在 SRCEXTS 中指定程序中使用的文件類(lèi)型。C/C++ 程序的擴(kuò)展名一般有比較固定的幾種形式:。c、。C、。cc、。cpp、。CPP、。c++、。cp、或者。cxx(參見(jiàn) man gcc)。擴(kuò)展名決定了程序是 C 還是 C++ 程序:。c 是 C 程序,其它擴(kuò)展名表示 C++ 程序。一般固定使用其中的一種擴(kuò)展名即可。但是也有可能需要使用多種擴(kuò)展名,這可以在 SOURCE_EXT 中一一指定,各個(gè)擴(kuò)展名之間用空格分隔。
雖然并不常用,但是 C 程序也可以被作為 C++ 程序編譯。這可以通過(guò)在 Makefile 中設(shè)置 CC = $(CXX) 和 CFLAGS = $(CXXFLAGS) 兩項(xiàng)即可實(shí)現(xiàn)。
這個(gè) Makefile 支持 C、C++ 以及 C/C++ 混合三種編譯方式:[list][*]如果只指定 .c 擴(kuò)展名,那么這是一個(gè) C 程序,用 $(CC) 表示的編譯命令進(jìn)行編譯和連接。
[*]如果指定的是除 .c 之外的其它擴(kuò)展名(如 .cc、。cpp、。cxx 等),那么這是一個(gè) C++ 程序,用 $(CXX) 進(jìn)行編譯和連接。
[*]如果既指定了 .c,又指定了其它 C++ 擴(kuò)展名,那么這是 C/C++ 混合程序,將用 $(CC) 編譯其中的 C 程序,用 $(CXX) 編譯其中的 C++ 程序,最后再用 $(CXX) 連接程序。
[/list]這些工作都是 make 根據(jù)在 Makefile 中提供的程序文件類(lèi)型(擴(kuò)展名)自動(dòng)判斷進(jìn)行的,不需要用戶(hù)干預(yù)。
[*]指定編譯選項(xiàng)編譯選項(xiàng)由三部分組成:預(yù)處理選項(xiàng)、編譯選項(xiàng)以及連接選項(xiàng),分別由 CPPFLAGS、CFLAGS與CXXFLAGS、LDFLAGS 指定。
CPPFLAGS 選項(xiàng)可參考 C 預(yù)處理命令 cpp 的說(shuō)明,但是注意不能包含 -M 以及和 -M 有關(guān)的選項(xiàng)。如果是 C/C++ 混合編程,也可以在這里設(shè)置 C/C++ 的一些共同的編譯選項(xiàng)。
CFLAGS 和 CXXFLAGS 兩個(gè)變量通常用來(lái)指定編譯選項(xiàng)。前者僅僅用于指定 C 程序的編譯選項(xiàng),后者僅僅用于指定 C++ 程序的編譯選項(xiàng)。其實(shí)也可以在兩個(gè)變量中指定一些預(yù)處理選項(xiàng)(即一些本來(lái)應(yīng)該放在 CPPFLAGS 中的選項(xiàng)),和 CPPFLAGS 并沒(méi)有明確的界限。
連接選項(xiàng)在 LDFLAGS 中指定。如果只使用 C/C++ 標(biāo)準(zhǔn)庫(kù),一般沒(méi)有必要設(shè)置。如果使用了非標(biāo)準(zhǔn)庫(kù),應(yīng)該在這里指定連接需要的選項(xiàng),如庫(kù)所在的路徑、庫(kù)名以及其它聯(lián)接選項(xiàng)。
現(xiàn)在的庫(kù)一般都提供了一個(gè)相應(yīng)的 .pc 文件來(lái)記錄使用庫(kù)所需要的預(yù)編譯選項(xiàng)、編譯選項(xiàng)和連接選項(xiàng)等信息,通過(guò) pkg-config 可以動(dòng)態(tài)提取這些選項(xiàng)。與由用戶(hù)顯式指定各個(gè)選項(xiàng)相比,使用 pkg-config 來(lái)訪問(wèn)庫(kù)提供的選項(xiàng)更方便、更具通用性。在后面可以看到一個(gè) GTK+ 程序的例子,其編譯和連接選項(xiàng)的指定就是用 pkg-config 實(shí)現(xiàn)的。
[*]編譯和連接上面的各項(xiàng)設(shè)置好之后保存 Makefile 文件。執(zhí)行 make 命令,程序就開(kāi)始編譯了。
命令 make 會(huì)根據(jù) Makefile 中設(shè)置好的路徑和文件類(lèi)型搜索源程序文件,然后根據(jù)文件的類(lèi)型調(diào)用相應(yīng)的編譯命令、使用相應(yīng)的編譯選項(xiàng)對(duì)程序進(jìn)行編譯。
編譯成功之后程序的連接會(huì)自動(dòng)進(jìn)行。如果沒(méi)有錯(cuò)誤的話最終會(huì)產(chǎn)生程序的可執(zhí)行文件。
注意:在對(duì)程序編譯之后,會(huì)產(chǎn)生和源程序文件一一對(duì)應(yīng)的 .d 文件。這是表示依賴(lài)關(guān)系的文件,通過(guò)它們 make 決定在源程序文件變動(dòng)之后要進(jìn)行哪些更新。為每一個(gè)源程序文件建立相應(yīng)的 .d 文件這也是 GNU Make 推薦的方式。
[*]Makefile 目標(biāo)(Targets)
下面是關(guān)于這個(gè) Makefile 提供的目標(biāo)以及它所完成的功能:[list][*]make編譯和連接程序。相當(dāng)于 make all. [*]make objs僅僅編譯程序產(chǎn)生 .o 目標(biāo)文件,不進(jìn)行連接(一般很少單獨(dú)使用)。
[*]make clean刪除編譯產(chǎn)生的目標(biāo)文件和依賴(lài)文件。
[*]make cleanall刪除目標(biāo)文件、依賴(lài)文件以及可執(zhí)行文件。
[*]make rebuild重新編譯和連接程序。相當(dāng)于 make clean && make all. [/list][/list]關(guān)于這個(gè) Makefile 的實(shí)現(xiàn)原理不準(zhǔn)備詳細(xì)解釋了。如果有興趣的話,可參考文末列出的“參考資料”。
Makefile 的內(nèi)容如下:############################################################################### # # Generic Makefile for C/C++ Program # # Author: whyglinux (whyglinux AT hotmail DOT com) # Date: 2006/03/04 # Description: # The makefile searches in <SRCDIRS> directories for the source files # with extensions specified in <SOURCE_EXT>, then compiles the sources # and finally produces the <PROGRAM>, the executable file, by linking # the objectives. # Usage: # $ make compile and link the program. # $ make objs compile only (no linking. Rarely used)。 # $ make clean clean the objectives and dependencies. # $ make cleanall clean the objectives, dependencies and executable. # $ make rebuild rebuild the program. The same as make clean && make all. #============================================================================== ## Customizing Section: adjust the following if necessary. ##============================================================================= # The executable file name. # It must be specified. # PROGRAM := a.out # the executable name PROGRAM := # The directories in which source files reside. # At least one path should be specified. # SRCDIRS := . # current directory SRCDIRS := # The source file types (headers excluded)。 # At least one type should be specified. # The valid suffixes are among of .c, .C, .cc, .cpp, .CPP, .c++, .cp, or .cxx. # SRCEXTS := .c # C program # SRCEXTS := .cpp # C++ program # SRCEXTS := .c .cpp # C/C++ program SRCEXTS := # The flags used by the cpp (man cpp for more)。 # CPPFLAGS := -Wall -Werror # show all warnings and take them as errors CPPFLAGS := # The compiling flags used only for C. # If it is a C++ program, no need to set these flags. # If it is a C and C++ merging program, set these flags for the C parts. CFLAGS := CFLAGS += # The compiling flags used only for C++. # If it is a C program, no need to set these flags. # If it is a C and C++ merging program, set these flags for the C++ parts. CXXFLAGS := CXXFLAGS += # The library and the link options ( C and C++ common)。 LDFLAGS := LDFLAGS += ## Implict Section: change the following only when necessary. ##============================================================================= # The C program compiler. Uncomment it to specify yours explicitly. #CC = gcc # The C++ program compiler. Uncomment it to specify yours explicitly. #CXX = g++ # Uncomment the 2 lines to compile C programs as C++ ones. #CC = $(CXX) #CFLAGS = $(CXXFLAGS) # The command used to delete file. #RM = rm -f ## Stable Section: usually no need to be changed. But you can add more. ##============================================================================= SHELL = /bin/sh SOURCES = $(foreach d,$(SRCDIRS),$(wildcard $(addprefix $(d)/*,$(SRCEXTS)))) OBJS = $(foreach x,$(SRCEXTS), \ $(patsubst %$(x),%.o,$(filter %$(x),$(SOURCES)))) DEPS = $(patsubst %.o,%.d,$(OBJS)) .PHONY : all objs clean cleanall rebuild all : $(PROGRAM) # Rules for creating the dependency files (。d)。 #—— %.d : %.c @$(CC) -MM -MD $(CFLAGS) $< %.d : %.C @$(CC) -MM -MD $(CXXFLAGS) $< %.d : %.cc @$(CC) -MM -MD $(CXXFLAGS) $< %.d : %.cpp @$(CC) -MM -MD $(CXXFLAGS) $< %.d : %.CPP @$(CC) -MM -MD $(CXXFLAGS) $< %.d : %.c++ @$(CC) -MM -MD $(CXXFLAGS) $< %.d : %.cp @$(CC) -MM -MD $(CXXFLAGS) $< %.d : %.cxx @$(CC) -MM -MD $(CXXFLAGS) $< # Rules for producing the objects. #—— objs : $(OBJS) %.o : %.c $(CC) -c $(CPPFLAGS) $(CFLAGS) $< %.o : %.C $(CXX) -c $(CPPFLAGS) $(CXXFLAGS) $< %.o : %.cc $(CXX) -c $(CPPFLAGS) $(CXXFLAGS) $< %.o : %.cpp $(CXX) -c $(CPPFLAGS) $(CXXFLAGS) $< %.o : %.CPP $(CXX) -c $(CPPFLAGS) $(CXXFLAGS) $< %.o : %.c++ $(CXX -c $(CPPFLAGS) $(CXXFLAGS) $< %.o : %.cp $(CXX) -c $(CPPFLAGS) $(CXXFLAGS) $< %.o : %.cxx $(CXX) -c $(CPPFLAGS) $(CXXFLAGS) $< # Rules for producing the executable. #—— $(PROGRAM) : $(OBJS) ifeq ($(strip $(SRCEXTS)), .c) # C file $(CC) -o $(PROGRAM) $(OBJS) $(LDFLAGS) else # C++ file $(CXX) -o $(PROGRAM) $(OBJS) $(LDFLAGS) endif -include $(DEPS) rebuild: clean all clean : @$(RM) *.o *.d cleanall: clean @$(RM) $(PROGRAM) $(PROGRAM)。exe ### End of the Makefile ## Suggestions are welcome ## All rights reserved ### ###############################################################################
下面提供兩個(gè)例子來(lái)具體說(shuō)明上面 Makefile 的用法。
[color=darkred]例一 Hello World 程序[/color]
這個(gè)程序的功能是輸出 Hello, world! 這樣一行文字。由 hello.h、hello.c、main.cxx 三個(gè)文件組成。前兩個(gè)文件是 C 程序,后一個(gè)是 C++ 程序,因此這是一個(gè) C 和 C++ 混編程序。
/* File name: hello.h * C header file */ #ifndef HELLO_H #define HELLO_H #ifdef __cplusplus extern "C" { #endif void print_hello(); #ifdef __cplusplus } #endif #endif
/* File name: hello.c * C source file. */ #include "hello.h" #include <stdio.h> void print_hello() { puts( "Hello, world!" ); }
/* File name: main.cxx * C++ source file. */ #include "hello.h" int main() { print_hello(); return 0; }
建立一個(gè)新的目錄,然后把這三個(gè)文件拷貝到目錄中,也把 Makefile 文件拷貝到目錄中。之后,對(duì) Makefile 的相關(guān)項(xiàng)目進(jìn)行如下設(shè)置:PROGRAM := hello # 設(shè)置運(yùn)行程序名 SRCDIRS := . # 源程序位于當(dāng)前目錄下 SRCEXTS := .c .cxx # 源程序文件有 .c 和 .cxx 兩種類(lèi)型 CFLAGS := -g # 為 C 目標(biāo)程序包含 GDB 可用的調(diào)試信息 CXXFLAGS := -g # 為 C++ 目標(biāo)程序包含 GDB 可用的調(diào)試信息
由于這個(gè)簡(jiǎn)單的程序只使用了 C 標(biāo)準(zhǔn)庫(kù)的函數(shù)(puts),所以對(duì)于 CFLAGS 和 CXXFLAGS 沒(méi)有過(guò)多的要求,LDFLAGS 和 CPPFLAGS 選項(xiàng)也無(wú)需設(shè)置。
經(jīng)過(guò)上面的設(shè)置之后,執(zhí)行 make 命令就可以編譯程序了。如果沒(méi)有錯(cuò)誤出現(xiàn)的話,。/hello 就可以運(yùn)行程序了。
如果修改了源程序的話,可以看到只有和修改有關(guān)的源文件被編譯。也可以再為程序添加新的源文件,只要它們的擴(kuò)展名是已經(jīng)在 Makefile 中設(shè)置過(guò)的,那么就沒(méi)有必要修改 Makefile.
[color=darkred]例二 GTK+ 版 Hello World 程序[/color]
這個(gè) GTK+ 2.0 版的 Hello World 程序可以從下面的網(wǎng)址上得到:http://www.gtk.org/tutorial/c58.html#SEC-HELLOWORLD.當(dāng)然,要編譯 GTK+ 程序,還需要你的系統(tǒng)上已經(jīng)安裝好了 GTK+.
跟第一個(gè)例子一樣,單獨(dú)創(chuàng)建一個(gè)新的目錄,把上面網(wǎng)頁(yè)中提供的程序保存為 main.c 文件。對(duì) Makefile 做如下設(shè)置:PROGRAM := hello # 設(shè)置運(yùn)行程序名 SRCDIRS := . # 源程序位于當(dāng)前目錄下 SRCEXTS := .c # 源程序文件只有 .c 一種類(lèi)型 CFLAGS := `pkg-config ——cflags gtk+-2.0` # CFLAGS LDFLAGS := `pkg-config ——libs gtk+-2.0` # LDFLAGS
這是一個(gè) C 程序,所以 CXXFLAGS 沒(méi)有必要設(shè)置——即使被設(shè)置了也不會(huì)被使用。
編譯和連接 GTK+ 庫(kù)所需要的 CFLAGS 和 LDFLAGS 由 pkg-config 程序自動(dòng)產(chǎn)生。
現(xiàn)在就可以運(yùn)行 make 命令編譯、。/hello 執(zhí)行這個(gè) GTK+ 程序了。
多繼承可以看作是單繼承的擴(kuò)展。所謂多繼承是指派生類(lèi)具有多個(gè)基類(lèi),派生類(lèi)與每個(gè)基類(lèi)之間的關(guān)系仍可看作是一個(gè)單繼承。
多繼承下派生類(lèi)的定義格式如下:
class <派生類(lèi)名>:<繼承方式1><基類(lèi)名1>,<繼承方式2><基類(lèi)名2>,…
{
<派生類(lèi)類(lèi)體>
};
其中,<繼承方式1>,<繼承方式2>,…是三種繼承方式:public、private、protected之一。例如:
class A
{
…
};
class B
{
…
};
class C : public A, public B
{
…
};
其中,派生類(lèi)C具有兩個(gè)基類(lèi)(類(lèi)A和類(lèi)B),因此,類(lèi)C是多繼承的。按照繼承的規(guī)定,派生類(lèi)C的成員包含了基類(lèi)A, B中成員以及該類(lèi)本身的成員。
多繼承的構(gòu)造函數(shù)
在多繼承的情況下,派生類(lèi)的構(gòu)造函數(shù)格式如下:
<派生類(lèi)名>(<總參數(shù)表>):<基類(lèi)名1>(<參數(shù)表1>),<基類(lèi)名2>(<參數(shù)表2>),…
<子對(duì)象名>(<參數(shù)表n+1>),…
{
<派生類(lèi)構(gòu)造函數(shù)體>
}
其中,<總參數(shù)表>中各個(gè)參數(shù)包含了其后的各個(gè)分參數(shù)表。
多繼承下派生類(lèi)的構(gòu)造函數(shù)與單繼承下派生類(lèi)構(gòu)造函數(shù)相似,它必須同時(shí)負(fù)責(zé)該派生類(lèi)所有基類(lèi)構(gòu)造函數(shù)的調(diào)用。同時(shí),派生類(lèi)的參數(shù)個(gè)數(shù)必須包含完成所有基類(lèi)初始化所需的參數(shù)個(gè)數(shù)。
派生類(lèi)構(gòu)造函數(shù)執(zhí)行順序是先執(zhí)行所屬基類(lèi)的構(gòu)造函數(shù),再執(zhí)行派生類(lèi)本身構(gòu)造函數(shù),處于同一層次的各基類(lèi)構(gòu)造函數(shù)的執(zhí)行順序取決于定義派生類(lèi)時(shí)所指定的各基類(lèi)順序,與派生類(lèi)構(gòu)造函數(shù)中所定義的成員初始化列表的各項(xiàng)順序無(wú)關(guān)。也就是說(shuō),執(zhí)行基類(lèi)構(gòu)造函數(shù)的順序取決于定義派生類(lèi)時(shí)基類(lèi)的順序。可見(jiàn),派生類(lèi)構(gòu)造函數(shù)的成員初始化列表中各項(xiàng)順序可以任意地排列。
下面通過(guò)一個(gè)例子來(lái)說(shuō)明派生類(lèi)構(gòu)造函數(shù)的構(gòu)成及其執(zhí)行順序。
#include <iostream.h>
class B1
{
public:
B1(int i)
{
b1 = i;
cout《"構(gòu)造函數(shù) B1."《i《 endl;
}
void print()
{
cout《"B1.print()"《b1《endl;
}
private:
int b1;
};
class B2
{
public:
B2(int i)
{
b2 = i;
cout《"構(gòu)造函數(shù) B2."《i《 endl;
}
void print()
{
cout《"B2.print()"《b2《endl;
}
private:
int b2;
};
class B3
{
public:
B3(int i)
{
b3 = i;
cout《"構(gòu)造函數(shù) B3."《i《endl;
}
int getb3()
{
return b3;
}
private:
int b3;
};
class A : public B2, public B1
{
public:
A(int i, int j, int k, int l):B1(i), B2(j), bb(k)
{
a = l;
cout《"構(gòu)造函數(shù) A."《a《endl;
}
void print()
{
B1::print();
B2::print();
cout《"A.print()"《a《","《bb.getb3()《endl;
}
private:
int a;
B3 bb;
};
void main()
{
A aa(1, 2, 3, 4);
aa.print();
}
該程序的輸出結(jié)果為:
構(gòu)造函數(shù) B2.2
構(gòu)造函數(shù) B1.1
構(gòu)造函數(shù) B3.3
構(gòu)造函數(shù) A.4
B1.print()。1
B2.print()2
A.print()4, 3
在該程序中,作用域運(yùn)算符::用于解決作用域沖突的問(wèn)題。在派生類(lèi)A中的print()函數(shù)的定義中,使用了B1::print;和B2::print();語(yǔ)句分別指明調(diào)用哪一個(gè)類(lèi)中的print()函數(shù),這種用法應(yīng)該學(xué)會(huì)。
二義性問(wèn)題
一般說(shuō)來(lái),在派生類(lèi)中對(duì)基類(lèi)成員的訪問(wèn)應(yīng)該是唯一的,但是,由于多繼承情況下,可能造成對(duì)基類(lèi)中某成員的訪問(wèn)出現(xiàn)了不唯一的情況,則稱(chēng)為對(duì)基類(lèi)成員訪問(wèn)的二義性問(wèn)題。
實(shí)際上,在上例已經(jīng)出現(xiàn)過(guò)這一問(wèn)題,回憶一下上例中,派生類(lèi)A的兩基類(lèi)B1和B2中都有一個(gè)成員函數(shù)print()。如果在派生類(lèi)中訪問(wèn) print()函數(shù),到底是哪一個(gè)基類(lèi)的呢?于是出現(xiàn)了二義性。但是在上例中解決了這個(gè)問(wèn)題,其辦法是通過(guò)作用域運(yùn)算符::進(jìn)行了限定。如果不加以限定,則會(huì)出現(xiàn)二義性問(wèn)題。
下面再舉一個(gè)簡(jiǎn)單的例子,對(duì)二義性問(wèn)題進(jìn)行深入討論。例如:
class A
{
public:
void f();
};
class B
{
public:
void f();
void g();
};
class C : public A, public B
{
public:
void g();
void h();
};
如果定義一個(gè)類(lèi)C的對(duì)象c1:
C c1;
則對(duì)函數(shù)f()的訪問(wèn)
c1.f();
便具有二義性:是訪問(wèn)類(lèi)A中的f(),還是訪問(wèn)類(lèi)B中的f()呢?
解決的方法可用前面用過(guò)的成員名限定法來(lái)消除二義性,例如:
c1.A::f();
或者
c1.B::f();
但是,最好的解決辦法是在類(lèi)C中定義一個(gè)同名成員f(),類(lèi)C中的f()再根據(jù)需要來(lái)決定調(diào)用A::f(),還是B::f(),還是兩者皆有,這樣,c1.f()將調(diào)用C::f()。
同樣地,類(lèi)C中成員函數(shù)調(diào)用f()也會(huì)出現(xiàn)二義性問(wèn)題。例如:
viod C::h()
{
f();
}
這里有二義性問(wèn)題,該函數(shù)應(yīng)修改為:
void C::h()
{
A::f();
}
或者
void C::h()
{
B::f();
}
或者
void C::f()
{
A::f();
B::f();
}
另外,在前例中,類(lèi)B中有一個(gè)成員函數(shù)g(),類(lèi)C中也有一個(gè)成員函數(shù)g()。這時(shí),
c1.g();
不存在二義性,它是指C::g(),而不是指B::g()。因?yàn)檫@兩個(gè)g()函數(shù),一個(gè)出現(xiàn)在基類(lèi)B,一個(gè)出現(xiàn)在派生類(lèi)C,規(guī)定派生類(lèi)的成員將支配基類(lèi)中的同名成員。因此,上例中類(lèi)C中的g()支配類(lèi)B中的g(),不存在二義性,可選擇支配者的那個(gè)名字。
當(dāng)一個(gè)派生類(lèi)從多個(gè)基類(lèi)派生類(lèi),而這些基類(lèi)又有一個(gè)共同的基類(lèi),則對(duì)該基類(lèi)中說(shuō)明的成員進(jìn)行訪問(wèn)時(shí),也可能會(huì)出現(xiàn)二義性。例如:
class A
{
public:
int a;
};
class B1 : public A
{
private:
int b1;
};
class B2 : public A
{
private:
int b2;
};
class C : public B1, public B2
{
public:
int f();
private:
int c;
};
已知:C c1;
下面的兩個(gè)訪問(wèn)都有二義性:
c1.a;
c1.A::a;
而下面的兩個(gè)訪問(wèn)是正確的:
c1.B1::a;
c1.B2::a;
類(lèi)C的成員函數(shù)f()用如下定義可以消除二義性:
int C::f()
{
retrun B1::a + B2::a;
}
由于二義性的原因,一個(gè)類(lèi)不可以從同一個(gè)類(lèi)中直接繼承一次以上,例如:
class A : public B, public B
{
…
}
這是錯(cuò)誤的。
呵呵,最近幾天我有個(gè)小發(fā)現(xiàn),那就是老白沒(méi)有來(lái)看過(guò)我的博客了,說(shuō)真的蠻希望他能來(lái)的,
他不來(lái)有點(diǎn)讓我失望,畢竟我也關(guān)注他很長(zhǎng)一段時(shí)間了,當(dāng)然,不管他來(lái)不來(lái),我自己的工作還是得繼續(xù)下去的嘛中,對(duì)不對(duì),這里我將簡(jiǎn)單對(duì)于將n個(gè)實(shí)數(shù)由大到小排序做個(gè)介紹吧。
n個(gè)實(shí)數(shù)用數(shù)組a描述。
本例提供用選擇排序方法與冒泡排序方法分別實(shí)現(xiàn)n個(gè)實(shí)數(shù)由大到小排序的函數(shù)。
算法一:選擇排序。
選擇排序需反復(fù)進(jìn)行求最大值與交換兩個(gè)數(shù)這兩種基本操作。
對(duì)a[o]、a[1]、…、a[n一1]由大到小排序:先求所有數(shù)的最大值,然后將最大值與a[o]進(jìn)行交換;再求a[1]~a[n一1]這些數(shù)的最大值,然后將最大值與a[1]進(jìn)行交換;再求a[2]~a[n一1]這些數(shù)的最大值,然后將最大值與a[2]進(jìn)行交換……;最后求a[n一2]與a[n一1]這些數(shù)的最大值,然后將最大值與a[n一2]進(jìn)行交換。如此,經(jīng)過(guò)n一1輪處理完成排序,本文首發(fā)中國(guó)自學(xué)編程網(wǎng)。
程序如下:
void sortl(a,n)/*選擇排序函數(shù)*/
float a[];
int n:
{int k,i,j;/*k最大值下標(biāo),i,j循環(huán)控制變量*/
float t;/*中間變量,用于兩個(gè)數(shù)的交換*/
for(i=0;i<n-1;i++)
{k=i;/*求最大值下標(biāo)*/
for(j=i+1}j<n;j++)
if(a[j]>a[k])k=j
t=a[i];a[i]一a[k];a[k]=t;/*進(jìn)行交換*/
}
}
算法二:冒泡排序。
冒泡排序需反復(fù)進(jìn)行相鄰兩個(gè)數(shù)的比較與交換兩個(gè)數(shù)這兩種基本操作。對(duì)相鄰的兩個(gè)數(shù)進(jìn)行比較時(shí),如果后面的數(shù)大于前面的數(shù),將這兩個(gè)數(shù)進(jìn)行交換,大的數(shù)往前冒。將所有相鄰的兩個(gè)安全閥數(shù)比較一遍,稱(chēng)為一輪比較。如果進(jìn)行一輪比較無(wú)交換,本文首發(fā)中國(guó)自學(xué)編程網(wǎng)排序完成。
有無(wú)交換用一標(biāo)志變量描述,一輪比較用for循環(huán)完成,整個(gè)排序利用標(biāo)志變量用條件循環(huán)控制。
程序如下:
void sort2(a,n)/*冒泡排序函數(shù)*/
float a[];
int n;
{int i;/*一輪比較的循環(huán)控制變量*/
int flag;/*標(biāo)志變量,為1有交換,為0無(wú)交換*/
float t;/*中間變量,用于兩個(gè)數(shù)的交換*/
do
{flag=O;/*先假定無(wú)交換,已排好序*/
for(i=O;i<n一2; i++)
if(a[i+1]>a[i])
{t=a[i];a[i]=a[i+1];a[i+1]=t;/*進(jìn)行交換*/
flag=1;/*有交換,標(biāo)志變量的值改變?yōu)?*/
}
}while(flag==1);
)
由小到大排序請(qǐng)讀者作類(lèi)似考慮。呵呵,差不多了,如果有不當(dāng)之處,請(qǐng)朋友們指正啊---
位段以位為單位定義結(jié)構(gòu)體(或共用體)中成員所占存儲(chǔ)空間的長(zhǎng)度。
含有位段的結(jié)構(gòu)體類(lèi)型稱(chēng)為位段結(jié)構(gòu)。
位段結(jié)構(gòu)也是一種結(jié)構(gòu)體類(lèi)型,只不過(guò)其中含有以位為單位定義存儲(chǔ)長(zhǎng)度的整數(shù)類(lèi)型位段成員。采用位段結(jié)構(gòu)既節(jié)省存儲(chǔ)空間,又可方便操作。
位段結(jié)構(gòu)中位段的定義格式為:
unsigned <成員名>:<二進(jìn)制位數(shù)>
例如:
struct bytedata
{unsigned a:2; /*位段a,占2位*/
unsigned:6; /*無(wú)名位段,占6位,但不能訪問(wèn)*/
unsigned:0; /*無(wú)名位段,占0位,表下一位段從下一字邊界開(kāi)始*/
unsigned b:10; /*位段b,占10位*/
int i; /*成員i,從下一字邊界開(kāi)始*/
}data;
位段數(shù)據(jù)的引用:
同結(jié)構(gòu)體成員中的數(shù)據(jù)引用一樣,但應(yīng)注意位段的最大取值范圍不要超出二進(jìn)制位數(shù)定的范圍,否則超出部分會(huì)丟棄。
例如:data.a=2; 但 data.a=10;就超出范圍(a占2位,最大3)
關(guān)于位段數(shù)據(jù),注意以下幾點(diǎn):
(1)一個(gè)位段必須存儲(chǔ)在同一存儲(chǔ)單元(即字)之中,不能跨兩個(gè)單元。如果其單元空間不夠,則剩余空間不用,從下一個(gè)單元起存放該位段。
(2)可以通過(guò)定義長(zhǎng)度為0的位段的方式使下一位段從下一存儲(chǔ)單元開(kāi)始。
(3)可以定義無(wú)名位段。
(4)位段的長(zhǎng)度不能大于存儲(chǔ)單元的長(zhǎng)度。
(5)位段無(wú)地址,不能對(duì)位段進(jìn)行取地址運(yùn)算。
(6)位段可以以%d,%o,%x格式輸出。
(7)位段若出現(xiàn)在表達(dá)式中,將被系統(tǒng)自動(dòng)轉(zhuǎn)換成整數(shù)。
-------------------------------------------------------
C語(yǔ)言中用結(jié)構(gòu)實(shí)現(xiàn)位段--個(gè)人心血!值得一看哦!C語(yǔ)言中的結(jié)構(gòu)是有實(shí)現(xiàn)位段的能力的,噢!你問(wèn)它到底是什么形式是吧?這個(gè)問(wèn)題呆會(huì)給你答案。讓我們先看看位段的作用:位段是在字段的聲明后面加一個(gè)冒號(hào)以及一個(gè)表示字段位長(zhǎng)的整數(shù)來(lái)實(shí)現(xiàn)的。這種用法又被就叫作“深入邏輯元件的編程”,如果你對(duì)系統(tǒng)編程感興趣,那么這篇文章你就不應(yīng)該錯(cuò)過(guò)!
我把使用位段的幾個(gè)理由告訴大家:1、它能把長(zhǎng)度為奇數(shù)的數(shù)據(jù)包裝在一起,從而節(jié)省存儲(chǔ)的空間;2、它可以很方便地訪問(wèn)一個(gè)整型值的部分內(nèi)容。
首先我要提醒大家注意幾點(diǎn):1、位段成員只有三種類(lèi)型:int ,unsigned int 和signed int這三種(當(dāng)然了,int型位段是不是可以取負(fù)數(shù)不是我說(shuō)了算的,因?yàn)檫@是和你的編譯器來(lái)決定的。位段,位段,它是用來(lái)表示字段位長(zhǎng)(bit)的,它只有整型值,不會(huì)有7.2這種float類(lèi)型的,如果你說(shuō)有,那你就等于承認(rèn)了有7.2個(gè)人這個(gè)概念,當(dāng)然也沒(méi)有char這個(gè)類(lèi)型的);2、成員名后面的一個(gè)冒號(hào)和一個(gè)整數(shù),這個(gè)整數(shù)指定該位段的位長(zhǎng)(bit);3、許多編譯器把位段成員的字長(zhǎng)限制在一個(gè)int的長(zhǎng)度范圍之內(nèi);4、位段成員在內(nèi)存的實(shí)現(xiàn)是從左到右還是從右到左是由編譯器來(lái)決定的,但二者皆對(duì)。
下面我們就來(lái)看看,它到底是什么東西(我先假定大家的機(jī)器字長(zhǎng)為32位):
Struct WORD
{
unsigned int chara: 6:
unsigned int font : 7;
unsigned int maxsize : 19;
};
Struct WORD chone;
這一段是從我編寫(xiě)的一個(gè)文字格式化軟件摘下來(lái)的,它最多可以容納64(既我說(shuō)的unsigned int chara :6; 它總共是6位)個(gè)不同的字符值,可以處理128(既unsigned int font : 7 ;既2的7次方)種不同的字體,和2的19次方的單位長(zhǎng)度的字。大家都可以看到maxsize是19位,它是無(wú)法被一個(gè)short int 類(lèi)型的值所容納的,我們又可以看到其余的成員的長(zhǎng)度比char還小,這就讓我們想起讓他們共享32位機(jī)器字長(zhǎng),這就避免用一個(gè)32位的整數(shù)來(lái)表示maxsize的位段。怎么樣?還要注意的是剛才的那一段代碼在16位字長(zhǎng)的機(jī)器上是無(wú)法實(shí)現(xiàn)的,為什么?提醒你一下,看看上面提醒的第3點(diǎn),你會(huì)明白的!
你是不是發(fā)現(xiàn)這個(gè)東西沒(méi)有用啊?如果你點(diǎn)頭了,那你就錯(cuò)了!這么偉大的創(chuàng)造怎么會(huì)沒(méi)有用呢(你對(duì)系統(tǒng)編程不感興趣,相信你會(huì)改變這么一個(gè)觀點(diǎn)的)?磁盤(pán)控制器大家應(yīng)該知道吧?軟驅(qū)與它的通信我們來(lái)看看是怎么實(shí)現(xiàn)的下面是一個(gè)磁盤(pán)控制器的寄存器:
│←5→│←5→│←9→│←8→│←1→│←1→∣←1→∣←1→∣←1→∣
上面位段從左到右依次代表的含義為:5位的命令,5位的扇區(qū),9位的磁道,8位的錯(cuò)誤代碼,1位的HEAD LOADED,1位的寫(xiě)保護(hù),1位的DISK SPINNING,1位的錯(cuò)誤判斷符,還有1位的READY位。它要怎么來(lái)實(shí)現(xiàn)呢?你先自己寫(xiě)寫(xiě)看:
struct DISK_FORMAT
{
unsigned int command : 5;
unsigned sector : 5;
unsigned track : 9 ;
unsigned err_code : 8;
unsigned ishead_loaded : 1;
unsigned iswrit_protect : 1;
unsigned isdisk_spinning : 1;
unsigned iserr_ocur : 1;
undigned isready :1 ;
};
注:代碼中除了第一行使用了unsigned int 來(lái)聲明位段后就省去了int ,這是可行的,詳見(jiàn)ANCI C標(biāo)準(zhǔn)。
如果我們要對(duì)044c18bfH的地址進(jìn)行訪問(wèn)的話,那就這樣:
#define DISK ((struct DISK_FORMAT *)0x044c18bf)
DISK->sector=fst_sector;
DISK->track=fst_track;
DISK->command=WRITE;
當(dāng)然那些都是要宏定義的哦!
我們用位段來(lái)實(shí)現(xiàn)這一目的是很方便的,其實(shí)這也可以用移位或屏蔽來(lái)實(shí)現(xiàn),你嘗試過(guò)就知道哪個(gè)更方便了!
使用expat的原因很多,主要還是因?yàn)閑xpat更靈活。習(xí)慣了TinyXML,一開(kāi)始不太習(xí)慣expat,分析一下,其實(shí)很容易上手的。
1.回調(diào)函數(shù)
以下案例解析xml文件中的elment,attribute和text。expat使用回調(diào)方式返回xml數(shù)據(jù),解析器解析到一個(gè)element及其內(nèi)部屬性后,將調(diào)用事先設(shè)置好的函數(shù),同樣,當(dāng)element結(jié)束和text結(jié)束后,也會(huì)分別調(diào)用對(duì)應(yīng)的函數(shù)。
2.如何處理數(shù)據(jù)之間的包含關(guān)系
典型的方式是定義三個(gè)函數(shù)分別處理elment開(kāi)始(含屬性)、element結(jié)束和文本內(nèi)容。回調(diào)函數(shù)的第一個(gè)參數(shù)是自定義的,通常用于存儲(chǔ) XML文檔的上下文信息,用XML_SetUserData可以設(shè)置這個(gè)參數(shù),下例中傳遞一個(gè)整數(shù)指針,以便在每次回調(diào)時(shí)能知道該元素是第幾層元素。
該參數(shù)也可以是一個(gè)棧對(duì)象的地址,開(kāi)始一個(gè)元素時(shí),將新元素對(duì)應(yīng)的數(shù)據(jù)壓入堆棧,處理下一級(jí)元素時(shí),新元素是棧頂元素在子元素,然后處理完了繼續(xù)把該元素壓入堆棧,繼續(xù)下一級(jí)新的子元素。當(dāng)元素結(jié)束后,需要出棧,以便解析下個(gè)兄弟元素程時(shí)能取到父節(jié)點(diǎn)。
好啦,基本應(yīng)用還是很簡(jiǎn)單的,實(shí)際上Expat的API函數(shù)不多。
3.如何處理屬性
屬性通過(guò)ElementHandler回調(diào)函數(shù)傳入,這里有一個(gè)char** atts就是屬性,這是一個(gè)字符指針數(shù)組,如果有N個(gè)屬性,數(shù)組大小就是2*N+1,最后一個(gè)素組元素為空指針,奇數(shù)指針對(duì)應(yīng)屬性名稱(chēng),偶數(shù)指針對(duì)應(yīng)屬性值(字符串格式)。可以在一個(gè)循環(huán)中處理多個(gè)屬性,當(dāng)遇到空指針時(shí),表示沒(méi)有更多屬性了。
好啦,先看sample吧:
#include <stdio.h>
#include "expat.h"
#pragma warning(disable:4996)
#define XML_FMT_INT_MOD "l"
static void XMLCALL startElement(void *userData, const char *name, const char **atts)
{
int i;
int *depthPtr = (int *)userData;
for (i = 0; i < *depthPtr; i++)
printf(" ");
printf(name);
*depthPtr += 1;
for(i=0;atts[i]!=0;i+=2)
{
printf(" %s=%s",atts[i],atts[i+1]);
}
printf("\n");
}
static void XMLCALL endElement(void *userData, const char *name)
{
int *depthPtr = (int *)userData;
*depthPtr -= 1;
}
int main(int argc, char *argv[])
{
char buf[BUFSIZ]; XML_Parser parser = XML_ParserCreate(NULL);
int done; int depth = 0;
XML_SetUserData(parser, &depth);
XML_SetElementHandler(parser, startElement, endElement);
FILE* pFile= argc<2 ?stdin : fopen(argv[1],"rb");
do
{ int len = (int)fread(buf, 1, sizeof(buf), pFile);
done = len < sizeof(buf);
if (XML_Parse(parser, buf, len, done) == XML_STATUS_ERROR)
{
fprintf(stderr,"%s at line %" XML_FMT_INT_MOD "u\n",
XML_ErrorString(XML_GetErrorCode(parser)),
XML_GetCurrentLineNumber(parser));
return 1;
}
}
while (!done);
XML_ParserFree(parser);
fclose(pFile);
return 0;
}
4.其他ElementHanlder
expat還可以設(shè)置CData,Comment的handler,另外一些函數(shù)本人還沒(méi)使用過(guò),涉及到更多的xml標(biāo)準(zhǔn)的知識(shí),如果需要,可以參考官方的手冊(cè)。
一、摘要
JSON 的全稱(chēng)為:JavaScript Object Notation,顧名思義,JSON 是用于標(biāo)記 Javascript 對(duì)象的,JSON 官方的解釋為:JSON 是一種輕量級(jí)的數(shù)據(jù)傳輸格式。
本文并不詳細(xì)介紹 JSON 本身的細(xì)節(jié),旨在討論如何使用 C++ 語(yǔ)言來(lái)處理 JSON。關(guān)于 JSON 更具體的信息,可參見(jiàn) JSON 官網(wǎng):http://www.json.org。
二、本文選擇處理 JSON的 C++ 庫(kù)
本文選擇一個(gè)第三方庫(kù) jsoncpp 來(lái)解析 JSON。jsoncpp 是比較出名的 C++ JSON 解析庫(kù)。在 JSON 官網(wǎng)也是首推的。
下載地址為:http://sourceforge.net/projects/jsoncpp。本文使用的 jsoncpp 版本為:0.5.0。
三、jsoncpp 在 Windows 下的編譯
要使用第三方源碼庫(kù),第一步少不了的就是編譯,將源碼文件編譯成我們方便使用的動(dòng)態(tài)鏈接庫(kù)、靜態(tài)鏈接庫(kù)或者靜態(tài)導(dǎo)入庫(kù)[1]。
jsconcpp 進(jìn)行 JSON 解析的源碼文件分布在 include/json、src/lib_json 下。其實(shí) jsoncpp 源碼并不多,為了方便產(chǎn)品管理,此處沒(méi)必要將其編譯為動(dòng)態(tài)鏈接庫(kù)或者靜態(tài)導(dǎo)入庫(kù),所以我們選擇使用靜態(tài)鏈接庫(kù)[2]。
jsoncpp 已經(jīng)處理的很完善了,所有編譯選項(xiàng)都已經(jīng)配置好,打開(kāi)makefiles/vs71/jsoncpp.sln 便可以開(kāi)始編譯(默認(rèn)是使用 VS2003 編譯器的,打開(kāi)時(shí)直接按照 VS2005 提示轉(zhuǎn)換即可)。
四、jsoncpp 使用詳解
jsoncpp 主要包含三種類(lèi)型的 class:Value、Reader、Writer。jsoncpp 中所有對(duì)象、類(lèi)名都在 namespace Json 中,包含 json.h 即可。
Json::Value 只能處理 ANSI 類(lèi)型的字符串,如果 C++ 程序是用 Unicode 編碼的,最好加一個(gè) Adapt 類(lèi)來(lái)適配。
1、Value
Json::Value 是jsoncpp 中最基本、最重要的類(lèi),用于表示各種類(lèi)型的對(duì)象,jsoncpp 支持的對(duì)象類(lèi)型可見(jiàn) Json::ValueType 枚舉值。
可如下是用 Json::Value 類(lèi):
Json::Value json_temp; // 臨時(shí)對(duì)象,供如下代碼使用
json_temp["name"] = Json::Value("huchao");
json_temp["age"] = Json::Value(26);
Json::Value root; // 表示整個(gè) json 對(duì)象
root["key_string"] = Json::Value("value_string"); // 新建一個(gè) Key(名為:key_string),賦予字符串值:"value_string"。
root["key_number"] = Json::Value(12345); // 新建一個(gè) Key(名為:key_number),賦予數(shù)值:12345。
root["key_boolean"] = Json::Value(false); // 新建一個(gè) Key(名為:key_boolean),賦予bool值:false。
root["key_double"] = Json::Value(12.345); // 新建一個(gè) Key(名為:key_double),賦予 double 值:12.345。
root["key_object"] = Json_temp; // 新建一個(gè) Key(名為:key_object),賦予 json::Value 對(duì)象值。
root["key_array"].append("array_string"); // 新建一個(gè) Key(名為:key_array),類(lèi)型為數(shù)組,對(duì)第一個(gè)元素賦值為字符串:"array_string"。
root["key_array"].append(1234); // 為數(shù)組 key_array 賦值,對(duì)第二個(gè)元素賦值為:1234。
Json::ValueType type = root.type(); // 獲得 root 的類(lèi)型,此處為 objectValue 類(lèi)型。
注:跟C++ 不同,JavaScript 數(shù)組可以為任意類(lèi)型的值,所以 jsoncpp 也可以。
如上幾個(gè)用法已經(jīng)可以滿足絕大部分 json 應(yīng)用了,當(dāng)然 jsoncpp 還有一些其他同能,比如說(shuō)設(shè)置注釋、比較 json 大小、交換 json 對(duì)象等,都很容易使用,大家自己嘗試吧。
2、Writer
如上說(shuō)了 Json::Value 的使用方式,現(xiàn)在到了該查看剛才賦值內(nèi)容的時(shí)候了,查看 json 內(nèi)容,使用 Writer 類(lèi)即可。
Jsoncpp 的 Json::Writer 類(lèi)是一個(gè)純虛類(lèi),并不能直接使用。在此我們使用 Json::Writer 的子類(lèi):Json::FastWriter、Json::StyledWriter、Json::StyledStreamWriter。
顧名思義,用 Json::FastWriter 來(lái)處理 json 應(yīng)該是最快的,下面我們來(lái)試試。
Json::FastWriter fast_writer;
std::cout << fast_writer.write(root) << std::endl;
輸出結(jié)果為:
{"key_array":["array_string",1234],"key_boolean":false,"key_double":12.3450,"key_number":12345,"key_object":{"age":26,"name":"huchao"},"key_string":"value_string"}
再次顧名思義,用 Json::StyledWriter 是格式化后的 json,下面我們來(lái)看看 Json::StyledWriter 是怎樣格式化的。
Json::StyledWriter styled_writer;
std::cout << styled_writer.write(root) << std::endl;
輸出結(jié)果為:
{
"key_array" : [ "array_string", 1234 ],
"key_boolean" : false,
"key_double" : 12.3450,
"key_number" : 12345,
"key_object" : {
"age" : 26,
"name" : "huchao"
},
"key_string" : "value_string"
}
3、Reader
Json::Reader 是用于讀取的,說(shuō)的確切點(diǎn),是用于將字符串轉(zhuǎn)換為 Json::Value 對(duì)象的,下面我們來(lái)看個(gè)簡(jiǎn)單的例子。
Json::Reader reader;
Json::Value json_object;
const char* json_document = "{\"age\" : 26,\"name\" : \"huchao\"}";
if (!reader.parse(json_document, json_object))
return 0;
std::cout << json_object["name"] << std::endl;
std::cout << json_object["age"] << std::endl;
輸出結(jié)果為:
"huchao"
26
可見(jiàn),上述代碼已經(jīng)解析出了 json 字符串。
當(dāng)你涉及到C/
C++的核心編程的時(shí)候,你會(huì)無(wú)止境地與內(nèi)存管理打交道。這些往往會(huì)使人受盡折磨。所以如果你想深入C/
C++編程,你必須靜下心來(lái),好好苦一番。
現(xiàn)在我們將討論C/C++里我認(rèn)為哪一本書(shū)都沒(méi)有完全說(shuō)清楚,也是涉及概念細(xì)節(jié)最多,語(yǔ)言中最難的技術(shù)之一的動(dòng)態(tài)內(nèi)存的傳遞。并且在軟件開(kāi)發(fā)中很多專(zhuān)業(yè)人員并不能寫(xiě)出相關(guān)的合格的代碼。
一、引入
看下面的例子,這是我們?cè)诰帉?xiě)庫(kù)函數(shù)或者項(xiàng)目?jī)?nèi)的共同函數(shù)經(jīng)常希望的。
void MyFunc(char *pReturn, size_t size)
{………
pReturn = (char *)malloc(sizeof(char) * num);………
}我們可以很明顯地看出代碼作者的意圖,他想在函數(shù)調(diào)用處聲明一個(gè)指針 char *pMyReturn=NULL;然后調(diào)用MyFunc處理并返回一段長(zhǎng)度為size的一段動(dòng)態(tài)內(nèi)存。
那么作者能達(dá)到預(yù)期的效果嗎?
那么我可以告訴作者,他的程序在編譯期很幸運(yùn)地通過(guò)了,可是在運(yùn)行期他的程序崩潰終止。原因何在,是他觸犯了系統(tǒng)不可侵犯的條款:錯(cuò)誤地操作內(nèi)存。
二、內(nèi)存操作及問(wèn)題相關(guān)知識(shí)點(diǎn)
為了能徹底解決動(dòng)態(tài)內(nèi)存?zhèn)鬟f的問(wèn)題,我們先回顧一下內(nèi)存管理的知識(shí)要點(diǎn)。
(1)內(nèi)存分配方式有三種:
從靜態(tài)存儲(chǔ)區(qū)域分配。內(nèi)存在程序編譯的時(shí)候就已經(jīng)分配好,這塊內(nèi)存在程序的整個(gè)運(yùn)行期間都存在。例如全局變量,static變量。
在棧上創(chuàng)建。在執(zhí)行函數(shù)時(shí),函數(shù)內(nèi)局部變量的存儲(chǔ)單元都可以在棧上創(chuàng)建,函數(shù)執(zhí)行結(jié)束時(shí)這些存儲(chǔ)單元自動(dòng)被釋放。棧內(nèi)存分配運(yùn)算內(nèi)置于處理器的指令集中,效率很高,但是分配的內(nèi)存容量有限。
從堆上分配,亦稱(chēng)動(dòng)態(tài)內(nèi)存分配。程序在運(yùn)行的時(shí)候用malloc或new申請(qǐng)任意多少的內(nèi)存,程序員自己負(fù)責(zé)在何時(shí)用free或delete釋放內(nèi)存。動(dòng)態(tài)內(nèi)存的生存期由我們決定,使用非常靈活。
(2)指針的操作流程
申請(qǐng)并初始化或設(shè)置為空:
int *pInt=NULL;開(kāi)辟空間或者使其指向?qū)ο螅?/p>
pInt=new Int(3);或者int i=3;pint=&i;用指針(更確切地說(shuō)是操作內(nèi)存,在使用之前加if(pint!=NULL)或者assert(pInt!=NULL)后再使用,以防內(nèi)存申請(qǐng)失敗的情況下使用指針):
if(p!=NULL) {use pint};釋放使用完的內(nèi)存
free(pInt);置指針為空
pInt=NULL;(避免野指針的出現(xiàn))
(3)在函數(shù)的參數(shù)傳遞中,編譯器總是要為函數(shù)的每個(gè)參數(shù)制作臨時(shí)副本,如果參數(shù)為p的話,那么編譯器會(huì)產(chǎn)生p的副本_p,使_p=p; 如果函數(shù)體內(nèi)的程序修改了_p的內(nèi)容,就導(dǎo)致參數(shù)p的內(nèi)容作相應(yīng)的修改。這就是指針可以用作輸出參數(shù)的原因。
三、問(wèn)題分析
根據(jù)上面的規(guī)則我們可以很容易分析例子中失敗的原因。
void MyFunc(char *pReturn, size_t size)
{………
pReturn = (char *)malloc(sizeof(char) * num);………
} void main(void){ char *pMyReturn=NULL;MyFunc(pMyReturn,10);}在MyFunc(char *pReturn, size_t size)中_pMyReturn真實(shí)地申請(qǐng)到了內(nèi)存, pMyReturn申請(qǐng)了新的內(nèi)存,只是把_pMyReturn 所指的內(nèi)存地址改變了,但是pMyReturn絲毫未變。所以函數(shù)MyFunc并不能輸出任何東西。事實(shí)上,每執(zhí)行一次MyFunc就會(huì)泄露一塊內(nèi)存,因?yàn)闆](méi)有用free釋放內(nèi)存。
四、問(wèn)題解決方案
函數(shù)間傳遞動(dòng)態(tài)數(shù)據(jù)我們可以有三種解決方法。
方法一:如果我們是用C++編程,我們可以很方便地利用引用這個(gè)技術(shù)。我也極力推薦你用引用,因?yàn)樗鼤?huì)使你少犯一些錯(cuò)誤。以下是一個(gè)例子。
void MyFunc(char* &pReturn,size_t size){ pReturn=(char*)malloc(size);memset(pReturn,0x00,size);if(size>=13)
strcpy(pReturn,"Hello World!");}
void main(){ char *pMyReturn=NULL;MyFunc(pMyReturn,15);if(pMyReturn!=NULL)
{ char *pTemp=pMyReturn;while(*pTemp!=''\0'')
cout<<*pTemp++;pTemp=NULL;strcpy(pMyReturn,"AAAAAAAA");free(pMyReturn);pMyReturn=NULL;}方法二:利用二級(jí)指針
void MyFunc (char ** pReturn, size_t size)
{ * pReturn = (char *)malloc(size);} void main(void)
{ char * pMyReturn = NULL;MyFunc (&pMyReturn, 100);// 注意參數(shù)是 & pMyReturn if(pMyReturn!=NULL){ strcpy(pMyReturn, "hello");cout<< pMyReturn << endl;free(pMyReturn);pMyReturn=NULL;}}為什么二級(jí)指針就可以了。原因通過(guò)函數(shù)傳遞規(guī)則可以很容易地分析出來(lái)。我們將& pMyReturn傳遞了進(jìn)去,就是將雙重指針的內(nèi)容傳遞到了函數(shù)中。函數(shù)過(guò)程利用改變指針的內(nèi)容,這樣pMyReturn很明顯指向了開(kāi)辟的內(nèi)存 .
方法三:用函數(shù)返回值來(lái)傳遞動(dòng)態(tài)內(nèi)存
char * MyFunc (void)
{ char *p =new char[20];memset(p,0x00,sizeof(p));return p;} void main(void)
{ char *str = NULL;str = MyFunc();if(str!=NULL)
{ strcpy(str,"Hello,baby");cout<< str << endl;free(str);str=NULL;}請(qǐng)注意的是函數(shù)寫(xiě)成這樣的話,你是不能返回什么動(dòng)態(tài)內(nèi)存的,因?yàn)閜指向的是字符串常量。內(nèi)存在位于靜態(tài)存儲(chǔ)區(qū)上分配,你無(wú)法改變。(你想要得到動(dòng)態(tài)內(nèi)存我們一定要看到malloc或者new)。
char * MyFunc (void)
{ char *p =“Hello World”
return p;}結(jié)束語(yǔ)
操作內(nèi)存是C/C++一個(gè)難點(diǎn),我們作為專(zhuān)業(yè)的軟件開(kāi)發(fā)人員。應(yīng)該深入理解并能靈活地掌握指針和內(nèi)存的操作。