基本上每門語(yǔ)言都是用"helloworld"作為她的第一講,C語(yǔ)言也不例外。
傳統(tǒng)HelloWorld
傳統(tǒng)的教材都是讓你安裝一種IDE集成環(huán)境,然后照例子敲入代碼,按下ctrl+r之類的運(yùn)行程序,感受一下運(yùn)行的結(jié)果。HelloWorld程序如下:
- 在編輯器中敲入:
#include <stdlib.h>
#include <stdio.h>
void main() {
printf("Hello World\n");
}
- 按下Ctrl+r運(yùn)行,當(dāng)然我沒(méi)有IDE環(huán)境,就用Linux終端代替一下了哈。如下:
- 當(dāng)然這里有幾個(gè)問(wèn)題
- 當(dāng)時(shí)你其實(shí)不知道這個(gè)程序是怎么被編譯出來(lái)的。
- 當(dāng)時(shí)你也不知道她在什么環(huán)境下運(yùn)行的。
- 當(dāng)時(shí)你肯定也不會(huì)去想,我這樣寫(xiě)在其它計(jì)算機(jī)上能運(yùn)行嗎?
- 現(xiàn)在你肯定也沒(méi)有考慮過(guò),我能不能換個(gè)花樣玩玩呢?
老生常談
- 為何我說(shuō)一個(gè)gcc程序和一個(gè)編輯器vim程序就是c的開(kāi)發(fā)環(huán)境呢?而傳統(tǒng)的書(shū)籍,特別是國(guó)內(nèi)的c程序數(shù)據(jù),以來(lái)就讓你裝一個(gè)大得一逼的IDE環(huán)境,以前是vb6.0幾百M(fèi)B,現(xiàn)在是vs2015之類的好幾GB。而我所說(shuō)的gcc、vim充其量就幾個(gè)MB。因?yàn)镮DE環(huán)境集成了太多功能了,編輯、編譯、調(diào)試、自動(dòng)補(bǔ)全、語(yǔ)法錯(cuò)誤提示等等。我一般不用這樣環(huán)境,初學(xué)者更不應(yīng)該用,她會(huì)使你太依賴IDE,不了解原理,也少了很多樂(lè)趣。
- 其實(shí)所有程序都是編譯器或者解釋器讀取一個(gè)純文本中的代碼,然后對(duì)要么生成目標(biāo)二進(jìn)制文件(編譯型語(yǔ)言),要么直接就運(yùn)行(解釋型語(yǔ)言)了。對(duì)于C語(yǔ)言,當(dāng)然是編譯后,生成有特定格式二進(jìn)制文件,在計(jì)算終端中運(yùn)行,終端也是個(gè)程序,她是操作系統(tǒng)的一部分,操作系統(tǒng)也是程序(一兩句話說(shuō)不清這個(gè)關(guān)系>_<)。像C這樣的語(yǔ)言,有語(yǔ)法解釋,中間文件編譯,目標(biāo)程序鏈接這三部曲構(gòu)成,當(dāng)然還可以細(xì)分。語(yǔ)法解釋主要是判斷語(yǔ)法正確與否,然后是將
#include<stdlib.h>
這樣的語(yǔ)句進(jìn)行預(yù)處理生成最終的代碼源文件,然后編譯成與特定類型的操作系統(tǒng)相關(guān)的目標(biāo)代碼,這個(gè)目標(biāo)代碼其實(shí)可以在只要與當(dāng)時(shí)生產(chǎn)的操作系統(tǒng)類型相同的操作系統(tǒng)中重復(fù)使用的,然后是鏈接成目標(biāo)文件,這個(gè)文件大多數(shù)也可以在相同的操作系統(tǒng)中直接使用,但是由于有的程序會(huì)依賴特定的庫(kù),所以出表現(xiàn)出不能運(yùn)行成功的情況罷了。比如剛才的helloworld程序在類Unix系統(tǒng)中,使用如下操作生成可執(zhí)行文件:
然后是運(yùn)行:
可以執(zhí)行環(huán)境就是開(kāi)啟一個(gè)終端程序,然后./a.out
運(yùn)行。
- 這段代碼在絕大多數(shù)類Unix操作系統(tǒng)中都被編譯運(yùn)行,甚至這個(gè)二進(jìn)制文件如果在內(nèi)核相同操作系統(tǒng)也可以直接運(yùn)行,而不需要重新編譯。但是你可能注意到這個(gè)程序在編譯是產(chǎn)生了警告,因?yàn)樗膍ain()入口函數(shù)不是標(biāo)準(zhǔn)的,即不是可移植的。
守則一:一個(gè)負(fù)責(zé)的程序員編寫(xiě)程序要考慮可移植性
- 標(biāo)準(zhǔn)的可以移植性入口函數(shù)應(yīng)該是這樣的:
int main(int argc, const char *argv[]) { ... }
其中的形式參數(shù)的作用就是接收運(yùn)行時(shí)傳入的命令參數(shù),后面我們會(huì)討論到。
- 你真沒(méi)想過(guò)怎么將這段代碼換個(gè)花樣玩?
這個(gè)不行,作為一個(gè)程序員,腦洞太小不好,腦洞需要大開(kāi)的,有多大得開(kāi)多大。
換著花樣玩
使用全局復(fù)用
全局變量基本是沒(méi)種語(yǔ)言都支持的,即為全局,即是對(duì)所有人可見(jiàn)
-
有一天你的老板說(shuō),現(xiàn)在我們生意不好,我們要改程序輸出的內(nèi)容,發(fā)發(fā)牢騷,而不是友好的問(wèn)候。恰恰這要交給你來(lái)做,而且當(dāng)時(shí)沒(méi)有使用任何全局變量或著宏來(lái)代替這些輸出內(nèi)容,而且涉及的幾十個(gè)文件可能是零零散散的分布在好幾百個(gè)位置中,那么恭喜你,即使使用多文件文本替換也是挺麻煩的事,而且你總是要修改好幾十個(gè)文件。如果當(dāng)時(shí)使用全局變量也很好用修改的。
- 找一個(gè)這些文件都會(huì)引用的頭文件,比如叫著utils.h,添加如下外部變量聲明:
extern char g_SayHello[];
- 再任何一個(gè).c文件都可以,但是推薦還是utils.c中,這個(gè)就是規(guī)范,下次,接手項(xiàng)目的人要修改哪個(gè)文件.h有類似上面的外部引用,自然而然的就在對(duì)應(yīng)的.c中尋找其定義了:
char g_SayHello[] = "Kick the bucket!";
- 修改helloworld.c,其實(shí)這個(gè)名字不合適,最好加一個(gè)前綴,表面她含有入口函數(shù),main_helloworld.c:
...
#include "utils.h"
...
int main(int argc, const char *argv[]) {
printf("%s\n", g_SayHello);
return EXIT_SUCCESS;
}
- 加入新的源文件一起編譯、運(yùn)行如下:
[zhoukai@zhoukai-MBPR:tmp]$ gcc main_helloworld.c utils.c
[zhoukai@zhoukai-MBPR:tmp]$ ./a.out
Kick the bucket!
[zhoukai@zhoukai-MBPR:tmp]$
使用宏復(fù)用
宏是c語(yǔ)言強(qiáng)有力的特性之一,不使用宏,你會(huì)做很多不討喜的工作,但是濫用宏也會(huì)不討喜,所有任何東西都有利有弊。就像沒(méi)有壞人,就體現(xiàn)不出好人;沒(méi)有細(xì)菌這樣的微生物,滿世界都是尸體一樣。這個(gè)是一個(gè)哲學(xué)問(wèn)題???
- 繼續(xù)上面的情景。你好不容易完成了需求,結(jié)果你老板說(shuō),我們需要在不同操作系統(tǒng)上讓運(yùn)行程序發(fā)不同的牢騷???,雖然你心中有一萬(wàn)匹草泥馬在狂奔,但為了工資忍了吧。顯然上面的代碼在不同的計(jì)算機(jī)上發(fā)布時(shí),如果在編譯后需要讓程序運(yùn)行時(shí)顯現(xiàn)一些不同的東西,是需要修改源代碼的,很不方便,做這樣的事情,宏的優(yōu)點(diǎn)就體現(xiàn)出來(lái)了,因?yàn)樵诰幾g時(shí),可以給定參數(shù)定義一個(gè)宏讓源代碼相同的程序有不同的行為:
- 還是utils.h,添加如下宏:
//稍作解釋,下面的宏定義是關(guān)聯(lián)預(yù)編譯條件宏使用,即如果沒(méi)有定義宏,則定一個(gè)默認(rèn)的宏
#ifndef SAY_HELLO
#define SAY_HELLO "Kick the bucket!"
#endif
- 修改main_helloworld.c:
int main(int argc, const char *argv[]) {
printf("%s\n", SAY_HELLO);
return EXIT_SUCCESS;
}
- 編譯不同的行為的程序:
[zhoukai@zhoukai-MBPR:tmp]$ gcc main_helloworld.c utils.c -DSAY_HELLO=\"Drop\ dead\!\" -o a.out
[zhoukai@zhoukai-MBPR:tmp]$ gcc main_helloworld.c utils.c -DSAY_HELLO=\"Go\ to\ hell\!\" -o ab.out
[zhoukai@zhoukai-MBPR:tmp]$ gcc main_helloworld.c utils.c -DSAY_HELLO=\"Damn\ you\!\" -o abc.out
[zhoukai@zhoukai-MBPR:tmp]$ ./a.out
Drop dead!
[zhoukai@zhoukai-MBPR:tmp]$ ./ab.out
Go to hell!
[zhoukai@zhoukai-MBPR:tmp]$ ./abc.out
Damn you!
[zhoukai@zhoukai-MBPR:tmp]$
稍作解釋,gcc最簡(jiǎn)單的使用就是
gcc <源文件名>
,然后就會(huì)生成默認(rèn)的程序文件a.out,但是一般都希望又一個(gè)自定義的程序文件名,所以加上選項(xiàng)參數(shù)-o <目標(biāo)名>
;在不加-c <源文件名>
的情況下都是直接完成三部曲,生成可執(zhí)行文件的;其它還有很多可選的參數(shù),后面慢慢說(shuō)。
- 可以看到不同的程序使用同樣的源代碼編譯的,但是編譯時(shí)可以重定義宏,從而改變其行為。這里使用了可選參數(shù)-Dmacro="string"(加上''只是因?yàn)閟hell環(huán)境需要轉(zhuǎn)義雙引號(hào)),這樣可以將編譯時(shí)宏參數(shù)帶入預(yù)處理,從而替換默認(rèn)的宏參數(shù)。
結(jié)束語(yǔ)
為什么要寫(xiě)這么多,其實(shí)也不是高深的代碼。目的就是一個(gè),你在編寫(xiě)代碼的時(shí)候是否比別人多想了一步呢?是否考慮過(guò)代碼的可移植性呢?是否考慮過(guò)代碼的可復(fù)用性呢?是否考慮過(guò)代碼的可維護(hù)性呢?這些!都是一名合格的程序員應(yīng)該考慮的問(wèn)題。