【轉(zhuǎn)載】C Primer Plus(第6版):第1章 初識(shí)C語(yǔ)言
第1章 初識(shí)C語(yǔ)言

本章介紹以下內(nèi)容:
?C的歷史和特性
?編寫程序的步驟
?編譯器和鏈接器的一些知識(shí)
?C標(biāo)準(zhǔn)

歡迎來(lái)到C語(yǔ)言的世界。C是一門功能強(qiáng)大的專業(yè)化編程語(yǔ)言,深受業(yè)余編程愛(ài)好者和專業(yè)程序員的喜愛(ài)。本章為讀者學(xué)習(xí)這一強(qiáng)大而流行的語(yǔ)言打好基礎(chǔ),并介紹幾種開發(fā)C程序最可能使用的環(huán)境。
我們先來(lái)了解C語(yǔ)言的起源和一些特性,包括它的優(yōu)缺點(diǎn)。然后,介紹編程的起源并探討一些編程的基本原則。最后,討論如何在一些常見(jiàn)系統(tǒng)中運(yùn)行C程序。
1.1 C語(yǔ)言的起源
1972年,貝爾實(shí)驗(yàn)室的丹尼斯·里奇(Dennis Ritch)和肯·湯普遜(Ken Thompson)在開發(fā)UNIX操作系統(tǒng)時(shí)設(shè)計(jì)了C語(yǔ)言。然而,C語(yǔ)言不完全是里奇突發(fā)奇想而來(lái),他是在B語(yǔ)言(湯普遜發(fā)明)的基礎(chǔ)上進(jìn)行設(shè)計(jì)。至于B語(yǔ)言的起源,那是另一個(gè)故事。C 語(yǔ)言設(shè)計(jì)的初衷是將其作為程序員使用的一種編程工具,因此,其主要目標(biāo)是成為有用的語(yǔ)言。
雖然絕大多數(shù)語(yǔ)言都以實(shí)用為目標(biāo),但是通常也會(huì)考慮其他方面。例如,Pascal 的主要目標(biāo)是為更好地學(xué)習(xí)編程原理提供扎實(shí)的基礎(chǔ);而BASIC的主要目標(biāo)是開發(fā)出類似英文的語(yǔ)言,讓不熟悉計(jì)算機(jī)的學(xué)生輕松學(xué)習(xí)編程。這些目標(biāo)固然很重要,但是隨著計(jì)算機(jī)的迅猛發(fā)展,它們已經(jīng)不是主流語(yǔ)言。然而,最初為程序員設(shè)計(jì)開發(fā)的C語(yǔ)言,現(xiàn)在已成為首選的編程語(yǔ)言之一。
1.2 選擇C語(yǔ)言的理由
在過(guò)去40多年里,C語(yǔ)言已成為最重要、最流行的編程語(yǔ)言之一。它的成長(zhǎng)歸功于使用過(guò)的人都對(duì)它很滿意。過(guò)去20多年里,雖然許多人都從C語(yǔ)言轉(zhuǎn)而使用其他編程語(yǔ)言(如,C++、Objective C、Java等),但是C語(yǔ)言仍憑借自身實(shí)力在眾多語(yǔ)言中脫穎而出。在學(xué)習(xí)C語(yǔ)言的過(guò)程中,會(huì)發(fā)現(xiàn)它的許多優(yōu)點(diǎn)(見(jiàn)圖1.1)。下面,我們來(lái)看看其中較為突出的幾點(diǎn)。
1.2.1 設(shè)計(jì)特性
C是一門流行的語(yǔ)言,融合了計(jì)算機(jī)科學(xué)理論和實(shí)踐的控制特性。C語(yǔ)言的設(shè)計(jì)理念讓用戶能輕松地完成自頂向下的規(guī)劃、結(jié)構(gòu)化編程和模塊化設(shè)計(jì)。因此,用C語(yǔ)言編寫的程序更易懂、更可靠。
1.2.2 高效性
C是高效的語(yǔ)言。在設(shè)計(jì)上,它充分利用了當(dāng)前計(jì)算機(jī)的優(yōu)勢(shì),因此C程序相對(duì)更緊湊,而且運(yùn)行速度很快。實(shí)際上,C語(yǔ)言具有通常是匯編語(yǔ)言才具有的微調(diào)控制能力(匯編語(yǔ)言是為特殊的中央處理單元設(shè)計(jì)的一系列內(nèi)部指令,使用助記符來(lái)表示;不同的CPU系列使用不同的匯編語(yǔ)言),可以根據(jù)具體情況微調(diào)程序以獲得最大運(yùn)行速度或最有效地使用內(nèi)存。
?

1.2.3 可移植性
C是可移植的語(yǔ)言。這意味著,在一種系統(tǒng)中編寫的C程序稍作修改或不修改就能在其他系統(tǒng)運(yùn)行。如需修改,也只需簡(jiǎn)單更改主程序頭文件中的少許項(xiàng)即可。大部分語(yǔ)言都希望成為可移植語(yǔ)言,但是,如果經(jīng)歷過(guò)把IBMPC BASIC程序轉(zhuǎn)換成蘋果BASIC(兩者是近親),或者在UNIX系統(tǒng)中運(yùn)行IBM大型機(jī)的FORTRAN程序的人都知道,移植是最麻煩的事。C語(yǔ)言是可移植方面的佼佼者。從8位微處理器到克雷超級(jí)計(jì)算機(jī),許多計(jì)算機(jī)體系結(jié)構(gòu)都可以使用C編譯器(C編譯器是把C代碼轉(zhuǎn)換成計(jì)算機(jī)內(nèi)部指令的程序)。但是要注意,程序中針對(duì)特殊硬件設(shè)備(如,顯示監(jiān)視器)或操作系統(tǒng)特殊功能(如,Windows 8或OS X)編寫的部分,通常是不可移植的。
由于C語(yǔ)言與UNIX關(guān)系密切,UNIX系統(tǒng)通常會(huì)將C編譯器作為軟件包的一部分。安裝Linux時(shí),通常也會(huì)安裝C編譯器。供個(gè)人計(jì)算機(jī)使用的C編譯器很多,運(yùn)行各種版本的Windows和Macintosh(即, Mac)的PC都能找到合適的C編譯器。因此,無(wú)論是使用家庭計(jì)算機(jī)、專業(yè)工作站,還是大型機(jī),都能找到針對(duì)特定系統(tǒng)的C編譯器。
1.2.4 強(qiáng)大而靈活
C語(yǔ)言功能強(qiáng)大且靈活(計(jì)算機(jī)領(lǐng)域經(jīng)常使用這兩個(gè)詞)。例如,功能強(qiáng)大且靈活的UNIX操作系統(tǒng),大部分是用C語(yǔ)言寫的;其他語(yǔ)言(如,F(xiàn)ORTRAN、Perl、Python、Pascal、LISP、Logo、BASIC)的許多編譯器和解釋器都是用C語(yǔ)言編寫的。因此,在UNIX機(jī)上使用FORTRAN時(shí),最終是由C程序生成最后的可執(zhí)行程序。C程序可以用于解決物理學(xué)和工程學(xué)的問(wèn)題,甚至可用于制作電影的動(dòng)畫特效。
1.2.5 面向程序員
C語(yǔ)言是為了滿足程序員的需求而設(shè)計(jì)的,程序員利用 C 可以訪問(wèn)硬件、操控內(nèi)存中的位。C 語(yǔ)言有豐富的運(yùn)算符,能讓程序員簡(jiǎn)潔地表達(dá)自己的意圖。C沒(méi)有Pascal嚴(yán)謹(jǐn),但是卻比C++的限制多。這樣的靈活性既是優(yōu)點(diǎn)也是缺點(diǎn)。優(yōu)點(diǎn)是,許多任務(wù)用C來(lái)處理都非常簡(jiǎn)潔(如,轉(zhuǎn)換數(shù)據(jù)的格式);缺點(diǎn)是,你可能會(huì)犯一些莫名其妙的錯(cuò)誤,這些錯(cuò)誤不可能在其他語(yǔ)言中出現(xiàn)。C 語(yǔ)言在提供更多自由的同時(shí),也讓使用者承擔(dān)了更大的責(zé)任。
另外,大多數(shù)C實(shí)現(xiàn)都有一個(gè)大型的庫(kù),包含眾多有用的C函數(shù)。這些函數(shù)用于處理程序員經(jīng)常需要解決的問(wèn)題。
1.2.6 缺點(diǎn)
人無(wú)完人,金無(wú)足赤。C語(yǔ)言也有一些缺點(diǎn)。例如,前面提到的,要享受用C語(yǔ)言自由編程的樂(lè)趣,就必須承擔(dān)更多的責(zé)任。特別是,C語(yǔ)言使用指針,而涉及指針的編程錯(cuò)誤往往難以察覺(jué)。有句話說(shuō)的好:想擁有自由就
必須時(shí)刻保持警惕。
C 語(yǔ)言緊湊簡(jiǎn)潔,結(jié)合了大量的運(yùn)算符。正因如此,我們也可以編寫出
讓人極其費(fèi)解的代碼。雖然沒(méi)必要強(qiáng)迫自己編寫晦澀的代碼,但是有興趣寫
寫也無(wú)妨。試問(wèn),除 C語(yǔ)言外還為哪種語(yǔ)言舉辦過(guò)年度混亂代碼大賽[1]?
瑕不掩瑜,C語(yǔ)言的優(yōu)點(diǎn)比缺點(diǎn)多很多。我們不想在這里多費(fèi)筆墨,還
是來(lái)聊聊C語(yǔ)言的其他話題。
1.3 C語(yǔ)言的應(yīng)用范圍
早在20世紀(jì)80年代,C語(yǔ)言就已經(jīng)成為小型計(jì)算機(jī)(UNIX系統(tǒng))使用的主流語(yǔ)言。從那以后,C語(yǔ)言的應(yīng)用范圍擴(kuò)展到微型機(jī)(個(gè)人計(jì)算機(jī))和大型機(jī)(龐然大物)。如圖1.2所示,許多軟件公司都用C語(yǔ)言來(lái)開發(fā)文字處理程序、電子表格、編譯器和其他產(chǎn)品,因?yàn)橛?C語(yǔ)言編寫的程序緊湊而高效。更重要的是,C程序很方便修改,而且移植到新型號(hào)的計(jì)算機(jī)中也沒(méi)什么問(wèn)題。
無(wú)論是軟件公司、經(jīng)驗(yàn)豐富的C程序員,還是其他用戶,都能從C語(yǔ)言中受益。越來(lái)越多的計(jì)算機(jī)用戶已轉(zhuǎn)而求助C語(yǔ)言解決一些安全問(wèn)題。不一定非得是計(jì)算機(jī)專家也能使用C語(yǔ)言。
20世紀(jì)90年代,許多軟件公司開始改用C++來(lái)開發(fā)大型的編程項(xiàng)目。C++在C語(yǔ)言的基礎(chǔ)上嫁接了面向?qū)ο缶幊坦ぞ撸嫦驅(qū)ο缶幊淌且婚T哲學(xué),它通過(guò)對(duì)語(yǔ)言建模來(lái)適應(yīng)問(wèn)題,而不是對(duì)問(wèn)題建模以適應(yīng)語(yǔ)言)。C++幾乎是C的超集,這意味著任何C程序差不多就是一個(gè)C++程序。學(xué)習(xí)C語(yǔ)言,也相當(dāng)于學(xué)習(xí)了許多C++的知識(shí)。
?

