上一節(jié),我們大致總覽了一個(gè)簡單C程序的框架,程序如下:
#include<stdio.h> /*引入頭文件*/ int main(void) /*一個(gè)簡單的C程序*/ { int number; /*定義個(gè)名字叫做number的變量*/ number=2014; /*給number賦一個(gè)值*/ printf("Hello ! I am dotcpp.com\n"); /*像屏幕打印一句話*/ printf("This year is %d\n",number); return 0; }
或許您已經(jīng)看懂了前面的敘述,但又覺得不能夠通透。誠然如此,麻雀雖小,五臟俱全,即使非常簡單的一個(gè)C程序,也包含著許多的知識(shí),這一節(jié),我們將從逐條語句出發(fā),去深入探討程序的背后,如果您能更深的理解這個(gè)簡單的程序,那么在之后的學(xué)習(xí)中將更加順暢自如!
一、頭文件
#include<stdio.h>
這個(gè)是程序的第一行,作用為在我們的代碼中該行所在的位置引入stdio.h這個(gè)文件的全部內(nèi)容,更簡單的說,就像一個(gè)粘貼的操作,而此刻大家應(yīng)該想到,#include后尖括號(hào)<>里的文件也可以是其他頭文件,沒錯(cuò),在今后的學(xué)習(xí)中,我們會(huì)看到更多的#include語句,其中包含了更多的頭文件,這也體現(xiàn)了一種設(shè)計(jì)思想??梢苑奖愕脑诙鄠€(gè)程序間共享公用的信息。
#include語句是C預(yù)處理器指令(preprocessor directive)的一個(gè)例子,這些是編譯器在編譯代碼之前要做的準(zhǔn)備工作,稱之為預(yù)處理(preprocessing)。
再來說說stdio.h這個(gè)文件,不用覺得奇怪,它和我們平常見到的123.txt并無差別,只是后綴名不同而已,這個(gè)名字表示:標(biāo)準(zhǔn)輸入輸出頭文件,正如英文:standard input/output header,它包含了與輸入輸出相關(guān)的函數(shù)(比如printf)的信息,在C語言的世界中,人們稱出現(xiàn)在文件頂部的信息集合為頭(header),C實(shí)現(xiàn)通常都帶有多個(gè)頭文件。
那么,為什么C語言沒有內(nèi)置的輸入輸出語句呢?一個(gè)答案是并非所有的程序都要用到I/O(輸入/輸出)包,并且C語言的一個(gè)基本的設(shè)計(jì)原則就是避免不必要的成分。這個(gè)經(jīng)濟(jì)的使用資源的原則使得C語言在嵌入式編程中非常的流行,例如:為一個(gè)控制自動(dòng)報(bào)警系統(tǒng)的芯片編寫程序。值得一提的是,#include甚至不是C語言的語句!開頭的#,這個(gè)符號(hào)表明這一行是在編譯器接手之前先由C預(yù)處理器處理的語句。以后我們將碰到更多的預(yù)處理指令。
二、主函數(shù)
int main()
接下來的代碼聲明了一個(gè)main函數(shù)。的確,main是一個(gè)及其普通的名字,但它是唯一的選擇。一個(gè)C程序(我們不考慮例外的情況)總是從main()函數(shù)開始執(zhí)行。我們可以對(duì)自己使用的其他函數(shù)任意命名,但是main()必須是開始的函數(shù)。每個(gè)C程序中必須有它!
那么圓括號(hào)的功能呢?它表明main()是一個(gè)函數(shù)。以后,我們將遇到更多的函數(shù)?,F(xiàn)在,請(qǐng)記住這個(gè)函數(shù)就是C程序的基本模塊。
int指明main()函數(shù)的返回類型。這意味著main()函數(shù)返回值的基本類型為整數(shù)。返回到哪里呢?返回給操作系統(tǒng)。為什么要有返回值呢?打個(gè)比方,就像皇上交代大臣去辦事,大臣完成后總要復(fù)命,向皇上稟告,告訴皇上是成功還是失敗。
函數(shù)名后面的圓括號(hào)一般包含傳遞給函數(shù)的信息。在我們這個(gè)簡單的例子中沒有傳遞任何信息,因此圓括號(hào)內(nèi)包含了單詞void。以后我們將看到更多傳遞參數(shù)的例子。
另外,您可能在一些教材或老的版本中,看到過這樣的寫法:
main()
這種形式在C90標(biāo)準(zhǔn)中勉強(qiáng)允許這種形式,在C99標(biāo)準(zhǔn)不允許,即使您的編譯器允許。
以及還有這樣的寫法:
void main()
僅僅有些編譯器允許這種形式,但還沒有任何編譯器考慮接受它,而有的編譯器則會(huì)報(bào)錯(cuò)。因此,我們建議您不要這樣做。另一方面,堅(jiān)持使用標(biāo)準(zhǔn)形式,我們也不必?fù)?dān)心程序從一個(gè)編譯環(huán)境移到另一個(gè)編譯環(huán)境上時(shí)出錯(cuò)的問題。
三、注釋
/*一個(gè)簡單的C程序*/
包含在/* */之間的部分是程序注釋。使用注釋的目的是使人們(包括我們自己)更容易的理解我們的程序。比如,有些程序員除了用注釋說明我們的代碼以外,還會(huì)在代碼的注釋中寫這一天的天氣如何、心情如何等等。C語言的注釋的一個(gè)好處就是可以放在任何地方,甚至是和它要解釋的語句在同一行。一個(gè)較長的注釋可以單放一行,或者是多行。在/*和*/之間的所有內(nèi)容都會(huì)被編譯器忽略掉,如:
int number; /* 定義一個(gè)整形變量number*/
注釋也可以分成兩行或多行,如:
/* 筆者自述: 時(shí)間:18:24 地點(diǎn):北半球 天氣:窗外天氣不明 微冷 肚子有點(diǎn)餓 */
除此之外,C99還增加另一種風(fēng)格的注釋,它被普遍用在C++或java里,這種新形式使用//符號(hào),但這種注釋被限制在一行里,如:
x=10; //將x賦值為10
四、花括號(hào)
{ /*... */ }
在C程序中,通常所有的C函數(shù)都使用花括號(hào)來表示函數(shù)體的開始與結(jié)束。它們的存在是必不可少的,因此我們不能丟掉它們。并且僅有花括號(hào){}能起到這種作用,小括號(hào)()和中括號(hào)[]都不行。花括號(hào)還可以用來把函數(shù)中的語句聚集到一個(gè)單元或代碼塊中。
五、聲明
int num;
程序中這一行叫做聲明語句(declaration statement)。該聲明語句是C語言中最重要的功能之一。這個(gè)特殊的例子聲明兩件事情。第一,在函數(shù)中您有一個(gè)名為num的變量,第二,int說明num是一個(gè)整數(shù),也就是說這個(gè)數(shù)沒有小數(shù)部分(int是C語言的一種數(shù)據(jù)類型)。編譯器使用這個(gè)信息為變量num在內(nèi)存中分配一個(gè)合適的存儲(chǔ)空間。句末的分號(hào)指明這一行是C語言的一個(gè)語句或指令,分號(hào)是語句的一部分,每個(gè)C語言都以一個(gè)分號(hào)結(jié)束。
單詞int是C語言的一個(gè)關(guān)鍵字,代表了一種C語言的數(shù)據(jù)類型。關(guān)鍵字是用來表達(dá)語言的單詞,您不能將他們用于其他目的。比如,不能用int用作一個(gè)函數(shù)名或者一個(gè)變量的名字。
本例中的單詞num是一個(gè)標(biāo)識(shí)符(identifier),也就是您為一個(gè)變量、函數(shù)或其他實(shí)體所選的名字。這樣該聲明就把一個(gè)特殊的標(biāo)識(shí)符和計(jì)算機(jī)中的一個(gè)特殊位置聯(lián)系了起來,同時(shí)也確定了該位置存儲(chǔ)的信息類型(即數(shù)據(jù)類型),我們?yōu)檫@個(gè)變量賦值,也就相當(dāng)于在計(jì)算機(jī)中這個(gè)特殊的位置(今后我們將清楚,這個(gè)位置即所謂的地址)賦值。因?yàn)?,C語言是人和計(jì)算機(jī)溝通的紐帶。
在C語言中,所有變量都必須在使用之前定義。這就意味著您必須提供程序中要用到的所有變量名的列表,并且指出每個(gè)變量的數(shù)據(jù)類型。聲明變量被認(rèn)為是一種好的編程技術(shù),在C語言當(dāng)中必須這樣做。
傳統(tǒng)上,C語言要求必須在一個(gè)代碼塊的開始處聲明變量,在這之前不允許任何其他語句。也就是說,main()函數(shù)如下所示:
int main() //traditional rules(傳統(tǒng)的用法) { int year; int month; int day; year = 2014; month = 2; day = 28; //other statements(其他的語句) }
現(xiàn)在C99遵循C++的慣例,允許把聲明放在代碼塊中的任何位置。然而,在首次使用變量之前仍然必須先聲明它。因此,如果你的編譯器支持這種功能,你的代碼就可以像下面這樣:
int main() //C99 rules(C99 用法) { //some statements(一些語句) int doors; doors = 5; //first use of doors(第一次使用到的變量) //more statements(更多的語句) int dogs; dogs = 3; //first use of dogs(第一次使用到的變量) //other statements (其他的語句) }
現(xiàn)在你可能還有三個(gè)問題。首先,數(shù)據(jù)類型是什么?第二,可以選擇什么樣的名字?第三,為什么必須對(duì)變量進(jìn)行聲明?下面來看這些問題的答案。
1. 數(shù)據(jù)類型
C語言可以處理多個(gè)數(shù)據(jù)種類(或類型),例如整數(shù)、字符和浮點(diǎn)數(shù)。把一個(gè)變量聲明為整數(shù)類型或字符類型是計(jì)算機(jī)正確地存儲(chǔ),獲取和解釋該數(shù)據(jù)的基本前提。在后面您將學(xué)到各種各樣的可用類型。
2. 變量名的選擇
您應(yīng)該盡量使用有意義的變量名(例如:如果你的程序需要蘋果這個(gè)變量,那么使用apple)。如果名字不能表達(dá)清楚,可以用注釋解釋變量所代表的意思。通過這種方式使程序更易讀是良好編程的基本技巧之一。
能夠使用的字符的數(shù)量與C語言的不同實(shí)現(xiàn)有關(guān)。C99標(biāo)準(zhǔn)允許一個(gè)標(biāo)識(shí)符最多可以有 63個(gè)字符,除了外部標(biāo)識(shí)符,后者只識(shí)別31個(gè)字符。與C90分別要求的31個(gè)字符和6個(gè)字符相比較,這是一個(gè)相當(dāng)可觀的進(jìn)步,而更舊的編譯器通常最多只允許8個(gè)字符。實(shí)際上,你使用的字符數(shù)量可以超過規(guī)定的最大值,但是編譯器不會(huì)識(shí)別額外的字符。因此,如果一個(gè)系統(tǒng)最大字符數(shù)為8,那么clangdot和clangdotcc將被看作是一個(gè)名字,因?yàn)樗鼈兊那?個(gè)字符相同。
可供使用的字符有小寫字母,大寫字母,數(shù)字和下劃線。第一個(gè)字符必須是字母或者下劃線。C語言的名字是區(qū)分大小寫的,即把一個(gè)大寫字母和與之對(duì)應(yīng)的小寫字母看作是不同的。因此,clang不同于Clang或者CLANG。
六. 賦值
num = 1; // =(賦值運(yùn)算符)
這行程序是一個(gè)賦值語句(assignment statement)。賦值語句是C語言的基本操作之一。這個(gè)特殊的例子的意思是“把值1賦給變量num”。前面的int num;語句在計(jì)算機(jī)內(nèi)存中為變量num分配了空間,該賦值語句在那個(gè)地方為變量存儲(chǔ)了一個(gè)值。如果你想的話,以后你還可以給num賦另一個(gè)值,這就是把num稱為變量的原因。可以把變量理解成是一個(gè)容器,用來盛放常量的。另外注意賦值語句賦值的順序是從右到左。同樣,該語句也用分號(hào)結(jié)束。
七. printf()函數(shù)
printf ("Hello!"); printf ("I am dotcpp.com\n"); printf ("My birthday is %d\n", num);
所有這些行都使用了C語言的一個(gè)標(biāo)準(zhǔn)函數(shù):printf()。圓括號(hào)表明printf是一個(gè)函數(shù)名。圓括號(hào)中包含的內(nèi)容是從函數(shù)main()傳遞到函數(shù)printf()的信息。例如,第一行把Hello!傳遞給printf()函數(shù)。 這樣的信息被稱為參數(shù)(argument),更完整的名稱是函數(shù)的實(shí)際參數(shù)(actual argument)。printf()函數(shù)如何處理這個(gè)參數(shù)?程序?qū)⒆R(shí)別兩個(gè)雙引號(hào)之間的內(nèi)容并把它們顯示在屏幕上。
第一行printf()語句是如何在C語言中調(diào)用(call)或請(qǐng)求(invoke)一個(gè)函數(shù)的例子。只須鍵入函數(shù)的名字,把所需的一個(gè)或多個(gè)參數(shù)放進(jìn)圓括號(hào)中。當(dāng)程序運(yùn)行到這一行時(shí),控制權(quán)將轉(zhuǎn)給該函數(shù)(在這個(gè)例子中是 printf())。當(dāng)函數(shù)完成了它所要做的工作,將控制權(quán)返回給原來的函數(shù)(調(diào)用函數(shù)),在這個(gè)例子中是main()。
那么下一個(gè)printf()行呢?引號(hào)中有字符\n,但并沒有輸出它們!發(fā)生了什么事情呢?\n字符的意思是開始新的一行。\n組合(依次鍵入這兩個(gè)字符)代表一個(gè)稱為換行符(newline character)的字符,它意味著“在下一行的最左邊開始新的一行”。換句話說,打印換行字符的效果和在普通鍵盤上按下回車鍵一樣。當(dāng)鍵入printf()這個(gè)參數(shù)時(shí),為什么不直接按回車鍵呢?因?yàn)槟菍⒖醋魇侵苯俞槍?duì)編輯器的命令,而不是作為存在源代碼中的指令。也就是說,當(dāng)你按回車鍵時(shí),編輯器退出你正在輸入的當(dāng)前行,并開始新的一行。而換行符則影響程序的輸出如何顯示。
換行符是轉(zhuǎn)義字符(Escape Sequence)的一個(gè)例子。轉(zhuǎn)義字符通常用于代表難于表達(dá)的或是無法鍵入的字符。其他的例子比如\t代表Tab鍵,\b代表退格鍵盤。每個(gè)轉(zhuǎn)義字符都用斜線字符(\)開始。
這樣就解釋了三個(gè)printf()語句只產(chǎn)生兩行輸出的原因:第一個(gè)printf指令中沒有換行字符,而第二個(gè)和第三個(gè)都有。最后一個(gè)printf()行中又有一個(gè)奇怪的問題:當(dāng)輸出這一行時(shí)%d起什么作用?回憶一下,這一行的輸出結(jié)果是:
My birthday is 2014
喔!當(dāng)這一行輸出時(shí),數(shù)字2014被符號(hào)組合%d代替了,而1是變量 num的值。%d是一個(gè)占位符,其作用是指出輸出num值的位置。
事實(shí)上,%告訴程序把一個(gè)變量在這個(gè)位置輸出,d告訴程序?qū)⑤敵鲆粋€(gè)十進(jìn)制(以10為基數(shù))整數(shù)變量。printf()函數(shù)允許多種輸出變量格式,包括十六進(jìn)制(以16為基數(shù))整數(shù)和帶小數(shù)點(diǎn)的數(shù)。實(shí)際上,printf()中的f暗示著這是一種格式化(format)的輸出函數(shù)。每一種數(shù)據(jù)都有自己的說明符,往后會(huì)有更多的說明。
八. return 語句
return 0;
return語句(返回語句)是程序的最后一個(gè)語句。在int main(void)中int表示main()函數(shù)的返回值應(yīng)該是一個(gè)整數(shù)。C標(biāo)準(zhǔn)要求main()這樣做。帶有返回值的C語言函數(shù)要使用一個(gè)return語句,該語句包括關(guān)鍵字return,后面緊跟著要返回的值,然后是一個(gè)分號(hào)。對(duì)于main()函數(shù)來說,如果你漏掉了return語句,則大多數(shù)編譯器將對(duì)你的疏忽提出警告,但仍將編譯該程序。此時(shí),你可以暫時(shí)把main()中的return語句看作是保持邏輯連貫性所需的內(nèi)容。但對(duì)于某些操作系統(tǒng)(包括DOS和UNIX)而言,它有實(shí)際的用途。
C語言網(wǎng)提供由在職研發(fā)工程師或ACM藍(lán)橋杯競賽優(yōu)秀選手錄制的視頻教程,并配有習(xí)題和答疑,點(diǎn)擊了解:
一點(diǎn)編程也不會(huì)寫的:零基礎(chǔ)C語言學(xué)練課程
解決困擾你多年的C語言疑難雜癥特性的C語言進(jìn)階課程
從零到寫出一個(gè)爬蟲的Python編程課程
只會(huì)語法寫不出代碼?手把手帶你寫100個(gè)編程真題的編程百練課程
信息學(xué)奧賽或C++選手的 必學(xué)C++課程
藍(lán)橋杯ACM、信息學(xué)奧賽的必學(xué)課程:算法競賽課入門課程
手把手講解近五年真題的藍(lán)橋杯輔導(dǎo)課程