雖然這些年來(lái)C++和JAVA非常流行,但是C語(yǔ)言仍是軟件業(yè)中的核心技能。在最想具備的技能中,C語(yǔ)言通常位居前十。特別是,C 語(yǔ)言已成為嵌入式系統(tǒng)編程的流行語(yǔ)言。也就是說(shuō),越來(lái)越多的汽車、照相機(jī)、DVD 播放機(jī)和其他現(xiàn)代化設(shè)備的微處理器都用 C 語(yǔ)言進(jìn)行編程。除此之外,C 語(yǔ)言還從長(zhǎng)期被FORTRAN獨(dú)占的科學(xué)編程領(lǐng)域分得一杯羹。最終,作為開發(fā)操作系統(tǒng)的卓越語(yǔ)言,C在Linux開發(fā)中扮演著極其重要的角色。因此,在進(jìn)入21世紀(jì)的第2個(gè)10年中,C語(yǔ)言仍然保持著強(qiáng)勁的勢(shì)頭。
簡(jiǎn)而言之,C 語(yǔ)言是最重要的編程語(yǔ)言之一,將來(lái)也是如此。如果你想拿下一份編程的工作,被問(wèn)到是否會(huì)C語(yǔ)言時(shí),最好回答“是”。
1.4 計(jì)算機(jī)能做什么
在學(xué)習(xí)如何用C語(yǔ)言編程之前,最好先了解一下計(jì)算機(jī)的工作原理。這些知識(shí)有助于你理解用C語(yǔ)言編寫程序和運(yùn)行C程序時(shí)所發(fā)生的事情之間有什么聯(lián)系。
現(xiàn)代的計(jì)算機(jī)由多種部件構(gòu)成。中央處理單元(CPU)承擔(dān)絕大部分的運(yùn)算工作。隨機(jī)存取內(nèi)存(RAM)是存儲(chǔ)程序和文件的工作區(qū);而永久內(nèi)存存儲(chǔ)設(shè)備(過(guò)去一般指機(jī)械硬盤,現(xiàn)在還包括固態(tài)硬盤)即使在關(guān)閉計(jì)算機(jī)后,也不會(huì)丟失之前儲(chǔ)存的程序和文件。另外,還有各種外圍設(shè)備(如,鍵盤、鼠標(biāo)、觸摸屏、監(jiān)視器)提供人與計(jì)算機(jī)之間的交互。CPU負(fù)責(zé)處理程序,接下來(lái)我們重點(diǎn)討論它的工作原理。
CPU 的工作非常簡(jiǎn)單,至少?gòu)囊韵潞?jiǎn)短的描述中看是這樣。它從內(nèi)存中獲取并執(zhí)行一條指令,然后再?gòu)膬?nèi)存中獲取并執(zhí)行下一條指令,諸如此類(一個(gè)吉赫茲的CPU一秒鐘能重復(fù)這樣的操作大約十億次,因此,CPU 能以驚人的速度從事枯燥的工作)。CPU 有自己的小工作區(qū)——由若干個(gè)寄存器組成,每個(gè)寄存器都可以儲(chǔ)存一個(gè)數(shù)字。一個(gè)寄存器儲(chǔ)存下一條指令的內(nèi)存地址,CPU 使用該地址來(lái)獲取和更新下一條指令。在獲取指令后,CPU在另一個(gè)寄存器中儲(chǔ)存該指令,并更新第1個(gè)寄存器儲(chǔ)存下一條指令的地址。CPU能理解的指令有限(這些指令的集合叫作指令集)。而且,這些指令相當(dāng)具體,其中的許多指令都是用于請(qǐng)求計(jì)算機(jī)把一個(gè)數(shù)字從一個(gè)位置移動(dòng)到另一個(gè)位置。例如,從內(nèi)存移動(dòng)到寄存器。
下面介紹兩個(gè)有趣的知識(shí)。其一,儲(chǔ)存在計(jì)算機(jī)中的所有內(nèi)容都是數(shù)字。計(jì)算機(jī)以數(shù)字形式儲(chǔ)存數(shù)字和字符(如,在文本文檔中使用的字母)。每個(gè)字符都有一個(gè)數(shù)字碼。計(jì)算機(jī)載入寄存器的指令也以數(shù)字形式儲(chǔ)存,指令集中的每條指令都有一個(gè)數(shù)字碼。其二,計(jì)算機(jī)程序最終必須以數(shù)字指令碼(即,機(jī)器語(yǔ)言)來(lái)表示。
簡(jiǎn)而言之,計(jì)算機(jī)的工作原理是:如果希望計(jì)算機(jī)做某些事,就必須為其提供特殊的指令列表(程序),確切地告訴計(jì)算機(jī)要做的事以及如何做。你必須用計(jì)算機(jī)能直接明白的語(yǔ)言(機(jī)器語(yǔ)言)創(chuàng)建程序。這是一項(xiàng)繁瑣、乏味、費(fèi)力的任務(wù)。計(jì)算機(jī)要完成諸如兩數(shù)相加這樣簡(jiǎn)單的事,就得分成類似以下幾個(gè)步驟。
1.從內(nèi)存位置2000上把一個(gè)數(shù)字拷貝到寄存器1。
2.從內(nèi)存位置2004上把另一個(gè)數(shù)字拷貝到寄存器2。
3.把寄存器2中的內(nèi)容與寄存器1中的內(nèi)容相加,把結(jié)果儲(chǔ)存在寄存器1中。
4.把寄存器1中的內(nèi)容拷貝到內(nèi)存位置2008。
而你要做的是,必須用數(shù)字碼來(lái)表示以上的每個(gè)步驟!
如果以這種方式編寫程序很合你的意,那不得不說(shuō)抱歉,因?yàn)橛脵C(jī)器語(yǔ)言編程的黃金時(shí)代已一去不復(fù)返。但是,如果你對(duì)有趣的事情比較感興趣,不妨試試高級(jí)編程語(yǔ)言。
1.5 高級(jí)計(jì)算機(jī)語(yǔ)言和編譯器
高級(jí)編程語(yǔ)言(如,C)以多種方式簡(jiǎn)化了編程工作。首先,不必用數(shù)字碼表示指令;其次,使用的指令更貼近你如何想這個(gè)問(wèn)題,而不是類似計(jì)算機(jī)那樣繁瑣的步驟。使用高級(jí)編程語(yǔ)言,可以在更抽象的層面表達(dá)你的想法,不用考慮CPU在完成任務(wù)時(shí)具體需要哪些步驟。例如,對(duì)于兩數(shù)相加,可以這樣寫:
total = mine + yours;
對(duì)我們而言,光看這行代碼就知道要計(jì)算機(jī)做什么;而看用機(jī)器語(yǔ)言寫成的等價(jià)指令(多條以數(shù)字碼形式表現(xiàn)的指令)則費(fèi)勁得多。但是,對(duì)計(jì)算機(jī)而言卻恰恰相反。在計(jì)算機(jī)看來(lái),高級(jí)指令就是一堆無(wú)法理解的無(wú)用數(shù)據(jù)。編譯器在這里派上了用場(chǎng)。編譯器是把高級(jí)語(yǔ)言程序翻譯成計(jì)算機(jī)能理解的機(jī)器語(yǔ)言指令集的程序。程序員進(jìn)行高級(jí)思維活動(dòng),而編譯器則負(fù)責(zé)處理冗長(zhǎng)乏味的細(xì)節(jié)工作。
編譯器還有一個(gè)優(yōu)勢(shì)。一般而言,不同CPU制造商使用的指令系統(tǒng)和編碼格式不同。例如,用Intel Core i7 (英特爾酷睿i7)CPU編寫的機(jī)器語(yǔ)言程序?qū)τ贏RM Cortex-A57 CPU而言什么都不是。但是,可以找到與特定類型CPU匹配的編譯器。因此,使用合適的編譯器或編譯器集,便可把一種高級(jí)語(yǔ)言程序轉(zhuǎn)換成供各種不同類型 CPU 使用的機(jī)器語(yǔ)言程序。一旦解決了一個(gè)編程問(wèn)題,便可讓編譯器集翻譯成不同 CPU 使用的機(jī)器語(yǔ)言。
簡(jiǎn)而言之,高級(jí)語(yǔ)言(如C、Java、Pascal)以更抽象的方式描述行為,不受限于特定CPU或指令集。而且,高級(jí)語(yǔ)言簡(jiǎn)單易學(xué),用高級(jí)語(yǔ)言編程比用機(jī)器語(yǔ)言編程容易得多。
1964年,控制數(shù)據(jù)公司(Control Data Corporation)研制出了CDC 6600計(jì)算機(jī)。這臺(tái)龐然大物是世界上首臺(tái)超級(jí)計(jì)算機(jī),當(dāng)時(shí)的售價(jià)是600萬(wàn)美元。它是高能核物理研究的首選。然而,現(xiàn)在的普通智能手機(jī)在計(jì)算能力和內(nèi)存方面都超過(guò)它數(shù)百倍,而且能看視頻,放音樂(lè)。
1964 年,在工程和科學(xué)領(lǐng)域的主流編程語(yǔ)言是 FORTRAN。雖然編程語(yǔ)言不如硬件發(fā)展那么突飛猛進(jìn),但是也發(fā)生了很大變化。為了應(yīng)對(duì)越來(lái)越大型的編程項(xiàng)目,語(yǔ)言先后為結(jié)構(gòu)化編程和面向?qū)ο缶幊烫峁┝烁嗟闹С?。隨著時(shí)間的推移,不僅新語(yǔ)言層出不窮,而且現(xiàn)有語(yǔ)言也會(huì)發(fā)生變化。
1.6 語(yǔ)言標(biāo)準(zhǔn)
目前,有許多C實(shí)現(xiàn)可用。在理想情況下,編寫C程序時(shí),假設(shè)該程序中未使用機(jī)器特定的編程技術(shù),那么它的運(yùn)行情況在任何實(shí)現(xiàn)中都應(yīng)該相同。要在實(shí)踐中做到這一點(diǎn),不同的實(shí)現(xiàn)要遵循同一個(gè)標(biāo)準(zhǔn)。
C語(yǔ)言發(fā)展之初,并沒(méi)有所謂的C標(biāo)準(zhǔn)。1987年,布萊恩·柯林漢(BrianKernighan)和丹尼斯·里奇(Dennis Ritchie)合著的The C ProgrammingLanguage(《C語(yǔ)言程序設(shè)計(jì)》)第1版是公認(rèn)的C標(biāo)準(zhǔn),通常稱之為K&R C或經(jīng)典C。特別是,該書中的附錄中的“C語(yǔ)言參考手冊(cè)”已成為實(shí)現(xiàn)C的指導(dǎo)標(biāo)準(zhǔn)。例如,編譯器都聲稱提供完整的K&R實(shí)現(xiàn)。雖然這本書中的附錄定義了C語(yǔ)言,但卻沒(méi)有定義C庫(kù)。與大多數(shù)語(yǔ)言不同的是,C語(yǔ)言比其他語(yǔ)言更依賴庫(kù),因此需要一個(gè)標(biāo)準(zhǔn)庫(kù)。實(shí)際上,由于缺乏官方標(biāo)準(zhǔn),UNIX實(shí)現(xiàn)提供的庫(kù)已成為了標(biāo)準(zhǔn)庫(kù)。
1.6.1 第1個(gè)ANSI/ISO C標(biāo)準(zhǔn)
隨著C的不斷發(fā)展,越來(lái)越廣泛地應(yīng)用于更多系統(tǒng)中,C社區(qū)意識(shí)到需要一個(gè)更全面、更新穎、更嚴(yán)格的標(biāo)準(zhǔn)。鑒于此,美國(guó)國(guó)家標(biāo)準(zhǔn)協(xié)會(huì)(ANSI)于 1983 年組建了一個(gè)委員會(huì)(X3J11),開發(fā)了一套新標(biāo)準(zhǔn),并于1989年正式公布。該標(biāo)準(zhǔn)(ANSI C)定義了C語(yǔ)言和C標(biāo)準(zhǔn)庫(kù)。國(guó)際標(biāo)準(zhǔn)化組織于1990年采用了這套C標(biāo)準(zhǔn)(ISO C)。ISO C和ANSI C是完全相同的標(biāo)準(zhǔn)。ANSI/ISO標(biāo)準(zhǔn)的最終版本通常叫作C89(因?yàn)锳NSI于1989年批準(zhǔn)該標(biāo)準(zhǔn))或C90(因?yàn)镮SO于1990年批準(zhǔn)該標(biāo)準(zhǔn))。另外,由于ANSI先公布C標(biāo)準(zhǔn),因此業(yè)界人士通常使用ANSI C。
在該委員會(huì)制定的指導(dǎo)原則中,最有趣的可能是:保持 C的精神。委員
會(huì)在表述這一精神時(shí)列出了以下幾點(diǎn):
?信任程序員;
?不要妨礙程序員做需要做的事;
?保持語(yǔ)言精練簡(jiǎn)單;
?只提供一種方法執(zhí)行一項(xiàng)操作;
?讓程序運(yùn)行更快,即使不能保證其可移植性。
在最后一點(diǎn)上,標(biāo)準(zhǔn)委員會(huì)的用意是:作為實(shí)現(xiàn),應(yīng)該針對(duì)目標(biāo)計(jì)算機(jī)來(lái)定義最合適的某特定操作,而不是強(qiáng)加一個(gè)抽象、統(tǒng)一的定義。在學(xué)習(xí)C語(yǔ)言過(guò)程中,許多方面都反映了這一哲學(xué)思想。
1.6.2 C99標(biāo)準(zhǔn)
1994年,ANSI/ISO聯(lián)合委員會(huì)(C9X委員會(huì))開始修訂C標(biāo)準(zhǔn),最終發(fā)布了C99標(biāo)準(zhǔn)。該委員會(huì)遵循了最初C90標(biāo)準(zhǔn)的原則,包括保持語(yǔ)言的精練簡(jiǎn)單。委員會(huì)的用意不是在C語(yǔ)言中添加新特性,而是為了達(dá)到新的目標(biāo)。第1個(gè)目標(biāo)是,支持國(guó)際化編程。例如,提供多種方法處理國(guó)際字符集。第2個(gè)目標(biāo)是,“調(diào)整現(xiàn)有實(shí)踐致力于解決明顯的缺陷”。因此,在遇到需要將C移至64位處理器時(shí),委員會(huì)根據(jù)現(xiàn)實(shí)生活中處理問(wèn)題的經(jīng)驗(yàn)來(lái)添加標(biāo)準(zhǔn)。第3個(gè)目標(biāo)是,為適應(yīng)科學(xué)和工程項(xiàng)目中的關(guān)鍵數(shù)值計(jì)算,提高C的適應(yīng)性,讓C比FORTRAN更有競(jìng)爭(zhēng)力。
這3點(diǎn)(國(guó)際化、彌補(bǔ)缺陷和提高計(jì)算的實(shí)用性)是主要的修訂目標(biāo)。在其他方面的改變則更為保守,例如,盡量與C90、C++兼容,讓語(yǔ)言在概念上保持簡(jiǎn)單。用委員會(huì)的話說(shuō):“??委員會(huì)很滿意讓C++成為大型、功能強(qiáng)大的語(yǔ)言”。
C99的修訂保留了C語(yǔ)言的精髓,C仍是一門簡(jiǎn)潔高效的語(yǔ)言。本書指出了許多C99修改的地方。雖然該標(biāo)準(zhǔn)已發(fā)布了很長(zhǎng)時(shí)間,但并非所有的編譯器都完全實(shí)現(xiàn)C99的所有改動(dòng)。因此,你可能發(fā)現(xiàn)C99的一些改動(dòng)在自己的系統(tǒng)中不可用,或者只有改變編譯器的設(shè)置才可用。
1.6.3 C11標(biāo)準(zhǔn)
維護(hù)標(biāo)準(zhǔn)任重道遠(yuǎn)。標(biāo)準(zhǔn)委員會(huì)在2007年承諾C標(biāo)準(zhǔn)的下一個(gè)版本是C1X,2011年終于發(fā)布了C11標(biāo)準(zhǔn)。此次,委員會(huì)提出了一些新的指導(dǎo)原則。出于對(duì)當(dāng)前編程安全的擔(dān)憂,不那么強(qiáng)調(diào)“信任程序員”目標(biāo)了。而且,供應(yīng)商并未像對(duì)C90那樣很好地接受和支持C99。這使得C99的一些特性成為C11的可選項(xiàng)。因?yàn)槲瘑T會(huì)認(rèn)為,不應(yīng)要求服務(wù)小型機(jī)市場(chǎng)的供應(yīng)商支持其目標(biāo)環(huán)境中用不到的特性。另外需要強(qiáng)調(diào)的是,修訂標(biāo)準(zhǔn)的原因不是因?yàn)樵瓨?biāo)準(zhǔn)不能用,而是需要跟進(jìn)新的技術(shù)。例如,新標(biāo)準(zhǔn)添加了可選項(xiàng)支持當(dāng)前使用多處理器的計(jì)算機(jī)。對(duì)于C11標(biāo)準(zhǔn),我們淺嘗輒止,深入分析這部分內(nèi)容已超出本書討論的范圍。
注意
本書使用術(shù)語(yǔ)ANSI C、ISO C或ANSI/ISO C講解C89/90和較新標(biāo)準(zhǔn)共有的特性,用C99或C11介紹新的特性。有時(shí)也使用C90(例如,討論一個(gè)特性被首次加入C語(yǔ)言時(shí))。
1.7 使用C語(yǔ)言的7個(gè)步驟
C是編譯型語(yǔ)言。如果之前使用過(guò)編譯型語(yǔ)言(如,Pascal或FORTRAN),就會(huì)很熟悉組建C程序的幾個(gè)基本步驟。但是,如果以前使用的是解釋型語(yǔ)言(如,BASIC)或面向圖形界面語(yǔ)言(如,VisualBasic),或者甚至沒(méi)接觸過(guò)任何編程語(yǔ)言,就有必要學(xué)習(xí)如何編譯。別擔(dān)心,這并不復(fù)雜。首先,為了讓讀者對(duì)編程有大概的了解,我們把編寫C程序的過(guò)程分解成7個(gè)步驟(見(jiàn)圖1.3)。注意,這是理想狀態(tài)。在實(shí)際的使用過(guò)程中,尤其是在較大型的項(xiàng)目中,可能要做一些重復(fù)的工作,根據(jù)下一個(gè)步驟的情況來(lái)調(diào)整或改進(jìn)上一個(gè)步驟。

1.7.1 第1步:定義程序的目標(biāo)
在動(dòng)手寫程序之前,要在腦中有清晰的思路。想要程序去做什么首先自己要明確自己想做什么,思考你的程序需要哪些信息,要進(jìn)行哪些計(jì)算和控制,以及程序應(yīng)該要報(bào)告什么信息。在這一步驟中,不涉及具體的計(jì)算機(jī)語(yǔ)言,應(yīng)該用一般術(shù)語(yǔ)來(lái)描述問(wèn)題。
1.7.2 第2步:設(shè)計(jì)程序
對(duì)程序應(yīng)該完成什么任務(wù)有概念性的認(rèn)識(shí)后,就應(yīng)該考慮如何用程序來(lái)完成它。例如,用戶界面應(yīng)該是怎樣的?如何組織程序?目標(biāo)用戶是誰(shuí)?準(zhǔn)備花多長(zhǎng)時(shí)間來(lái)完成這個(gè)程序?
除此之外,還要決定在程序(還可能是輔助文件)中如何表示數(shù)據(jù),以及用什么方法處理數(shù)據(jù)。學(xué)習(xí)C語(yǔ)言之初,遇到的問(wèn)題都很簡(jiǎn)單,沒(méi)什么可選的。但是,隨著要處理的情況越來(lái)越復(fù)雜,需要決策和考慮的方面也越來(lái)越多。通常,選擇一個(gè)合適的方式表示信息可以更容易地設(shè)計(jì)程序和處理數(shù)據(jù)。
再次強(qiáng)調(diào),應(yīng)該用一般術(shù)語(yǔ)來(lái)描述問(wèn)題,而不是用具體的代碼。但是,你的某些決策可能取決于語(yǔ)言的特性。例如,在數(shù)據(jù)表示方面,C的程序員就比Pascal的程序員有更多選擇。
1.7.3 第3步:編寫代碼
設(shè)計(jì)好程序后,就可以編寫代碼來(lái)實(shí)現(xiàn)它。也就是說(shuō),把你設(shè)計(jì)的程序翻譯成 C語(yǔ)言。這里是真正需要使用C語(yǔ)言的地方。可以把思路寫在紙上,但是最終還是要把代碼輸入計(jì)算機(jī)。這個(gè)過(guò)程的機(jī)制取決于編程環(huán)境,我們稍后會(huì)詳細(xì)介紹一些常見(jiàn)的環(huán)境。一般而言,使用文本編輯器創(chuàng)建源代碼文件。該文件中內(nèi)容就是你翻譯的C語(yǔ)言代碼。程序清單1.1是一個(gè)C源代碼的示例。

程序清單 1.1? C源代碼舉例
#include <stdio.h>
int main(void)
{
? ? int dogs;
? ? printf("How many dogs do you have?\n");
? ? scanf("%d", &dogs);
? ? printf("So you have %d dog(s)!\n", dogs);
? ? return 0;
}

在這一步驟中,應(yīng)該給自己編寫的程序添加文字注釋。最簡(jiǎn)單的方式是使用 C的注釋工具在源代碼中加入對(duì)代碼的解釋。第2章將詳細(xì)介紹如何在代碼中添加注釋。
1.7.4 第4步:編譯
接下來(lái)的這一步是編譯源代碼。再次提醒讀者注意,編譯的細(xì)節(jié)取決于編程的環(huán)境,我們稍后馬上介紹一些常見(jiàn)的編程環(huán)境。現(xiàn)在,先從概念的角度講解編譯發(fā)生了什么事情。
前面介紹過(guò),編譯器是把源代碼轉(zhuǎn)換成可執(zhí)行代碼的程序??蓤?zhí)行代碼是用計(jì)算機(jī)的機(jī)器語(yǔ)言表示的代碼。這種語(yǔ)言由數(shù)字碼表示的指令組成。如前所述,不同的計(jì)算機(jī)使用不同的機(jī)器語(yǔ)言方案。C 編譯器負(fù)責(zé)把C代碼翻譯成特定的機(jī)器語(yǔ)言。此外,C編譯器還將源代碼與C庫(kù)(庫(kù)中包含大量的標(biāo)準(zhǔn)函數(shù)供用戶使用,如printf()和scanf())的代碼合并成最終的程序(更精確地說(shuō),應(yīng)該是由一個(gè)被稱為鏈接器的程序來(lái)鏈接庫(kù)函數(shù),但是在大多數(shù)系統(tǒng)中,編譯器運(yùn)行鏈接器)。其結(jié)果是,生成一個(gè)用戶可以運(yùn)行的可執(zhí)行文件,其中包含著計(jì)算機(jī)能理解的代碼。
編譯器還會(huì)檢查C語(yǔ)言程序是否有效。如果C編譯器發(fā)現(xiàn)錯(cuò)誤,就不生成可執(zhí)行文件并報(bào)錯(cuò)。理解特定編譯器報(bào)告的錯(cuò)誤或警告信息是程序員要掌握的另一項(xiàng)技能。
1.7.5 第5步:運(yùn)行程序
傳統(tǒng)上,可執(zhí)行文件是可運(yùn)行的程序。在常見(jiàn)環(huán)境(包括Windows命令提示符模式、UNIX終端模式和Linux終端模式)中運(yùn)行程序要輸入可執(zhí)行文件的文件名,而其他環(huán)境可能要運(yùn)行命令(如,在VAX中的VMS[2])或一些其他機(jī)制。例如,在Windows和Macintosh提供的集成開發(fā)環(huán)境(IDE)中,用戶可以在IDE中通過(guò)選擇菜單中的選項(xiàng)或按下特殊鍵來(lái)編輯和執(zhí)行C程序。最終生成的程序可通過(guò)單擊或雙擊文件名或圖標(biāo)直接在操作系統(tǒng)中運(yùn)行。
1.7.6 第6步:測(cè)試和調(diào)試程序
程序能運(yùn)行是個(gè)好跡象,但有時(shí)也可能會(huì)出現(xiàn)運(yùn)行錯(cuò)誤。接下來(lái),應(yīng)該檢查程序是否按照你所設(shè)計(jì)的思路運(yùn)行。你會(huì)發(fā)現(xiàn)你的程序中有一些錯(cuò)誤,計(jì)算機(jī)行話叫作bug。查找并修復(fù)程序錯(cuò)誤的過(guò)程叫調(diào)試。學(xué)習(xí)的過(guò)程中不可避免會(huì)犯錯(cuò),學(xué)習(xí)編程也是如此。因此,當(dāng)你把所學(xué)的知識(shí)應(yīng)用于編程時(shí),最好為自己會(huì)犯錯(cuò)做好心理準(zhǔn)備。隨著你越來(lái)越老練,你所寫的程序中的錯(cuò)誤也會(huì)越來(lái)越不易察覺(jué)。
將來(lái)犯錯(cuò)的機(jī)會(huì)很多。你可能會(huì)犯基本的設(shè)計(jì)錯(cuò)誤,可能錯(cuò)誤地實(shí)現(xiàn)了一個(gè)好想法,可能忽視了輸入檢查導(dǎo)致程序癱瘓,可能會(huì)把圓括號(hào)放錯(cuò)地方,可能誤用 C語(yǔ)言或打錯(cuò)字,等等。把你將來(lái)犯錯(cuò)的地方列出來(lái),這份錯(cuò)誤列表應(yīng)該會(huì)很長(zhǎng)。
看到這里你可能會(huì)有些絕望,但是情況沒(méi)那么糟?,F(xiàn)在的編譯器會(huì)捕獲許多錯(cuò)誤,而且自己也可以找到編譯器未發(fā)現(xiàn)的錯(cuò)誤。在學(xué)習(xí)本書的過(guò)程中,我們會(huì)給讀者提供一些調(diào)試的建議。
1.7.7 第7步:維護(hù)和修改代碼
創(chuàng)建完程序后,你發(fā)現(xiàn)程序有錯(cuò),或者想擴(kuò)展程序的用途,這時(shí)就要修改程序。例如,用戶輸入以Zz開頭的姓名時(shí)程序出現(xiàn)錯(cuò)誤、你想到了一個(gè)更好的解決方案、想添加一個(gè)更好的新特性,或者要修改程序使其能在不同的計(jì)算機(jī)系統(tǒng)中運(yùn)行,等等。如果在編寫程序時(shí)清楚地做了注釋并采用了合理的設(shè)計(jì)方案,這些事情都很簡(jiǎn)單。
1.7.8 說(shuō)明
編程并非像描述那樣是一個(gè)線性的過(guò)程。有時(shí),要在不同的步驟之間往復(fù)。例如,在寫代碼時(shí)發(fā)現(xiàn)之前的設(shè)計(jì)不切實(shí)際,或者想到了一個(gè)更好的解決方案,或者等程序運(yùn)行后,想改變?cè)瓉?lái)的設(shè)計(jì)思路。對(duì)程序做文字注釋為今后的修改提供了方便。
許多初學(xué)者經(jīng)常忽略第1步和第2步(定義程序目標(biāo)和設(shè)計(jì)程序),直接跳到第3步(編寫代碼)。剛開始學(xué)習(xí)時(shí),編寫的程序非常簡(jiǎn)單,完全可以在腦中構(gòu)思好整個(gè)過(guò)程。即使寫錯(cuò)了,也很容易發(fā)現(xiàn)。但是,隨著編寫的程序越來(lái)越龐大、越來(lái)越復(fù)雜,動(dòng)腦不動(dòng)手可不行,而且程序中隱藏的錯(cuò)誤也越來(lái)越難找。最終,那些跳過(guò)前兩個(gè)步驟的人往往浪費(fèi)了更多的時(shí)間,因?yàn)樗麄儗懗龅某绦螂y看、缺乏條理、讓人難以理解。要編寫的程序越大越復(fù)雜,事先定義和設(shè)計(jì)程序環(huán)節(jié)的工作量就越大。
磨刀不誤砍柴工,應(yīng)該養(yǎng)成先規(guī)劃再動(dòng)手編寫代碼的好習(xí)慣,用紙和筆記錄下程序的目標(biāo)和設(shè)計(jì)框架。這樣在編寫代碼的過(guò)程中會(huì)更加得心應(yīng)手、條理清晰。
1.8 編程機(jī)制
生成程序的具體過(guò)程因計(jì)算機(jī)環(huán)境而異。C是可移植性語(yǔ)言,因此可以在許多環(huán)境中使用,包括UNIX、Linux、MS-DOS(一些人仍在使用)、Windows和Macintosh OS。有些產(chǎn)品會(huì)隨著時(shí)間的推移發(fā)生演變或被取代,本書無(wú)法涵蓋所有環(huán)境。
首先,來(lái)看看許多C環(huán)境(包括上面提到的5種環(huán)境)共有的一些方面。雖然不必詳細(xì)了解計(jì)算機(jī)內(nèi)部如何運(yùn)行C程序,但是,了解一下編程機(jī)制不僅能豐富編程相關(guān)的背景知識(shí),還有助于理解為何要經(jīng)過(guò)一些特殊的步驟才能得到C程序。
用C語(yǔ)言編寫程序時(shí),編寫的內(nèi)容被儲(chǔ)存在文本文件中,該文件被稱為源代碼文件(source code file)。大部分C系統(tǒng),包括之前提到的,都要求文件名以.c結(jié)尾(如,wordcount.c和budget.c)。在文件名中,點(diǎn)號(hào)(.)前面的部分稱為基本名(basename),點(diǎn)號(hào)后面的部分稱為擴(kuò)展名(extension)。因此,budget是基本名,c是擴(kuò)展名?;久c擴(kuò)展名的組合(budget.c)就是文件名。文件名應(yīng)該滿足特定計(jì)算機(jī)操作系統(tǒng)的特殊要求。例如,MS-DOS是IBM PC及其兼容機(jī)的操作系統(tǒng),比較老舊,它要求基本名不能超過(guò)8個(gè)字符。因此,剛才提到的文件名wordcount.c就是無(wú)效的DOS文件名。有些UNIX系統(tǒng)限制整個(gè)文件名(包括擴(kuò)展名)不超過(guò)14個(gè)字符,而有些UNIX系統(tǒng)則允許使用更長(zhǎng)的文件名,最多255個(gè)字符。Linux、Windows和Macintosh OS都允許使用長(zhǎng)文件名。
接下來(lái),我們來(lái)看一下具體的應(yīng)用,假設(shè)有一個(gè)名為concrete.c的源文件,其中的C源代碼如程序清單1.2所示。

程序清單 1.2? c程序
#include <stdio.h>
int main(void)
{
? ? printf("Concrete contains gravel and cement.\n");
? ? return 0;
}

如果看不懂程序清單1.2中的代碼,不用擔(dān)心,我們將在第2章學(xué)習(xí)相關(guān)知識(shí)。
1.8.1 目標(biāo)代碼文件、可執(zhí)行文件和庫(kù)
C編程的基本策略是,用程序把源代碼文件轉(zhuǎn)換為可執(zhí)行文件(其中包含可直接運(yùn)行的機(jī)器語(yǔ)言代碼)。典型的C實(shí)現(xiàn)通過(guò)編譯和鏈接兩個(gè)步驟來(lái)完成這一過(guò)程。編譯器把源代碼轉(zhuǎn)換成中間代碼,鏈接器把中間代碼和其他代碼合并,生成可執(zhí)行文件。C 使用這種分而治之的方法方便對(duì)程序進(jìn)行模塊化,可以獨(dú)立編譯單獨(dú)的模塊,稍后再用鏈接器合并已編譯的模塊。通過(guò)這種方式,如果只更改某個(gè)模塊,不必因此重新編譯其他模塊。另外,鏈接器還將你編寫的程序和預(yù)編譯的庫(kù)代碼合并。
中間文件有多種形式。我們?cè)谶@里描述的是最普遍的一種形式,即把源代碼轉(zhuǎn)換為機(jī)器語(yǔ)言代碼,并把結(jié)果放在目標(biāo)代碼文件(或簡(jiǎn)稱目標(biāo)文件)中(這里假設(shè)源代碼只有一個(gè)文件)。雖然目標(biāo)文件中包含機(jī)器語(yǔ)言代碼,但是并不能直接運(yùn)行該文件。因?yàn)槟繕?biāo)文件中儲(chǔ)存的是編譯器翻譯的源代碼,這還不是一個(gè)完整的程序。
目標(biāo)代碼文件缺失啟動(dòng)代碼(startup code)。啟動(dòng)代碼充當(dāng)著程序和操作系統(tǒng)之間的接口。例如,可以在MS Windows或Linux系統(tǒng)下運(yùn)行IBM PC兼容機(jī)。這兩種情況所使用的硬件相同,所以目標(biāo)代碼相同,但是Windows和Linux所需的啟動(dòng)代碼不同,因?yàn)檫@些系統(tǒng)處理程序的方式不同。
目標(biāo)代碼還缺少庫(kù)函數(shù)。幾乎所有的C程序都要使用C標(biāo)準(zhǔn)庫(kù)中的函數(shù)。例如,concrete.c中就使用了 printf()函數(shù)。目標(biāo)代碼文件并不包含該函數(shù)的代碼,它只包含了使用 printf()函數(shù)的指令。printf()函數(shù)真正的代碼儲(chǔ)存在另一個(gè)被稱為庫(kù)的文件中。庫(kù)文件中有許多函數(shù)的目標(biāo)代碼。鏈接器的作用是,把你編寫的目標(biāo)代碼、系統(tǒng)的標(biāo)準(zhǔn)啟動(dòng)代碼和庫(kù)代碼這 3 部分合并成一個(gè)文件,即可執(zhí)行文件。對(duì)于庫(kù)代碼,鏈接器只會(huì)把程序中要用到的庫(kù)函數(shù)代碼提取出來(lái)(見(jiàn)圖1.4)。

簡(jiǎn)而言之,目標(biāo)文件和可執(zhí)行文件都由機(jī)器語(yǔ)言指令組成的。然而,目標(biāo)文件中只包含編譯器為你編寫的代碼翻譯的機(jī)器語(yǔ)言代碼,可執(zhí)行文件中還包含你編寫的程序中使用的庫(kù)函數(shù)和啟動(dòng)代碼的機(jī)器代碼。
在有些系統(tǒng)中,必須分別運(yùn)行編譯程序和鏈接程序,而在另一些系統(tǒng)中,編譯器會(huì)自動(dòng)啟動(dòng)鏈接器,用戶只需給出編譯命令即可。
接下來(lái),了解一些具體的系統(tǒng)。
1.8.2 UNIX系統(tǒng)
由于C語(yǔ)言因UNIX系統(tǒng)而生,也因此而流行,所以我們從UNIX系統(tǒng)開始(注意:我們提到的UNIX還包含其他系統(tǒng),如FreeBSD,它是UNIX的一個(gè)分支,但是由于法律原因不使用該名稱)。
1.在UNIX系統(tǒng)上編輯
UNIX C沒(méi)有自己的編輯器,但是可以使用通用的UNIX編輯器,如emacs、jove、vi或X Window System文本編輯器。
作為程序員,要負(fù)責(zé)輸入正確的程序和為儲(chǔ)存該程序的文件起一個(gè)合適的文件名。如前所述,文件名應(yīng)該以.c結(jié)尾。注意,UNIX區(qū)分大小寫。因此,budget.c、BUDGET.c和Budget.c是3個(gè)不同但都有效的C源文件名。但是BUDGET.C是無(wú)效文件名,因?yàn)樵撁Q的擴(kuò)展名使用了大寫C而不是小寫c。
假設(shè)我們?cè)趘i編譯器中編寫了下面的程序,并將其儲(chǔ)存在inform.c文件中:
#include <stdio.h>
int main(void)
{
printf("A .c is used to end a C program filename.\n");
return 0;
}
以上文本就是源代碼,inform.c是源文件。注意,源文件是整個(gè)編譯過(guò)程的開始,不是結(jié)束。
2.在UNIX系統(tǒng)上編譯
雖然在我們看來(lái),程序完美無(wú)缺,但是對(duì)計(jì)算機(jī)而言,這是一堆亂碼。計(jì)算機(jī)不明白#include 和printf是什么(也許你現(xiàn)在也不明白,但是學(xué)到后面就會(huì)明白,而計(jì)算機(jī)卻不會(huì))。如前所述,我們需要編譯器將我們編寫的代碼(源代碼)翻譯成計(jì)算機(jī)能看懂的代碼(機(jī)器代碼)。最后生成的可執(zhí)行文件中包含計(jì)算機(jī)要完成任務(wù)所需的所有機(jī)器代碼。
以前,UNIX C編譯器要調(diào)用語(yǔ)言定義的cc命令。但是,它沒(méi)有跟上標(biāo)準(zhǔn)發(fā)展的腳步,已經(jīng)退出了歷史舞臺(tái)。但是,UNIX系統(tǒng)提供的C編譯器通常來(lái)自一些其他源,然后以cc命令作為編譯器的別名。因此,雖然在不同的系統(tǒng)中會(huì)調(diào)用不同的編譯器,但用戶仍可以繼續(xù)使用相同的命令。
編譯inform.c,要輸入以下命令:
cc inform.c
幾秒鐘后,會(huì)返回 UNIX 的提示,告訴用戶任務(wù)已完成。如果程序編寫錯(cuò)誤,你可能會(huì)看到警告或錯(cuò)誤消息,但我們先假設(shè)編寫的程序完全正確(如果編譯器報(bào)告void的錯(cuò)誤,說(shuō)明你的系統(tǒng)未更新成ANSI C編譯器,只需刪除void即可)。如果使用ls命令列出文件,會(huì)發(fā)現(xiàn)有一個(gè)a.out文件(見(jiàn)圖1.5)。該文件是包含已翻譯(或已編譯)程序的可執(zhí)行文件。要運(yùn)行該文件,只需輸入:
a.out
輸出內(nèi)容如下:
A .c is used to end a C program filename.
?

如果要儲(chǔ)存可執(zhí)行文件(a.out),應(yīng)該把它重命名。否則,該文件會(huì)被下一次編譯程序時(shí)生成的新a.out文件替換。
如何處理目標(biāo)代碼?C 編譯器會(huì)創(chuàng)建一個(gè)與源代碼基本名相同的目標(biāo)代碼文件,但是其擴(kuò)展名是.o。在該例中,目標(biāo)代碼文件是 inform.o。然而,卻找不到這個(gè)文件,因?yàn)橐坏╂溄悠魃闪送暾目蓤?zhí)行程序,就會(huì)將其刪除。如果原始程序有多個(gè)源代碼文件,則保留目標(biāo)代碼文件。學(xué)到后面多文 件程序時(shí),你會(huì)明白到這樣做的好處。
1.8.3 GNU編譯器集合和LLVM項(xiàng)目
GNU項(xiàng)目始于1987年,是一個(gè)開發(fā)大量免費(fèi)UNIX軟件的集合(GNU的意思是“GNU’s Not UNIX”,即GNU不是UNIX)。GNU編譯器集合(也被稱為GCC,其中包含GCC C編譯器)是該項(xiàng)目的產(chǎn)品之一。GCC在一個(gè)指導(dǎo)委員會(huì)的帶領(lǐng)下,持續(xù)不斷地開發(fā),它的C編譯器緊跟C標(biāo)準(zhǔn)的改動(dòng)。GCC有各種版本以適應(yīng)不同的硬件平臺(tái)和操作系統(tǒng),包括UNIX、Linux和Windows。用gcc命令便可調(diào)用GCC C編譯器。許多使用gcc的系統(tǒng)都用cc作為gcc的別名。
LLVM項(xiàng)目成為cc的另一個(gè)替代品。該項(xiàng)目是與編譯器相關(guān)的開源軟件集合,始于伊利諾伊大學(xué)的2000份研究項(xiàng)目。它的 Clang編譯器處理C代碼,可以通過(guò)clang調(diào)用。有多種版本供不同的平臺(tái)使用,包括Linux。2012年,Clang成為FreeBSD的默認(rèn)C編譯器。Clang也對(duì)最新的C標(biāo)準(zhǔn)支持得很好。
GNU和LLVM都可以使用-v選項(xiàng)來(lái)顯示版本信息,因此各系統(tǒng)都使用cc別名來(lái)代替gcc或clang命令。以下組合:
cc -v
顯示你所使用的編譯器及其版本。
gcc和clang命令都可以根據(jù)不同的版本選擇運(yùn)行時(shí)選項(xiàng)來(lái)調(diào)用不同C標(biāo)準(zhǔn)。
gcc -std=c99 inform.c[3]
gcc -std=c1x inform.c
gcc -std=c11 inform.c
?第1行調(diào)用C99標(biāo)準(zhǔn),第2行調(diào)用GCC接受C11之前的草案標(biāo)準(zhǔn),第3行調(diào)用GCC接受的C11標(biāo)準(zhǔn)版本。Clang編譯器在這一點(diǎn)上用法與GCC相同。
1.8.4 Linux系統(tǒng)
Linux是一個(gè)開源、流行、類似于UNIX的操作系統(tǒng),可在不同平臺(tái)(包括PC和Mac)上運(yùn)行。在Linux中準(zhǔn)備C程序與在UNIX系統(tǒng)中幾乎一樣,不同的是要使用GNU提供的GCC公共域C編譯器。編譯命令類似于:
gcc inform.c
注意,在安裝Linux時(shí),可選擇是否安裝GCC。如果之前沒(méi)有安裝GCC,則必須安裝。通常,安裝過(guò)程會(huì)將cc作為gcc的別名,因此可以在命令行中使用cc來(lái)代替gcc。
欲詳細(xì)了解GCC和最新發(fā)布的版本,請(qǐng)?jiān)L問(wèn)http://www.gnu.org/software/gcc/index.html。
1.8.5 PC的命令行編譯器
C編譯器不是標(biāo)準(zhǔn)Windows軟件包的一部分,因此需要從別處獲取并安裝C編譯器。可以從互聯(lián)網(wǎng)免費(fèi)下載Cygwin和MinGW,這樣便可在PC上通過(guò)命令行使用GCC編譯器。Cygwin在自己的視窗運(yùn)行,模仿Linux命令行環(huán)境,有一行命令提示。MinGW在Windows的命令提示模式中運(yùn)行。這和GCC的最新版本一樣,支持C99和C11最新的一些功能。Borland的C++編譯器5.5也可以免費(fèi)下載,支持C90。
源代碼文件應(yīng)該是文本文件,不是字處理器文件(字處理器文件包含許多額外的信息,如字體和格式等)。因此,要使用文本編輯器(如,Windows Notepad)來(lái)編輯源代碼。如果使用字處理器,要以文本模式另存文件。源代碼文件的擴(kuò)展名應(yīng)該是.c。一些字處理器會(huì)為文本文件自動(dòng)添加.txt 擴(kuò)展名。如果出現(xiàn)這種情況,要更改文件名,把txt替換成c。
通常,C編譯器生成的中間目標(biāo)代碼文件的擴(kuò)展名是.obj(也可能是其他擴(kuò)展名)。與UNIX編譯器不同,這些編譯器在完成編譯后通常不會(huì)刪除這些中間文件。有些編譯器生成帶.asm擴(kuò)展名的匯編語(yǔ)言文件,而有些編譯器則使用自己特有的格式。
一些編譯器在編譯后會(huì)自動(dòng)運(yùn)行鏈接器,另一些要求用戶手動(dòng)運(yùn)行鏈接器。在可執(zhí)行文件中鏈接的結(jié)果是,在原始的源代碼基本名后面加上.exe擴(kuò)展名。例如,編譯和鏈接concrete.c源代碼文件,生成的是concrete.exe文件??梢栽诿钚休斎牖久麃?lái)運(yùn)行該程序:
C>concrete
1.8.6 集成開發(fā)環(huán)境(Windows)
許多供應(yīng)商(包括微軟、Embarcadero、Digital Mars)都提供Windows下的集成開發(fā)環(huán)境,或稱為IDE(目前,大多數(shù)IDE都是C和C++結(jié)合的編譯器)??梢悦赓M(fèi)下載的IDE有Microsoft Visual Studio Express和Pelles C。利用集成開發(fā)環(huán)境可以快速開發(fā)C程序。關(guān)鍵是,這些IDE都內(nèi)置了用于編寫C程序的編輯器。這類集成開發(fā)環(huán)境都提供了各種菜單(如,命名、保存源代碼文件、編譯程序、運(yùn)行程序等),用戶不用離開IDE就能順利編寫、編譯和運(yùn)行程序。如果編譯器發(fā)現(xiàn)錯(cuò)誤,會(huì)返回編輯器中,標(biāo)出有錯(cuò)誤的行號(hào),并簡(jiǎn)單描述情況。
初次接觸Windows IDE可能會(huì)望而生畏,因?yàn)樗峁┝硕喾N目標(biāo)(target),即運(yùn)行程序的多種環(huán)境。例如,IDE提供了32位Windows程序、64位Windows程序、動(dòng)態(tài)鏈接庫(kù)文件(DLL)等。許多目標(biāo)都涉及Windows圖形界面。要管理這些(及其他)選擇,通常要先創(chuàng)建一個(gè)項(xiàng)目(project),以便稍后在其中添加待使用的源代碼文件名。不同的產(chǎn)品具體步驟不同。一般而言,首先使用【文件】菜單或【項(xiàng)目】菜單創(chuàng)建一個(gè)項(xiàng)目。選擇正確的項(xiàng)目形式非常重要。本書中的例子都是一般示例,針對(duì)在簡(jiǎn)單的命令行環(huán)境中運(yùn)行而設(shè)計(jì)。Windows IDE提供多種選擇以滿足用戶的不同需求。例如,Microsoft Visual Studio提供【W(wǎng)in32控制臺(tái)應(yīng)用程序】選項(xiàng)。對(duì)于其他系統(tǒng),查找一個(gè)諸如【DOS EXE】、【Console】或【Character Mode】的可執(zhí)行選項(xiàng)。選擇這些模式后,將在一個(gè)類控制臺(tái)窗口中運(yùn)行可執(zhí)行程序。選擇好正確的項(xiàng)目類型后,使用IDE的菜單打開一個(gè)新的源代碼文件。對(duì)于大多數(shù)產(chǎn)品而言,使用【文件】菜單就能完成。你可能需要其他步驟將源文件添加到項(xiàng)目中。
通常,Windows IDE既可處理C也可處理C++,因此要指定待處理的程序是C還是C++。有些產(chǎn)品用項(xiàng)目類型來(lái)區(qū)分兩者,有些產(chǎn)品(如,MicrosoftVisual C++)用.c文件擴(kuò)展名來(lái)指明使用C而不是C++。當(dāng)然,大多數(shù)C程序也可以作為C++程序運(yùn)行。欲了解C和C++的區(qū)別,請(qǐng)參閱參考資料IX。
你可能會(huì)遇到一個(gè)問(wèn)題:在程序執(zhí)行完畢后,執(zhí)行程序的窗口立即消失。如果不希望出現(xiàn)這種情況,可以讓程序暫停,直到按下Enter鍵,窗口才消失。要實(shí)現(xiàn)這種效果,可以在程序的最后(return這行代碼之前)添加下面一行代碼:
getchar();
該行讀取一次鍵的按下,所以程序在用戶按下Enter鍵之前會(huì)暫停。有時(shí)根據(jù)程序的需要,可能還需要一個(gè)擊鍵等待。這種情況下,必須用兩次getchar():
getchar();
getchar();
例如,程序在最后提示用戶輸入體重。用戶鍵入體重后,按下Enter鍵以輸入數(shù)據(jù)。程序?qū)⒆x取體重,第1個(gè)getchar()讀取Enter鍵,第2個(gè)getchar()會(huì)導(dǎo)致程序暫停,直至用戶再次按下Enter鍵。如果你現(xiàn)在不知所云,沒(méi)關(guān)系,在學(xué)完C輸出后就會(huì)明白。到時(shí),我們會(huì)提醒讀者使用這種方法。
雖然許多IDE在使用上大體一致,但是細(xì)節(jié)上有所不同。就一個(gè)產(chǎn)品的系列而言,不同版本也是如此。要經(jīng)過(guò)一段時(shí)間的實(shí)踐,才會(huì)熟悉編譯器的工作方式。必要時(shí),還需閱讀使用手冊(cè)或網(wǎng)上教程。
Microsoft Visual Studio和C標(biāo)準(zhǔn)
在Windows軟件開發(fā)中,Microsoft Visual Studio及其免費(fèi)版本MicrosoftVisual Studio Express都久負(fù)盛名,它們與C標(biāo)準(zhǔn)的關(guān)系也很重要。然而,微軟鼓勵(lì)程序員從C轉(zhuǎn)向C++和C#。雖然Visual Studio支持C89/90,但是到目前為止,它只選擇性地支持那些在C++新特性中能找到的C標(biāo)準(zhǔn)(如,long long類型)。而且,自2012版本起,Visual Studio不再把C作為項(xiàng)目類型的選項(xiàng)。盡管如此,本書中的絕大多數(shù)程序仍可用Visual Studio來(lái)編譯。在新建項(xiàng)目時(shí),選擇C++選項(xiàng),然后選擇【W(wǎng)in32控制臺(tái)應(yīng)用程序】,在應(yīng)用設(shè)置中選擇【空項(xiàng)目】。幾乎所有的C程序都能與C++程序兼容。所以,本書中的絕大多數(shù)C程序都可作為C++程序運(yùn)行?;蛘?,在選擇C++選項(xiàng)后,將默認(rèn)的源文件擴(kuò)展名.cpp替換成.c,編譯器便會(huì)使用C語(yǔ)言的規(guī)則代替C++。
1.8.7 Windows/Linux
許多Linux發(fā)行版都可以安裝在Windows系統(tǒng)中,以創(chuàng)建雙系統(tǒng)。一些存儲(chǔ)器會(huì)為L(zhǎng)inux系統(tǒng)預(yù)留空間,以便可以啟動(dòng)Windows或Linux??梢栽赪indows系統(tǒng)中運(yùn)行Linux程序,或在Linux系統(tǒng)中運(yùn)行Windows程序。不能通過(guò)Windows系統(tǒng)訪問(wèn)Linux文件,但是可以通過(guò)Linux系統(tǒng)訪問(wèn)Windows文檔。
1.8.8 Macintosh中的C
目前,蘋果免費(fèi)提供Xcode開發(fā)系統(tǒng)下載(過(guò)去,它有時(shí)免費(fèi),有時(shí)付費(fèi))。它允許用戶選擇不同的編程語(yǔ)言,包括C語(yǔ)言。
Xcode 憑借可處理多種編程語(yǔ)言的能力,可用于多平臺(tái),開發(fā)超大型的項(xiàng)目。但是,首先要學(xué)會(huì)如何編寫簡(jiǎn)單的C程序。在Xcode 4.6中,通過(guò)【File】菜單選擇【New Project】,然后選擇【OS X Application CommandLine Tool】,接著輸入產(chǎn)品名并選擇C類型。Xcode使用Clang或GCC C編譯器來(lái)編譯C代碼,它以前默認(rèn)使用GCC,但是現(xiàn)在默認(rèn)使用Clang??梢栽O(shè)置選擇使用哪一個(gè)編譯器和哪一套C標(biāo)準(zhǔn)(因?yàn)樵S可方面的事宜,Xcode中Clang的版本比GCC的版本要新)。
UNIX系統(tǒng)內(nèi)置Mac OS X,終端工具打開的窗口是讓用戶在UNIX命令行環(huán)境中運(yùn)行程序。蘋果在標(biāo)準(zhǔn)軟件包中不提供命令行編譯器,但是,如果下載了 Xcode,還可以下載可選的命令行工具,這樣就可以使用clang和gcc命令在命令行模式中編譯。
1.9 本書的組織結(jié)構(gòu)
本書采用多種方式編排內(nèi)容,其中最直接的方法是介紹A主題的所有內(nèi)容、介紹B主題的所有內(nèi)容,等等。這對(duì)參考類書籍來(lái)說(shuō)尤為重要,讀者可以在同一處找到與主題相關(guān)的所有內(nèi)容。但是,這通常不是學(xué)習(xí)的最佳順序。例如,如果在開始學(xué)習(xí)英語(yǔ)時(shí),先學(xué)完所有的名詞,那你的表達(dá)能力一定很有限。雖然可以指著物品說(shuō)出名稱,但是,如果稍微學(xué)習(xí)一些名詞、動(dòng)詞、形容詞等,再學(xué)習(xí)一些造句規(guī)則,那么你的表達(dá)能力一定會(huì)大幅提高。
為了讓讀者更好地吸收知識(shí),本書采用螺旋式方法,先在前幾個(gè)章節(jié)中介紹一些主題,在后面章節(jié)再詳細(xì)討論相關(guān)內(nèi)容。例如,對(duì)學(xué)習(xí)C語(yǔ)言而言,理解函數(shù)至關(guān)重要。因此,我們?cè)谇皫讉€(gè)章節(jié)中安排一些與函數(shù)相關(guān)的內(nèi)容,等讀者學(xué)到第 9 章時(shí),已對(duì)函數(shù)有所了解,學(xué)習(xí)使用函數(shù)會(huì)更加容易。與此類似,前幾章還概述了一些字符串和循環(huán)的內(nèi)容。這樣,讀者在完全弄懂這些內(nèi)容之前,就可以在自己的程序中使用這些有用的工具。
1.10 本書的約定
在學(xué)習(xí)C語(yǔ)言之前,先介紹一下本書的格式。
1.10.1 字體
本書用類似在屏幕上或打印輸出時(shí)的字體(一種等寬字體),表示文本程序和計(jì)算機(jī)輸入、輸出。前面已經(jīng)出現(xiàn)了多次,如果讀者沒(méi)有注意到,字體如下所示:
#include <stdio.h>
int main(void)
{
? ? printf("Concrete contains gravel and cement.\n");
? ? return 0;
}
在涉及與代碼相關(guān)的術(shù)語(yǔ)時(shí),也使用相同的等寬字體,如stdio.h。本書用等寬斜體表示占位符,可以用具體的項(xiàng)替換這些占位符。例如,下面是一個(gè)聲明的模型:
type_name variable_name;
這里,可用int替換type_name,用zebra_count替換variable_name。
1.10.2 程序輸出
本書用相同的字體表示計(jì)算機(jī)的輸出,粗體表示用戶輸入。例如,下面是第14章中一個(gè)程序的輸出:
Please enter the book title.
Press [enter] at the start of a line to stop.
My Life as a Budgie
Now enter the author.
Mack Zackles
如上所示,以標(biāo)準(zhǔn)計(jì)算機(jī)字體顯示的行表示程序的輸出,粗體行表示用戶的輸入。
可以通過(guò)多種方式與計(jì)算機(jī)交互。在這里,我們假設(shè)讀者使用鍵盤鍵入內(nèi)容,在屏幕上閱讀計(jì)算機(jī)的響應(yīng)。
1.特殊的擊鍵
通常,通過(guò)按下標(biāo)有 Enter、c/r、Return 或一些其他文字的鍵來(lái)發(fā)送指令。本書將這些按鍵統(tǒng)一稱為Enter鍵。一般情況下,我們默認(rèn)你在每行輸入的末尾都會(huì)按下Enter鍵。盡管如此,為了標(biāo)示一些特定的位置,本書使用[enter]顯式標(biāo)出Enter鍵。方括號(hào)表示按下一次Enter鍵,而不是輸入enter。
除此之外,書中還會(huì)提到控制字符(如,Ctrl+D)。這種寫法的意思是,在按下Ctrl鍵(也可能是Control鍵)的同時(shí)按下D鍵。
2.本書使用的系統(tǒng)
C 語(yǔ)言的某些方面(如,儲(chǔ)存數(shù)字的空間大?。┮蛳到y(tǒng)而異。本書在示例中提到“我們的系統(tǒng)”時(shí),通常是指在iMac上運(yùn)行OS X 10.8.4,使用Xcode 4.6.2開發(fā)系統(tǒng)的Clang 3.2編譯器。本書的大部分程序都能使用Windows7系統(tǒng)的Microsoft Visual Studio Express 2012和Pelles C 7.0,以及Ubuntu13.04Linux系統(tǒng)的GCC 4.7.3進(jìn)行編譯。
3.讀者的系統(tǒng)
你需要一個(gè)C編譯器或訪問(wèn)一個(gè)C編譯器。C程序可以在多種計(jì)算機(jī)系統(tǒng)中運(yùn)行,因此你的選擇面很廣。確保你使用的C編譯器與當(dāng)前使用的計(jì)算機(jī)系統(tǒng)匹配。本書中,除了某些示例要求編譯器支持C99或C11標(biāo)準(zhǔn),其余大部分示例都可在C90編譯器中運(yùn)行。如果你使用的編譯器是早于ANSI/ISO的老式編譯器,在編譯時(shí)肯定要經(jīng)常調(diào)整,很不方便。與其如此,不如換個(gè)新的編譯器。
大部分編譯器供應(yīng)商都為學(xué)生和教學(xué)人員提供特惠版本,詳情請(qǐng)查看供應(yīng)商的網(wǎng)站。
1.10.3 特殊元素
本書包含一些強(qiáng)調(diào)特定知識(shí)點(diǎn)的特殊元素,提示、注意、警告,將以如下形式出現(xiàn)在本書中:
邊欄
邊欄提供更深入的討論或額外的背景,有助于解釋當(dāng)前的主題。
提示
提示一般都短小精悍,幫助讀者理解一些特殊的編程情況。
警告
用于警告讀者注意一些潛在的陷阱。
注意
提供一些評(píng)論,提醒讀者不要誤入歧途。
1.11 本章小結(jié)
C是強(qiáng)大而簡(jiǎn)潔的編程語(yǔ)言。它之所以流行,在于自身提供大量的實(shí)用編程工具,能很好地控制硬件。而且,與大多數(shù)其他程序相比,C程序更容易從一個(gè)系統(tǒng)移植到另一個(gè)系統(tǒng)。
C是編譯型語(yǔ)言。C編譯器和鏈接器是把C語(yǔ)言源代碼轉(zhuǎn)換成可執(zhí)行代碼的程序。
用C語(yǔ)言編程可能費(fèi)力、困難,讓你感到沮喪,但是它也可以激發(fā)你的興趣,讓你興奮、滿意。我們希望你在愉快的學(xué)習(xí)過(guò)程中愛(ài)上C。
1.12 復(fù)習(xí)題
復(fù)習(xí)題的參考答案在附錄A中。
1.對(duì)編程而言,可移植性意味著什么?
2.解釋源代碼文件、目標(biāo)代碼文件和可執(zhí)行文件有什么區(qū)別?
3.編程的7個(gè)主要步驟是什么?
4.編譯器的任務(wù)是什么?
5.鏈接器的任務(wù)是什么?
1.13 編程練習(xí)
我們尚未要求你編寫C代碼,該練習(xí)側(cè)重于編程過(guò)程的早期步驟。
1.你剛被MacroMuscle有限公司聘用。該公司準(zhǔn)備進(jìn)入歐洲市場(chǎng),需要一個(gè)把英寸單位轉(zhuǎn)換為厘米單位(1 英寸=2.54 厘米)的程序。該程序要提示用戶輸入英寸值。你的任務(wù)是定義程序目標(biāo)和設(shè)計(jì)程序(編程過(guò)程的第1步和第2步)。

[1].國(guó)際C語(yǔ)言混亂代碼大賽(IOCCC,The International Obfuscated C Code Contest)。這是一項(xiàng)國(guó)際編程賽事,從1984年開始,每年舉辦一次(1997、1999、2002、2003和2006年除外),目的是寫出最有創(chuàng)意且最讓人難以理解的C語(yǔ)言代碼。——譯者注
[2].VAX(Virtual Address eXtension)是一種可支持機(jī)器語(yǔ)言和虛擬地址的32位小型計(jì)算機(jī)。VMS(Virtual Memory System)是舊名,現(xiàn)在叫OpenVMS,是一種用于服務(wù)器的操作系統(tǒng),可在VAX、Alpha或Itanium處理器系列平臺(tái)上運(yùn)行?!g者注
[3].GCC最基本的用法是:gcc [options] [filenames],其中options是所需的參數(shù),filenames是文件名?!g者注