語言教程程序設(shè)計_第1頁
語言教程程序設(shè)計_第2頁
語言教程程序設(shè)計_第3頁
語言教程程序設(shè)計_第4頁
語言教程程序設(shè)計_第5頁
已閱讀5頁,還剩246頁未讀, 繼續(xù)免費閱讀

下載本文檔

版權(quán)說明:本文檔由用戶提供并上傳,收益歸屬內(nèi)容提供方,若內(nèi)容存在侵權(quán),請進(jìn)行舉報或認(rèn)領(lǐng)

文檔簡介

C第1C

第1無論如何,經(jīng)驗豐富的程序員應(yīng)能從本章所介紹的有關(guān)材料中推斷他們在程序設(shè)計中需要的東西。初學(xué)者則應(yīng)編寫類似的小程序來充實它。這兩種人都可以把本章當(dāng)作了解后續(xù)各章的詳細(xì)內(nèi)容的框架。 o,在初學(xué)語言時這是一個很大的,要越過這個,首先必須建立程序文本,然后成功地對它進(jìn)行編譯,并裝入、運行,最后再看看所產(chǎn)生的輸出。只要把這些操作細(xì)節(jié)掌握了,其他內(nèi)容就比較容易了。在C語言中,用如下程序打印“o,#include{ o,}至于如何運行這個程序取決于使用的系統(tǒng)。作為一個特殊的例NIX統(tǒng)中 如果在輸入上述程序時沒有出現(xiàn)錯誤(例如沒有漏掉字符或錯拼字符),那么編譯程序?qū)⑼鵤.o,#include包含有關(guān)標(biāo)準(zhǔn)庫的信{ 定義名為main的函數(shù),它不接收變元main的語句括在花括號main函數(shù)調(diào)用庫函數(shù)printf可打印字符序列,\n代表換行}下面對這個程序本身做一些解釋說明。每一個程序,不論大小如何,都由函數(shù)和變量組成。函數(shù)中包含若干用于指定所要做的計算操作的語句,而變量則用于在計算過程中有關(guān)值。C中的函數(shù)類似于FOTRAN語言中的子程序與函數(shù)或Pascal語言中的過程與函數(shù)。在本例中,函數(shù)的名字為main。一般而言,可以給函數(shù)任意命名,但main是一個特殊的函數(shù)名,每一個程序都從名為mainmainmain函數(shù)通常要調(diào)用其他函數(shù)來協(xié)助其完成某些工作,調(diào)用的函數(shù)有些是程序人員自己編寫的,有些則由系統(tǒng)函數(shù)庫提供。上述程序的第一行#includeC源程序的開始處都包含這一行。在第7章和附錄中對標(biāo)準(zhǔn)庫進(jìn)行詳細(xì)介紹。在函數(shù)之間進(jìn)行數(shù)據(jù)通信的法是讓調(diào)用函數(shù)向被調(diào)用函數(shù)提供一串叫做變元的值。(括起來。在本例子中,所定義的main函函數(shù)中的語句用一對花括號{}括起來。本例中的mainprintf("o,面這個語句就是用變元 o,world\n"來調(diào)用函。

引號括住的字符序列叫做字符串或字符串常量,如"o,world\n"就是一個字符串。目在C語言中,字符序列\(zhòng)n表示換行符,在打印時它用于指示從下一行的左邊換行打印。如果在字符串中遺漏了\n(一個值得做的試驗printf函數(shù)的變元中必須用\n引入換行符,如果用程序中的換行來代替\ o,printf函數(shù)不會自動換行,我們可以多次調(diào)用這個函數(shù)來分階段打印一輸出行。上面給出的第一個程序也可以寫成如下形式:#include{}

o,請注意,\n只表示一個字符。諸如\n等換碼序列為表示不能打印或不可見字符提供了一種通用可擴(kuò)充機(jī)制。除此之外,C語言提供的換碼序列還有:表示制表符的\t,表示回退符的\b,表示雙引號的\",表示反斜杠符本身的\\。2.3節(jié)將給出換碼序列的完整列表。練習(xí)1-1請讀者在自己的系統(tǒng)上運行“o,world”程序。再做個實驗,讓程序中遺漏一練習(xí)1-2做個實驗,觀察一下當(dāng)printf函數(shù)的變元字符串中包含\c(其中c是上面未列出的某

?C=(5/9)(?F-0--4 這個程序本身仍只由一個名為main的函數(shù)定組成,要前面用打“ o,world”的程序長,但并不復(fù)雜。這個程序中引入了一些新的概念,包括注解、說明、變量、算術(shù)表達(dá)式、循環(huán)以及格式輸出。該程序如下:#include/*fahr0,20,300打印華氏溫度與攝氏溫度對照表*/{intfahr,intlower,upper,lower=0;/*溫度表的下限*/upper=300;/*溫度表的上限*/step=20;/*步長*/fahr=while(fahr<={celsius=5*(fahr-32)/printf("%d\t%d\n",fahr,celsius);fahr=fahr+step;}}/*對fahr=0,20,打印華氏溫度與攝氏溫度對照/*與*/之間的字符序列在編譯時被忽略掉,它們可以在程序中自由地使用,目的是為了使程序更易于理解。注解可以出現(xiàn)在任何空格、制表符或換行符可以出現(xiàn)的地方。在C語言中,所有變量都必須先說明后使用,說明通常放在函數(shù)開始處的可執(zhí)行語句之前。說明用于變量的性質(zhì),它由一個類型名與若干所要說明的變量組成,例如intfahr,intlower,upper,其中,類型int表示所列變量為整數(shù)變量,與之相對,float表示所列變量為浮點變量(浮點數(shù)可以有小數(shù)部分。int與float類型的取值范圍取決于所使用的機(jī)器。對于int類型,通常為16位(取值在-32768~ 除int與float之外,C 這些數(shù)據(jù)類型對象的大小也取決于機(jī)器。另外,還有由這些基本類型組成的數(shù)組、結(jié)構(gòu)與聯(lián)合類型、指向這些類型的指針類型以及返回這些類型的函數(shù),在后面適當(dāng)?shù)恼鹿?jié)再分別介紹它們。上面溫度轉(zhuǎn)換程序計算以4lower=0;upper=300;step=20;fahr=lower;溫度轉(zhuǎn)換表中的每一行均以相同的方式計算,故可以用循環(huán)語句來重復(fù)產(chǎn)生各行輸出,每行重復(fù)一次。這就是whil循環(huán)語句的用途:while(fahr<=upper)}while循環(huán)語句的執(zhí)行步驟如下:首先測試圓括號中的條件。如果條件為真(fahr小于等于upper,則執(zhí)行循環(huán)體(括在花括號中的三個語句。然后再重新測試該條件,如果為真,則再次執(zhí)行該循環(huán)體。當(dāng)該條件測試為假(fahr大于upper)時,循環(huán)結(jié)束,繼續(xù)執(zhí)行跟在該循環(huán)語句之后的下一個語句。在本程序中,循環(huán)語句后再沒有其他語句,因此整個程序終止執(zhí)行。while(i<j)i=2*while控制的語句向里縮入一個制表位(在書中以四個空格表示,這樣就可以很容易地看出循環(huán)語句中包含那些語句。這種縮進(jìn)方式強(qiáng)化了程序的邏輯結(jié)構(gòu)。盡管C編譯程序并不關(guān)心程序的具體形式,但使程序在適當(dāng)位置采用縮進(jìn)空格的風(fēng)格對于使程序更易于為人們閱讀是很重要的。我們建議每行只寫一個語句,并在運算符兩邊各放一個空格字符以使運算組合更清楚?;ɡㄌ柕奈恢貌惶匾M管每個人都有他所喜愛的風(fēng)格。我們從一些比較流行的風(fēng)格中選擇了一種。讀者可以選擇自己所合適的風(fēng)格并一直使用它。celsius=5*(fahr-32)/celsius。在該語句中,之所以把表達(dá)式寫成先乘5然后再除以9而不直接寫成5/9,是因為在C語言及其他許多語言中,整數(shù)除法要進(jìn)5和95/9相除后所截取得的結(jié)果為00。這個例子也對printf函數(shù)的工作功能做了的介紹。printf是一個通用輸出格式化函數(shù),第7章將對此做詳細(xì)介紹。該函數(shù)的第一個變元是要打印的字符串,其中百分號(%)變元(第2、第3個?變元)之一對其進(jìn)行替換,以及打印變元的格式。例如,%d指定一個整數(shù)變printf("%d\t%d\n",fahr,用于打印兩個整數(shù)fahr與celsius值并在兩者之間空一個制表位(tprintf函數(shù)第1個變元中的各個%分別對應(yīng)于第2個、第3個?第n個變元,它們在數(shù)目和類型上順便,printf函數(shù)并不是C語言本身的一部分,C語言本身沒有定義輸入輸出功能。printf是標(biāo)準(zhǔn)庫函數(shù)中一個有用的函數(shù),標(biāo)準(zhǔn)庫函數(shù)一般在C程序中都可以使用。ANSI標(biāo)準(zhǔn)中定義了為了集中討論C語言本身,在第7章之前的各章中不再對輸入輸出做的介紹,特別是把格式輸入延后到第7章。如果讀者想要了解數(shù)據(jù)輸入,請先閱讀7.4節(jié)對scanf函數(shù)的討論。scanf上面這個溫度轉(zhuǎn)換程序存在著兩個問題。比較簡單的一個問題是,由于所輸出的數(shù)不是右對齊的,輸出顯得不是特別好看。這個問題比較容易解決:只要在printf語句的第1個變元的%d中指明打印長度,則打印的數(shù)字會在打印區(qū)域內(nèi)右對齊。例如,可以用printf("%3d%6d\n",fahr,打印fahr與celsius的值,使得fahr的值占3個數(shù)字寬、celsius的值占60--4?另一個較為嚴(yán)重的問題是,由于使用的是整數(shù)算術(shù)運算,故所求得的攝氏溫度不很精確,例如,與F對應(yīng)的精確的攝氏溫度為-17.8℃,而不是-17℃。為了得到更精確的答案,應(yīng)該用浮點算術(shù)運算來代替上面的整數(shù)算術(shù)運算。這就要求對程序做適當(dāng)修改。下面給出這個程序的第2#include/*對fahr0,20,300打印華氏溫度與攝氏溫度對照表;浮點數(shù)版本*/{floatfahr,celsius;intlower,upper,step;lower=0;/*溫度表的下限*/upper=300;/*溫度表的上限*/step=20;/*步長*/fahr=while(fahr<=upper)celsius=(5.0/9.0)*(fahr-32.0);printf("%3.0f%6.1f\n",fahr,celsius);fahr=fahr+step;}}這個版與一個版基相同,是把fahr與celsius說明成float浮點類型,轉(zhuǎn)換的表達(dá)也更然。一版中,之以用5/9為0而,在此版本中5.0/9.0是兩個浮點數(shù)相除,不需要截取。如果某個算術(shù)運算符的運算分量均為整數(shù)類型,那么就執(zhí)行整數(shù)運算。然而,如果某個算術(shù)運算符有一個浮點運算分量和一個整數(shù)運算分量,那么這個整數(shù)運算分量在開始運算之前會fahr-32,32在運算過程中將被自動轉(zhuǎn)換成浮點數(shù)再參與運算。不過,在寫浮點常量時最好還是把它寫成帶小數(shù)點,即使該浮點常量取的是整數(shù)值,因為這樣可以強(qiáng)調(diào)其浮點性質(zhì),便于人們閱讀。fahr=while(fahr<=upper也都是以自然的方式執(zhí)行—在運算之前先把int轉(zhuǎn)換成floatprintf中的轉(zhuǎn)換說明%3.0f表明要打印的浮點數(shù)(即fahr)至少占3個字符寬,不帶小數(shù)點與小數(shù)部分。%6.1f表示另一個要打印的數(shù)(celsius至少有61位數(shù)字。輸出類似于如下形式: - - ?在格式說明中可以省去寬度(與小數(shù)點之間的數(shù))與精度(小數(shù)點與字母f之間的數(shù)。例如,%6f的意思是要打印的數(shù)至少有6個字符寬;%.2f說明要打印的數(shù)在小數(shù)點后有兩位小數(shù),但整個數(shù)的寬度不受限制;%f的意思僅僅是要打印的數(shù)為浮點數(shù)。 打印十進(jìn)制整 打印十進(jìn)制整數(shù),至少6個字符 打印浮點 打印浮點數(shù),至少6個字符 打印浮點數(shù),小數(shù)點后有兩位小 打印浮點數(shù),至少6個字符寬,小數(shù)點后有兩位小printf函數(shù)還可以識別如下格式說明:表示八進(jìn)制數(shù)的%o、表示十六進(jìn)制數(shù)的%x、表示練習(xí)1- 練習(xí)1- 對于一個特定任務(wù),可以用多種方法來編寫程序。下面是前面講述的溫度轉(zhuǎn)換程序的一個變種:#include/*打印華氏與攝氏溫度對照表*/main(){intfor(fahr=0;fahr<=300;fahr=fahr+20printf("%3d#%6.1f\n",fahr,(5.0/9.0)*(fahr-32)}這個版本與前一個版本執(zhí)行的結(jié)果相同,但看起來有些不同。一個主要的變化是它刪去了大部fahr,其類型為int。本來用變量表示的下限、上限與步長都在新引入的for語句中作為常量出現(xiàn),用于求攝氏溫度的表達(dá)式現(xiàn)在已變成了printf函數(shù)的第3個變元,而不再是一個獨立的賦值語句。這最后一點變化說明了一個通用規(guī)則:在所有可以使用某個類型的變量的值的地方,都可printf函數(shù)的第3個變元必須為與%6.1f匹配的浮點值,則可以在這里使用任何浮點表達(dá)式。for語句是一種循環(huán)語句,是while語句的推廣。如果將其與前面介紹的while語句比較,就會fahr=fahr<=fahr=fahr+加步長,并再次對條件求值。一旦求得的條件值為假,那么就終止循環(huán)的執(zhí)行。像while語句一樣,for循環(huán)語句的體可以是單個語句,也可以是用花括號括住的一組語句。初始化部分(第一部分、條件部分(第二部分)與加步長部分(第三部分)均可以是任何表達(dá)式。至于在while與for這兩個循環(huán)語句中使用哪一個,這是隨意的,主要看使用哪一個更能清楚for語句比較適合描述這樣的循環(huán):初值和增量都是單個語句并且是邏輯相關(guān)的,因為for語句把循環(huán)控制語句放在一起,比while語句更緊湊。練習(xí)1- 在結(jié)束對溫度轉(zhuǎn)換程序的討論之前,再來看看符號常量。把300、20等“幻數(shù)”埋在程序中并不是一種好的習(xí)慣,這些數(shù)幾乎沒有向以后可能要閱讀該程序的人提供什么信息,而且使程序的修改變得。處理這種幻數(shù)的法是賦予它們有意義的名字。#define指令就用于把符號名字(或稱為符號常量)定義為一特定的字符串:#define名字替換文#define中定義的名字,該名字既沒有用引號括起來,也不是其他名字的一部分,都用所對應(yīng)的替換文本替換。這里的名字與普通變量名有相同的形式:它們都是以字母打頭的字母或數(shù)字序列。替換文本可以是任何字符序列,而不僅限于數(shù)。#include#defineLOWER0/*表的下限*/#defineUPPER300/*表的上*/#defineSTEP20/*步長*//*打印華氏-攝氏溫度對照表*/main(){intfor(fahr=LOWER;fahr<=UPPER;fahr=fahr+STEPprintf( %6.1f\n",fahr,(5.0/9.0)*(fahr-32)}LOWER、UPPER與STEP等幾個量是符號常量,而不是變量,故不需要出現(xiàn)在說明中。符號常量名通常用大寫字母拼寫,這樣就可以很容易與用小寫字母拼寫的變量名相區(qū)別。注意,#define指令行的末尾沒有分號。接下來討論一些與字符數(shù)據(jù)處理有關(guān)的程序。讀者將會發(fā)現(xiàn),許多程序只不過是這里所討論的程序原型的擴(kuò)充版本。由標(biāo)準(zhǔn)庫提供的輸入輸出模型非常簡單。文本的輸入輸出都是作為字符流處理的,不管它從何處輸入、輸出到何處。文本流是由一行行字符組成的字符序列,而每一行字符則由0個或多個字符組成,并后跟一個換行符。標(biāo)準(zhǔn)庫有責(zé)任使每一輸入輸出流符合這一模型,使用標(biāo)準(zhǔn)庫的程序員不必?fù)?dān)心各字符行在程序外面怎么表示。標(biāo)準(zhǔn)庫中有幾個函數(shù)用于控制一次讀寫一個字符,其中最簡單的是getchar和putchar這兩個函c=getchar輸入字符的方法將在第7putchar(c用于把整數(shù)變量cputchar與printf這兩個函數(shù)文件借助getchar與putchar函數(shù),可以在不掌握其他輸入輸出知識的情況下編寫出許多有用的代碼。最簡單的程序是一次一個字符地把輸入到輸出,其基本思想如下:讀一個while(該字符不是文件結(jié)束指示符下面是其C#include/*用于將輸入到輸出的程序;第1個版本*/main(){intc=getchar();while(c!=EOF){putchar(c);c=getchar(}}像其他許多東西一樣,一個字符不論在鍵盤或屏幕上以什么形式出現(xiàn),在機(jī)器內(nèi)部都是以位模式的。char類型就是專門用于這種字符數(shù)據(jù)的類型,當(dāng)然任何整數(shù)類型也可以用于int需要解決的問題是如何將文件中的有效數(shù)據(jù)與文件結(jié)束標(biāo)記區(qū)分開來。C語言采取的解決方,getchar函數(shù)在沒有輸入時返回一個特殊值,這個特殊值不能與任何實際字符相。這個值叫做EOF(endoffile,文件結(jié)束。必須把cgetchar函數(shù)可能返回的各種值的類型。之所以不把c說明成char類型,是因為c必須大到除了能任何可能的字符外還要能文件結(jié)束符EOF。因此,把c說明成int類型的。EOF是一個在<stdio.h>庫中定義的整數(shù),但其具體的數(shù)值是什么并不重要,只要知道它與charEOF在程序中不依賴于特定的對于經(jīng)驗比較豐富的C程序員,可以把字符程序編寫得更精致些。在C語言中,諸c=getchar(之類的賦值操作是一個表達(dá)式,因而就有一個值,即賦值后位于=左邊變量的值。換言之,賦值可以作為更大的表達(dá)式的一部分出現(xiàn)??梢园褜⒆址x給c的賦值操作放在while循環(huán)語句的測試部分中,即可以將上面的字符程序改寫成如下形式:#include/*用于將輸入到輸出的程序;第個版本*/main(){intwhile((c=getchar())!=EOF)putchar(c);}在這一程序中,while循環(huán)語句先讀一個字符并將其賦給c,然后測試該字符是否為文件結(jié)束標(biāo)記。如果該字符不是文件結(jié)束標(biāo)記,那么就執(zhí)行while語句體,把該字符打印出來。再重復(fù)執(zhí)行該while語句。當(dāng)最后到達(dá)輸入結(jié)束位置時,while循環(huán)語句終止執(zhí)行,從而整個main程序執(zhí)行這個版本的特點是將輸入集中處理—只調(diào)用了一次getchar函數(shù)—這樣使整個程序的規(guī)模有所縮短,所得到的程序更緊湊,從風(fēng)格上講,程序更易閱讀。讀者將會不斷地看到這種風(fēng)(然而,如果再往前走,所編寫出的程序可能很難理解,對這種趨勢進(jìn)行遏制。)在while條件中用于括住賦值表達(dá)式的圓括號不能省略。不等運算符!=的優(yōu)先級要比賦值運算符的優(yōu)先級高,這就是說,在不使用圓括號時關(guān)系測試!=將在賦值之前執(zhí)行。故語句c=getchar()!=c=(getchar()!=EOF這個語句的作用是把c的值置為0或1(取決于getchar函數(shù)在調(diào)用執(zhí)行時所讀的數(shù)據(jù)是否為文件結(jié)束標(biāo)記(的第2)練習(xí)1-6驗證表達(dá)式getchar()!=EOF的值是0還是1練習(xí)1- 編寫一個用于打印EOF值的程序字符計#include/*統(tǒng)計輸入的字符數(shù)個版本*/main(){longnc=while(getchar()!=EOF}引入了一個新的運算符++,其功能是加1nc=nc+來代替它,但++nc比之要更精致,通常效率也更高。與該運算符相對應(yīng)的還有一個減一運算符--。++與--這兩個運算符既可以作為前綴運算符(如++nc),也可以作為后綴運算符(如nc++)。正如第2章將要的,這兩種形式在表達(dá)式中有不同的值,但 與c++都使nc的值這個字符計數(shù)程序沒有用int類型的變量而是用long類型的變量來存放計數(shù)值。long整數(shù)(長整數(shù))至少要占用32位單元。盡管在某些機(jī)器上int與long類型的值具有同樣大小,但在其他機(jī)器上int類型的值可能只有16位單元(最大取值32767,相當(dāng)小的輸入都可能使int類型的如果使用double(雙精度浮點數(shù))類型,那么可以統(tǒng)計的字符。下面不再用while循環(huán)語句而用for循環(huán)語句來說明編寫循環(huán)的另法:#include/*統(tǒng)計輸入的字符數(shù)個版本*/main(){doublefor(nc=0;getchar()!=EOF;++nc;printf("%.0f\n",}%f duble %.0f用于控制不打印小數(shù)點和小數(shù)部分,因此小數(shù)部分 0這個for循環(huán)語句的體是空的,這是因為它的所有工作都在測試(條件)部分與加步長部分做了。但C語求for循環(huán)語句必須有一,因此用單獨的分號代替。單個分號fr的這一要求。把它單獨放在一行是為了使它顯目一點。在結(jié)束討論字符計數(shù)程序之前,請觀察一下以下情況:如果輸入中不包含字符,那么,while語句或forgetchar函數(shù)一次也不會調(diào)用,程序的執(zhí)行結(jié)果為0,這個結(jié)果也是正確的。這一點很重要。while語句與for語句的優(yōu)點之一就是在執(zhí)行循環(huán)體之前就對條件進(jìn)試。如果沒有什么事要做,那么就不去做,即使它意味著不執(zhí)行循環(huán)體。程序在出現(xiàn)0長度的輸入時應(yīng)表現(xiàn)得機(jī)靈一點。while語句與for語句有助于保證在出現(xiàn)邊界條件時做合理的事情。行計下一個程序用于統(tǒng)計輸入的行數(shù)。正如上文提到的,標(biāo)準(zhǔn)庫保證輸入文本流是以行序列的形式出現(xiàn)的,每一行均以換行符結(jié)束。因此,統(tǒng)計輸入的行數(shù)就等價于統(tǒng)計換行符的個數(shù)。#include/*統(tǒng)計輸入的行數(shù)*/main(){longc,nl=while((c=getchar())!=EOF)if(c=='\n')}ilif語句,該if語句用于控制增值++nl。if語句執(zhí)行時首先測試圓括號中的條件,如果該條件為真,那么就執(zhí)行內(nèi)嵌在其中的語句(或括在花括號中的一組語句。這里再次用縮進(jìn)方式指示哪個語句被哪個語句控制。雙等于號==是C語言中表示“等于”的運算符(Pascal中的單等于號=及FOTRN中的.EQ.。由于C已用單等于號=別號==表示相等測試。注意,C=來表示==2章所述,即使這樣誤用了,得到的通常仍是合法的表達(dá)式,故系統(tǒng)不會給出警告信息。夾在單引號中的字符表示一個整數(shù)值,這個值等于該字符在機(jī)器字符集中的數(shù)值。它叫做字符常量,盡管它只不過是比較小的整數(shù)的另一種寫法。例如,'A'即字符常量;在ASCII集中其值為65(即字符65'A'要比用65優(yōu)越,'A'的意義清楚,并獨立于特定的字符集。字符串常量中使用的換碼序列也是合法的字符常量,故'\n'表示換行符的值,在ASCII字符集中其值為10。我們應(yīng)該仔細(xì)注意到,'\n'是單個字符,在表達(dá)式中它只不過是一個整數(shù);而另一方面,"\n"是一個只包含一個字符的字符串常量。有關(guān)字符串與字符之間的關(guān)系將在第2章做練習(xí)1-8練習(xí)1-9編寫一個程序,把它的輸入到輸出,并在此過程中將相連的多個空格用一個空練習(xí)1-10 編寫一個程序,把它的輸入到輸出,并在此過程中把制表符換成\t、把回退.5.4比較寬,它是任何其中不包含空格、制表符或換行符的字符序列。下面這個比較簡單的版本是在UNIX系統(tǒng)上實現(xiàn)的完成這能的程序wc:#include#defineIN 1/*在單詞內(nèi)*/#defineOUT0/*在單詞外main(){intc,nl,nw,nc,state=nl=nw=nc=while((c=getchar())!=EOF)if(c=='\n'if(c==''||c=='\n'||c=='\t')state=OUT;elseif(state==OUT)state=}}printf("%d%d%d\n",nl,nw,}程序在執(zhí)行時,每當(dāng)遇到單詞的第一個字符,它就作為一個新單詞加以統(tǒng)計。state記錄程序是否正在處理一個單詞(是否在一個單詞中,它的初值是“不在單詞中”,即被賦初值為OUTIN與OUT1與0,主要是因為這可以使程序更可讀。在比較小的程序中,這樣做也許看不出有什么區(qū)別,但在比較大的程序中,如果從一開始就這樣做,那么所增加的一點工作量與所提高的程序的明晰性相比是很值得的。讀者也會發(fā)現(xiàn),在程序中,如果幻數(shù)僅僅出現(xiàn)在符號常量中,那么對程序做大量修改就顯得比較容易。nl=nw=nc= nw與nc置為 nl=(nw=(nc=0||的意思是O或)if(c==''||c=='\n'||c=='\t'的意思是“如果c是空格或c是換行符或c是制表符”(回憶一下,換碼序列\(zhòng)t示&&,其含義是AND(與)||高一級。經(jīng)由&&||連接的表達(dá)式由左至右求值,在求值過程中只要已得知真或假,求值就停止。如果c是一個空格,那么就沒有必要再測試它是否為換行符或制表符,故后兩個測試無需再進(jìn)行。在這里這倒不特別重要,但在某些更復(fù)雜的情況下這樣做就顯得很重要,不久會看到這種例子。這個例子中還給出了else部分,它指定當(dāng)if語句中的條件部分為假時所要采取的動作。其一般形式為:if(表達(dá)式語語12在if-else的兩個語句中有一個并且只有一個被執(zhí)行。如果表達(dá)式的值為真,那么就執(zhí)行語句則,執(zhí)行語句。這兩個語句均可以或者是單個語句或者是括在花括號內(nèi)的語句序列。在單詞計else之后的語句仍是一個if語句,這個if語句用于控制括在花括號中的兩個語句。12練習(xí)1-11你準(zhǔn)備怎樣測試單詞計數(shù)程序?如果程序中出現(xiàn)任何錯誤,那么什么樣的輸入最有練習(xí)1- 下面編寫一個用來統(tǒng)計各個數(shù)字、空白符(空格符、制表符及換行符)以及所有其他字符出現(xiàn)次數(shù)的程序。這個程序聽起來有點矯揉造作,但有助于在一個程序中對C語言的幾個方面加以討論。由于所有輸入的字符可以分成12個范疇,因此用一個數(shù)組比用十個獨立的變量來存放各個數(shù)字的出現(xiàn)次數(shù)要方便一些。下面是這個程序的第1個版本:#includemain(){intc,i,nwhite,nother;intndigit[10];nwhite=nother=for(i=0;i<10;++i)ndigit[i]=0;while((c=getchar())!=EOF)if(c>='0'&&c<='9')++ndigit[c-elseif(c==''||c=='\n'||c=='\t'printf("digits=");for(i=0;i<10;++i)printf("%d",ndigit[i]printf(",whitespace=%d,other=%d/n",nwhite,}digits=9300000001,whitespace=123,other=int用于把ndigit說明為由10個整數(shù)組成的數(shù)組。在C語言中,數(shù)組下標(biāo)總是從0開始,故這個數(shù)組的10個元素是ndigit[0]、ndigit[1]、?、ndigit[9]。這一點在分別用于初始化和打印數(shù)組的兩個for下標(biāo)可以是任何整數(shù)表達(dá)式,包括整數(shù)變量(如i)與整數(shù)常量。if(c>='0'&&c<='9')c- 據(jù)定義,char類型的字符是小整數(shù),故char類型的變量和常量等價于算術(shù)表達(dá)式中int類型的變量和常量。這樣做既自然又方便,例如,c-'0'是一個整數(shù)表達(dá)式,對應(yīng)于在c中的字'0'至'9',其值為0至9,因此可以充當(dāng)數(shù)組ndigitif(c>='0'&&c<='9'++ndigit[c-elseif(c==''||c=='\n'||c=='\t'語句elseif語??語分,語句執(zhí)行完成后,整個if()如果其中沒有一個條件滿足,那么就執(zhí)行位于最后一個else之后的語句(如果有這個語句。如果沒有最后一個else及對應(yīng)的語句,那么這個if構(gòu)造就不執(zhí)行任何動作,如同前面的單詞計數(shù)程序一樣。在第一個if與最后一個els之間可以有0elseif條件語就風(fēng)格而言,我們建議讀者采用縮進(jìn)格式。如果每一個if都比前一個else向里縮進(jìn)一點,那么第3章將要討論的switch語句提供了編寫多路分支的另一種,它特別適合于表示數(shù)個整數(shù)或字符表達(dá)式是否與一常量集中的某個元素匹配的情況。為便于對比,在3.4節(jié)給出用switch語句編寫的這個程序的另一個版本。練習(xí)1-13編寫一個程序,打印其輸入的文件中單詞長度的直方圖。橫條的直方圖比較容易繪制,豎條直方圖則要些。練習(xí)1- C語言中的函數(shù)類似于FOTRANPascal語言中的過程或函數(shù)。函數(shù)為計算的封裝提供了一種簡便的方法,在其他地方使用函數(shù)時不需要考慮它是如何實現(xiàn)的。在使用正確設(shè)計的函數(shù)時不需要考慮它是怎么做的,只需要知道它是做什么的就夠了。C用了簡單、方便、有效的函數(shù),會經(jīng)??吹揭恍┲欢x和調(diào)用了一次的短函數(shù),這樣使用函數(shù)使某些代碼段更易于理解。到目前為此,我們所使用的函數(shù)(如printf、getchar與putchar等)都是函數(shù)庫為我們提供的。C語言沒有像FOTRAN語言那樣提供諸如**之類的乘冪運算符,我們可以通過編寫一個求乘冪的函數(shù)power(m,n)來說明定義函數(shù)的方法。power(m,n)函數(shù)用于計算整數(shù)m的正整數(shù)次冪n,即power(2,5)的值為32。這個函數(shù)不是一個實用的乘冪函數(shù),它只能用于處理比較小的整數(shù)的正整數(shù)次冪,但它對于說明問題已足夠了。(在標(biāo)準(zhǔn)庫中包含了一個用于計算xy的函數(shù)pow(x,y))下面給出函數(shù)power(m,n)#includeintpower(intm,int/*測試power*/main(){intfor(i=0;i<10;++iprintf("%d%d%d\n",i,power(2,i),power(-3,i));return0;}/*power:求底的nn>=0*/intpower(intbase,intn){inti,p=for(i=1;i<=n;++i)p=p*base;return}返回值類型函數(shù)名(可能有的參數(shù)說明{}不同函數(shù)的定義可以以任意次序出現(xiàn)在一個源文件或多個源文件中,但同一函數(shù)不能分開存放在幾個文件中。如果源程序出現(xiàn)在幾個文件中,那么對它的編譯和裝入比將整個源程序放在同一文件時要做說明,但這是操作系統(tǒng)的任務(wù),而不是語言屬性。我們暫且假定兩個函程序的知識在目前仍然有用。printf("%d%d%d\n",i,power(2,i),power(-3,每一次調(diào)用均向power函數(shù)傳送兩個變元,而power函數(shù)則在每次調(diào)用執(zhí)行完時返回一個要按一定格式打印的整數(shù)。在表達(dá)式中,power(2,i就像2和i一樣是一個整數(shù)。(并不是所有函數(shù)都產(chǎn)intpower(intbase,int說明參數(shù)的類型與名字以及該函數(shù)返回的結(jié)果的類型。power的參數(shù)名只能在power,在其他函數(shù)中不可見:在其他函數(shù)中可以使用與之相同的參數(shù)名而不會發(fā)生。對變量i與p亦如一般而言,把在函數(shù)定義中用圓括號括住的表中命名的變量叫做參數(shù),而把函數(shù)調(diào)用中與參數(shù)對應(yīng)的值叫做變元。為了表示兩者的區(qū)別,有時也用形式變元與實際變元這兩個術(shù)語。power函數(shù)計算得的值由return語句返回給main函數(shù)。return可以后跟任何表達(dá)式return表達(dá)式函數(shù)不一定都返回一個值。不含表達(dá)式的return語句用于使控制返回調(diào)用者(但不返回有用讀者可能已經(jīng)注意到,在main函數(shù)末尾有一個return語句。由于main本身也是一個函數(shù),它也就可以向其調(diào)用者返回一個值,這個調(diào)用者實際上就是程序的執(zhí)行環(huán)境。一般而言,返回值為0表示正常返回,返回值非0則異?;蝈e誤終止條件。從簡明性角度考慮,在這之前的main函數(shù)中都省去了return語句,但在以后的main函數(shù)中將包含return語句,以提醒程序要向環(huán)境返回狀態(tài)。intpower(intm,int表明power是一個有兩個int類型的變元并返回一個int類型的值的函數(shù)。這個說明叫做函數(shù)原型,要與power函數(shù)的定義和使用相一致。如果該函數(shù)的定義和使用與這一函數(shù)原型不一致,那么就是錯誤的。函數(shù)原型與函數(shù)說明中參數(shù)的名字不要求相同。更確切地說,函數(shù)原型中的參數(shù)名是可有可無的。故上面這個函數(shù)原型也可以寫成:intpower(int,但是,選擇一個合適的參數(shù)名是一種良好的文檔編寫風(fēng)格,我們在使用函數(shù)原型時仍將指明參數(shù)名。ANSIC和CC語言的最初定義中,power函數(shù)要寫成如下形式:/*power:求底的n次冪;n0/*(老方式版本)*/power(base,n)intbase,n;{inti,p=for(i=1;i<=n;++i)p=p*base;return}參數(shù)的名字在圓括號中指定,但參數(shù)類型則在左花括號之前說明,如果一個參數(shù)未在這一位置加以說明,那么缺省為int(函數(shù)的體與ANSIC)intpower(在函數(shù)說明中不包含參數(shù),這樣編譯程序就不能馬上對調(diào)用power的進(jìn)行檢查。實際上,由在新定義的函數(shù)原型的語法中,編譯程序可以很容易檢測函數(shù)調(diào)用在變元數(shù)目和類型方面的錯誤。在ANSIC中仍可以使用老方式的函數(shù)說明與定義,至少它可以作為一個過渡階段。但我們還是強(qiáng)烈建議讀者:在可以使用支持新方式的編譯程序時,最好使用新形式的函數(shù)原型。練習(xí)1- 變元—按值調(diào)使用其他語言(特別是FOTRAN語言)的程序員可能對C語言有關(guān)參數(shù)的這樣一個方面不太熟悉。在C語言中,所有函數(shù)變元都是“按值”傳遞的。這意味著,被調(diào)用函數(shù)所得到的變元值放在臨時變量中而不是放在原來的變量中。這樣它的性質(zhì)就與諸如FOTRAN等采用“按調(diào)用”的語言或諸如Pascal等采用var參數(shù)的語言有所不同,在這些語言中,被調(diào)用函數(shù)必須訪問原來的變元,而不是采用局部的方法。最主要的區(qū)別在于,在C語言中,被調(diào)用函數(shù)不能直接更改調(diào)用函數(shù)中變量的值,它只能更改其私有臨時拷貝的值。按值調(diào)用有益處而非弊端。由于在采用按值調(diào)用時在被調(diào)用函數(shù)中參數(shù)可以像通常的局部變量一樣處理,這樣可以使函數(shù)中只使用少量的外部變量,從而使程序更簡潔。例如,下面是利用這一性質(zhì)的power/*power:求底的nn>=0;第2個版本*/intpower(intbase,intn){intfor(n=1;n>0;--n)p=p*base;return}參數(shù)n被用做臨時變量,(通過一個向后執(zhí)行的for循環(huán)語句)向下計數(shù),一直到其值變成0,這樣就不再需要引入變量i。在power函數(shù)內(nèi)部對n的操作不會影響到調(diào)用函數(shù)在調(diào)用power時所使用的在必要時,也可以在對函數(shù)改寫,使之可以修改調(diào)用例程中的變量。此時調(diào)用者要向被調(diào)用函數(shù)提供所要改變值的變量的地址(從技術(shù)角度看,地址就是指向變量的指針,而被調(diào)用函數(shù)則要對的參數(shù)明指針類,通過它接變量。在第5章討論指針。對數(shù)組的情況有所不同。當(dāng)把數(shù)組名用做變元時,傳遞給函數(shù)的值是數(shù)組開始處的位置或地址—不是數(shù)組元素的副本。在被調(diào)用函數(shù)中可以通過數(shù)組下標(biāo)來或改變數(shù)組元素的值。這是下一節(jié)所要討論的問題。C語言中最常用的數(shù)組類型是字符數(shù)組。為了說明字符數(shù)組以及用于處理字符數(shù)組的函數(shù)的用法,我們來編寫一個程序,它用于讀入一組文本行并把最長的文本行打印出來。對其算法描述相當(dāng)簡單:while(還有沒有處理的行if(該行比已處理的最長的行還要長這一算法描述很清楚,很自然地把所要編寫的程序分成了若分,分別用于讀入新行、測試讀入的行、保存該行及控制這一過程。由于分得較好,可像這樣編程序。先寫一個立函數(shù)getline來入的getline函數(shù)至少在讀到文件末尾時要返回一個0于0不是有效的行長度,因此是一個可以接受的標(biāo)記文件結(jié)束的返回值。每一行至少要有一個字1。當(dāng)發(fā)現(xiàn)某一個新讀入的行比以前讀入的最長的行還要長時,就要把該新行保存起來。這意味著需要用第二個函數(shù)cop來把新行到一個安全的位置。最后,需要用主函數(shù)main來控制對getline和copy#include#defineMAXLINE /*最大輸入行的大小intgetline(charline[],intmaxline);voidcopy(charto[],charfrom[]);/*打印最長的*/main(){int /*當(dāng)前行長度intmax; /*至目前為止所發(fā)現(xiàn)的最長行的長度*/charline[MAXLINE]; /*當(dāng)前輸入的行*/char /*用于保存最長的行max=while((len=getline(line,MAXLINE))>0)if(len>max){max=copy(longest,line}if(max>0) /*有一行*/printf("%s"longestreturn0}/*getline:將一行讀入s中并返回*/intgetline(chars[],intlim){intc,for(i=0;i<lim-1&&(c=getchar())!=EOF&&c!='\n';++i)s[i]=c; (c=='\n'{s[i]=}s[i]='\0';returni;}/*copy:從from拷貝到to;假定to足夠大*/voidcopychartocharfrom{inti=while((to[i]=from[i])!=}main與getlinegetline函數(shù)中,兩個變元intgetline(chars[],int說明的,它把第一個變元s說明成數(shù)組,把第二個變元lim說明成整數(shù)。在說明中提供數(shù)組大小的目的是留出空間。在getline函數(shù)中沒有必要說明數(shù)組s該是在main函數(shù)中設(shè)置的。如同powergetline函數(shù)使用了一個return語句把值回送給其調(diào)用者。這一程序行也說明了getline函數(shù)的返回值類型為int,由于in為缺省返回值類型,故可以省略。有些函數(shù)要返回一個有用的值,而另外一些函數(shù)(如copy)則僅用于執(zhí)行一些動作,并不getline'\0'(即空字符,其值為0)放到它所建立的數(shù)組的末尾,以標(biāo)記字符串的結(jié) 的字符串常量時,它被作為字符數(shù)組,該數(shù)組中包含這個字符串中各個字符并以'\0'來標(biāo)記字hello在printf庫函數(shù)中,格式說明%s要求所對應(yīng)的變元是以這種形式表示的字符串。copy函數(shù)也基于這樣的事實,其輸入變元值以'\0'結(jié)束,并將這個字符拷貝到輸出變元中。(所有這一切都意味著空字符'\0'不是通常文本的一部分。)值得一提的是,在傳遞參數(shù)(變元)時,即使是像本例這樣很小的程序也會遇到某些麻煩的設(shè)計問題。例如,當(dāng)所遇到的行比所允許的最大值還要大時,main函數(shù)應(yīng)該怎么處理?getline函數(shù)的執(zhí)行是安全的,當(dāng)數(shù)組滿了時它就停止讀字符,即使它還沒有遇到換行符。main函數(shù)可以通過測試行長度以及所返回的最后一個字符來判定該行是否太長,然后再按其需要進(jìn)getline函數(shù)的調(diào)用程序沒有辦法預(yù)先知道一個輸入行可能有多長,故getline函數(shù)采用了檢查溢出的方法。另一方面,copy函數(shù)的調(diào)用程序則已經(jīng)知道(或可以找出)所處理字符串的長度,練習(xí)1-16對用于打印最長行的程序的主程序main進(jìn)行修改,使之可以打印任意長度的輸入練習(xí)1-1780練習(xí)1- 編寫函數(shù)reverse(s),把字符串s顛倒過來。用它編寫一個程序,一次把一個輸main函數(shù)中的變量(如line、longest等)是main函數(shù)私有的或稱局部于main函數(shù)的。由于它們是在main函數(shù)中說明的,其他函數(shù)不能直接它們。在其他函數(shù)中說明的變量也同樣如此,getline函數(shù)中說明的變量i與copy函數(shù)中說明的變量i沒有關(guān)系。函數(shù)中的每一個局部變量只在該函數(shù)被調(diào)用時存在,在該函數(shù)執(zhí)行完退出時。這也是仿照其他語言通常把這類變量稱為自動變量的原因。以后用自動變量來指局部變量。(第4章將討論靜態(tài)類,屬于靜態(tài)類局部變在數(shù)調(diào)用間持其值變)由于自動變量只在函數(shù)調(diào)用執(zhí)行期間存在,故在函數(shù)的兩次調(diào)用期間自動變量不保留次調(diào)用時所賦的值,且在函數(shù)的每次調(diào)用執(zhí)行時都要顯式給其賦初值。如果沒有給自動變量賦初值,那么其中所存放的是無用數(shù)據(jù)。作為對自動變量的替補(bǔ),可以定義適用于所有函數(shù)的外部變量,即可以被所有函數(shù)通過變(于 語言的MON變量或Pascal語言在最外層)由于外部變量可以全局,因此可以用外部變量代替變元表用于在函數(shù)間交換數(shù)據(jù)。而且,外部變量在程序執(zhí)行期間一直存在,而不是在函數(shù)調(diào)用時產(chǎn)生、在函數(shù)執(zhí)行完時,即使從為其賦值的函數(shù)返回后仍保留原來的值不變。外部變量必須在所有函數(shù)之外定義,且只能定義一次,定義的目的是為之分配單元。在每一個函數(shù)中都要對所要的外部變量進(jìn)行說明,說明所使用外部變量的類型。在說明時可以用extern語句顯式指明,也可以通過上下文隱式說明。為了更具體地討論外部變量,我們重line、longest與max說明成外部變量。這需要修改所有這三個函數(shù)的調(diào)用、說明與函數(shù)體。#include#defineMAXLINE /*最大輸入行的大小intmax;/*至目前為止所發(fā)現(xiàn)的最長行的長度*/charline[MAXLINE]; /*當(dāng)前輸入的行*/char /*用于保存最長的行intgetline(void);voidcopy(void/*打印最長的輸入行*/main(){intlen;externintmax;externcharlongest[max=while((len=getline())>0)if(len>max){max=len;copy();}if(max>0) /*有一行*/printf("%s"longestreturn0}/*getline:特別版本*/intgetline(void){intc,externcharline[for(i=0;i<MAXLINE-1&&(c=getchar())!=EOF&&c!='\n';++i)line[i]=c;if(c=='\n'{line[i]=}line[i]='\0';returni;}/*copy:特*/voidcopyvoid{intexterncharline[],longest[i=while((longest[i]=line[i])!=}在這個例子中,前幾行定義了在main、getline與copy函數(shù)中使用的幾個外部變量,指明各外部變量的類型并使系統(tǒng)為之分配單元。從語法角度看,外部變量定義就像局部變量的定義一樣,但由于它們出現(xiàn)在各個函數(shù)的外部,這些變量就成了外部變量。一個函數(shù)在使用外部變量之前必須使變量的名字在該函數(shù)中可見,法是在該函數(shù)中編寫一個extern說明,說明除了面加了一個extern外,其他地方均與普通說明相同。extern說明可以省略。如果外部變量的定義在源文件中出現(xiàn)在使用它的函數(shù)externmain、getline及copy中的幾個extern說明都是多余的。事實上,比較常用的做法是把所有外部變量的定義放在源文件的開始處,這樣就可以省略extern如果程序包含幾個源文件,某個變量在file1文件中定義、在file2與file3文件中使用,那么在file2與file3文件中就需要使用extern說明來連接該變量的出現(xiàn)。人們通常把變量的extern說明與函數(shù)放在一個單獨的文件中(歷史上叫做頭文件),在每一個源文件的前面用#include語句把所要用的頭文件包含進(jìn)來。后綴.h被約定為頭文件名的擴(kuò)展名。例如,標(biāo)準(zhǔn)庫中的函數(shù)就是在諸如<stdio.h>的頭文件中說明的。這一問題將在第4章詳細(xì)討論,而庫本身在第7章及附錄B中討由于getline與copy函數(shù)的特別版本中不帶有變元,從道理上講,在源文件開始處它們的原型應(yīng)該是getline()與copy()。但為了與較老的CC標(biāo)準(zhǔn)把空變元表作為老方式說明,并關(guān)閉所有對變元表的檢查。如果變元表本身是空的,那么要使用void,第4章將對此做進(jìn)一步討論。讀者應(yīng)該注意到,這一節(jié)我們在說到外部變量時很謹(jǐn)慎地使用著兩個詞定義與說明?!岸x”指變量建立或分配單元的位置,而“說明”則指指明變量性質(zhì)的位置,但并不分配順便,現(xiàn)在有一種把所有看得見的東西都作為外部變量的趨勢,因為這樣似乎可以簡化通信—變元表變短了,且變量在需要時總是存在。但外部變量即使在不需要時也還是存在的。過分依賴于外部變量充滿了,因為這將會使程序中的數(shù)據(jù)聯(lián)系變得很不明顯—外部變量的值可能會被意外地或不經(jīng)意地改變,程序也變得難以修改。上面打印最長行的程序的第2個版本就不如第1個版本,之所以如此,部分是由于這個原因,部分是由于它把兩個有用的函數(shù)所的變量的名字綁到函數(shù)中,使這兩個函數(shù)失去了一般性。到目前為此,我們已經(jīng)對C語言傳統(tǒng)的部分進(jìn)行了介紹。借助于這少量的構(gòu)件,我們已經(jīng)能編寫出相當(dāng)規(guī)模的程序,因此建議讀者花上較長的時間來編寫一些程序。下面給出的練習(xí)比本章前面的程序復(fù)雜一些。練習(xí)1-20編寫程序detab,將輸入中的制表符替換成適當(dāng)數(shù)目的空白符(使空白充滿到下一制表符停止位。假定制表符停止位的位置是固定的,比如在每個n列的位置上。n應(yīng)為變量或練習(xí)1-21編寫程序entab替換。使用與detab程序相同的制表停止位。請問,當(dāng)一個制表符與一個空白符都可以到達(dá)制表符停止位時,選用哪一個比較好?練習(xí)1-22編寫一個程序,用于把較長的輸入行“折”成短一些的兩行或多行,折行的位置在輸入的第n列之前的最后一個非空白符之后。要保證程序具備一定的智能,能應(yīng)付輸入行很長以及在指定的列前沒有空白符或制表符時的情況。練習(xí)1-23編寫一個用于把C程序中所有注解都刪除掉的程序。記處理好帶引號的字符練習(xí)1-24編寫一個程序,查找程序中的基本語法錯誤,如圓括號、方括號、花括號不配對等。記引號(包括單引號和雙引號(如果讀者想把該程序編寫成)C第2C

第27ANSIC語言標(biāo)準(zhǔn)對語言的基本類型與表達(dá)式做了許多小的修改與增補(bǔ)。所有整數(shù)類型現(xiàn)在都有signed(有符號)與unsigned(無符號)兩種形式,且可以表示無符號常量與十六進(jìn)制字符常量。浮點運算可以以單精度進(jìn)行,另外還可以使用更高精度的longdouble類型。字符串常量可以在編譯時連接。枚舉現(xiàn)在也成了語言的一部分,這是經(jīng)過長期努力才形成的語言特征。對象可以說明成const(常量,這種對象的值不能進(jìn)行修改。語言還對算術(shù)類型之間的自動強(qiáng)制轉(zhuǎn)換規(guī)則做了擴(kuò)充,使這一規(guī)則可以適合的數(shù)據(jù)類型。對變量與符號常量的名字存在著一些限制,這一點在第1章中沒有來。名字由字母與數(shù)字組成,但其第一個字符必須為字母。下劃線_也被看做是字母,它有時因此不要將這類名字用做變量名。大寫字母與小寫字母是有區(qū)別的,x與X是兩個不同的名字,一般把由大寫字母組成的名字用做符號常量。在內(nèi)部名字中至少前31個字符是有效的。對于函數(shù)名與外部變量名,其中所包含的字符的數(shù)目可以小于31個,這是因為它們可能會被語言無法控制的匯編程序和裝配ANSIC6個字符而言并且不區(qū)分大小寫。諸如if、else、int、float等是保留的,不能把它們用做變量名。所有關(guān)鍵詞中的字符都必須小寫。在選擇變量名時比較明智的方法是使所選名字的含義能表達(dá)變量的用途。我們傾向于局部變量使用比較短的名字(尤其是循環(huán)控制變量,亦叫循環(huán)位標(biāo),外部變量使用比較長的名字。 第28 shortintsh;longintcounter;在這種說明中,int通常反映特定機(jī)器的自然大小,一般為16位或32位,short對象一般為16位,long對象一般為32位。各個編譯程序可以根據(jù)其硬件適當(dāng)?shù)拇笮?,唯一的限制是,short與int對象至少要有類型限定符signed與unsigned可用于限定char類型或任何整數(shù)類型。經(jīng)unsigned限定符限定的數(shù)總是正的或0,并服從算術(shù)模2定律,其中nchar對象占用8位,那么unsignedchar變量的取值范圍為0~255,而signedchar變量的取值范圍則為-128~127(在采用補(bǔ)碼的機(jī)器上。普通char對象是有符號的還是無符號的則取決于具體機(jī)器,但可打印字符總是正的。float、double與longdouble類型的對象可以具有同樣大小,也可以表示兩種或三種不同的大小。在標(biāo)準(zhǔn)頭文件<limits.h>與<float.h>中包含了有關(guān)所有這些類型的符號常量以及機(jī)器與編譯程序的其他性質(zhì)。這些內(nèi)容將在附錄中討論。練習(xí)2-1編寫一個程序來確定signed及unsigned的char、short、int與long變量的取值范圍,可以通過打印標(biāo)準(zhǔn)頭文件中的相應(yīng)值來完成,也可以直接計算來做。后法較一些,諸如1234一類的整數(shù)常量是int常量。long常量要以字母l或L結(jié)尾,如 L。一個整數(shù)常量如果大到在int類型中放不下,那么也被當(dāng)做long常量處理。無符號常量以字母u或U結(jié),后綴ul或UL用于表示unsignedlong量。浮點常量中必須包含一個小數(shù)點(123.4)或指數(shù)(如1e-2)或兩者都包含,在沒有后綴時類型為double。后綴f與F用于指定float常量,而后綴l或L則用于指定longdouble常量。整數(shù)值除了用十進(jìn)制表示外,還可以用八進(jìn)制或十六進(jìn)制表示。如果一個整數(shù)常量的第一個數(shù)為0,那么這個數(shù)就是八進(jìn)制數(shù);如果第一個數(shù)字為0x或0X,那么個就是十進(jìn)數(shù)。例如,十進(jìn)制數(shù)31可以寫成八進(jìn)制數(shù)03,也可以寫成十六進(jìn)制數(shù)0x1f或0X1F。在八進(jìn)制與十六進(jìn)制常量中也可以帶有后綴l或L(long,表示長八進(jìn)制或十六進(jìn)制常量)以及后綴 u或U(unsigned,表示無符號八進(jìn)制或十六進(jìn)制常量)0XFUL是一個unsignedlong常量(無字符常量是一個整數(shù),寫成用單引號括住單個字符的的形式,如'x'。字符常量的值是該字符在機(jī)器字符集中的數(shù)值。例如,在ASCII字符集中,字符'0'的值為48,與數(shù)值0果用字符0'來代替像48一類的依賴于字符集的數(shù)值,那么程序會因獨立于特定的值而更易于閱讀。雖然字符常量一般用來與其他字符進(jìn)行比較,但字符常量也可以像整數(shù)一樣參與數(shù)值運算。\n(換行符)的換碼序列,換碼序列看起'\ooo'\xhh0?9,a?f,?F#defineVTAB'\013' /*ASCII縱向制表符*/#defineBELL'\007' /*ASCII響鈴符*/#defineVTAB'\xb' /*ASCII縱向制表符*/#defineBELL'\x7' /*ASCII響鈴符*/ 響鈴 反斜 回退 問 換頁 單引 換行 雙引 回車 八進(jìn)制 橫向制表 十六進(jìn)制 縱向制表字符常量'\0'表示其值為0的字符,即空字符。我們用'\0'來代替0,以在某些表達(dá)式中強(qiáng)調(diào)字符的性質(zhì),但其數(shù)字值就是0。常量表達(dá)式是其中只涉及到常量的表達(dá)式。這種表達(dá)式可以在編譯時計算而不必推運行時,因而可以用在常量可以出現(xiàn)的任何位置,例如:#defineMAXLINE1000charline[MAXLINE+1];#defineLEAP /*閏年int字符串常量也叫字符串字面值,是引號括住的由0個或多個字符組成的字符序列。"Iama /*空字符串可以用在字符串中,在字符串中用\" o,"" o,'0'故用于字符串的物理單元數(shù)比括在雙引號中的字符數(shù)多一個。這種表示方法意味著,C語言對字符串的長度沒有限制,但程序必須掃描完整個字符串才能決定這個字符串的長度。標(biāo)準(zhǔn)庫函數(shù)strlen(s)用于返回其字符串變元'\0'。下面是我們設(shè)計的一個版本:/*strlen:返回s的長度*/intstrlen(chars[]){inti=while(s[i]!=return}strlen等字符串函數(shù)均說明在標(biāo)準(zhǔn)頭文件<string.h>請仔細(xì)區(qū)分字符常量與只包含一個字符的字符串的區(qū)別:'x'與"x"不相同。前者是一個整數(shù),用于產(chǎn)生字母x在機(jī)器字符集中的數(shù)值(內(nèi)部表示值);后者是一個只包含一個字符(即字母x)與一個'\0'的字符數(shù)組。enumboolean{NO,YES在enum0,第二個為1,如此等等,除非指定了顯式值。如果不是所有值都指定了,那么未指定名字的值依著最后一個指定值向后遞增,如同下面兩個說明中的第二個說明:enumescapes{BELL='\a',BACKSPACE='\b',TAB='\t',NEWLINE='\n',VTAB='\v',RETURN='\r'};enummonths{JAN=1,FEB,MAR,APR,MAY,JUN,JUL,AUG,SEP,OCT,NOV,DEC};/*FEB的值為2,MAR的值為3,等等。枚舉是使常量值與名字相關(guān)聯(lián)的又便的方法,其相對于#define語句的優(yōu)勢是常量值明enum類型的變量,但編譯程序不必檢查在這個變量中的值是否為該枚舉的有效值。不過枚舉變量仍然提供了做這種檢查的機(jī)會,故其比#define更具優(yōu)勢。此外,調(diào)試程序能以符號形式打印出枚舉變量的值。 intlower,upper,step;charc,line[1000];同一類型的變量可以以任何方式分散在多個說明中,上面兩個說明也可以等價地寫成如下五個說明:intlower;intupper;intstep;charc;charline后一種形式需要占用較多的空間,但這樣不僅便于向各個說明中增添注解,也便于以后的修改。變量在說明時可以同時初始化。如果所說明的變量名后跟一個等號與一個表達(dá)式,那么這charesc='\\';inti=0;intlimit=MAXLINE+1;floateps=1.0e-5;如果所涉及的變量不是自動變量,那么只初始化一次,而且從概念上講應(yīng)該在程序開始執(zhí)行之前進(jìn)行,此時要求初始化符必須為常量表達(dá)式。顯式初始化的自動變量每當(dāng)進(jìn)入其所在的函數(shù)或分程序時就進(jìn)行一次初始化,其初始化符可以是任何表達(dá)式。外部變量與靜態(tài)變量的缺省初值為0。顯式初始化的自動變量的值為未定義值(即為。constdoublee=2.71828182845905;constcharmsg[]="warning:";intstrlen(const二元算術(shù)運算符包括+、-、*、/以及取模運算符%x%的結(jié)果是x除以y的余數(shù),當(dāng)y能整除x時,x%y的結(jié)果為04整除但不能被100整除,那么這一年就是閏年,此外,能被400整除的年份也是閏年。因此,有if((year%4==0&&year%100!=0)||year%400==0)printf("%disaleapyear\n",year);printf("%disnotaleapyear\n",year第PAGE32第PAGE32二元+和-運算符的優(yōu)先級相同,但它們的優(yōu)先級比*、/和% i<lim-i<(lim-更有趣的是邏輯運算符&&與||。由&&與||連接的表達(dá)式從左至右計算,并且一旦知道結(jié)果的真假值就立即停止計算。絕大多數(shù)C程序利用了這些性質(zhì)。例如,下面這個循環(huán)語句取自第1for(i=0;i<lim-1&&(c=getchar())!='\n'&&c!=EOF;++i)s[i]=c;s中是否還有空間存放這個字符,因此首先必須測試是否ilim-1。而且,如果這一測試失?。磇lim-1不成立),那么就沒有必要繼續(xù)讀下一字符。類似地,如果在調(diào)用getchar函數(shù)之前就對c是否為EOF進(jìn)試,那么也是令人遺憾的,因此,函數(shù)調(diào)用與賦值都必須在對c中的字符進(jìn)試之前完成&&運算符的優(yōu)先級比||運算符的優(yōu)先級高,但兩者都比關(guān)系運算符和等于運算符的優(yōu)先級低。從而,像i<lim-1&&(c=getchar())!='\n'&&c!=之類的表達(dá)式就不需要另外加圓括號了。但是,由于!=運算符的優(yōu)先級高于賦值運算符=的優(yōu)先(c=getchar())!=中,圓括號還是需要的,這樣才能達(dá)到我們所希望的先把函數(shù)值賦給c'\n'進(jìn)行比較的效果。按照定義,如果關(guān)系表達(dá)式與邏輯表達(dá)式的計算結(jié)果為真,那么它們的值為1那么它們的值為0一元求反運算符!用于將非0運算分量轉(zhuǎn)換成0,把0運算分量轉(zhuǎn)換成1如if(!validif(valid==0!valid一類的構(gòu)造讀起來好聽一點(“如果不是有效練習(xí)2- 不使用&&或||運算符編寫一個與上面的for循環(huán)語句等價的循環(huán)語句當(dāng)一個運算符的幾個運算分量的類型不相同時,要根據(jù)一些規(guī)則把它們轉(zhuǎn)換成某個共同的類型。一般而言,只能把“比較窄的”運算分量自動轉(zhuǎn)換成“比較寬的”運算分量,這樣才能不丟失信息,例如,在諸如f+一類的表達(dá)式的計算中要把整數(shù)變量i的值自動轉(zhuǎn)換成浮點類型。不允許使用沒有意義的表達(dá)式,例如,不允許把float表達(dá)式用作下標(biāo)。可能丟失信息的表達(dá)式可能會招來警告信息,如把較長整數(shù)類型的值賦給較短整數(shù)類型的變量,把浮點類型賦給整數(shù)類型,等等,但不是表達(dá)式。由于char類型就是小整數(shù)類型,在算術(shù)表達(dá)式中可以自由地使用char類型的變量或常量。這就使得在某些字符轉(zhuǎn)換中有了很大的靈活性。一個例子是用于將數(shù)字字符串轉(zhuǎn)換成對應(yīng)的數(shù)值的函數(shù)atoi:/*atoi:將字符串s轉(zhuǎn)換成整數(shù)*/intatoi(chars[]){inti,n=for(i=0;s[i]>='0'&&s[i]<='9';++i)n=10*n+(s[i]-'0');return}s[i]-用于求s[i]中的字符所對應(yīng)的數(shù)字值,因為'0'、'1'、'2'等的值形成了續(xù)的遞增序列。將字符轉(zhuǎn)換成整數(shù)的另一個例子是函數(shù)lower,它把ASCIIlower/*lower:把字符c轉(zhuǎn)換成小寫字母;僅對ASCII字符*/intlower(intc){if(c>='A'&&c<='Z'returnc+'a'-return}這個函數(shù)是為ASCII字符集設(shè)計的。在ASCII字符集中,大寫字母與對應(yīng)的小寫字母像數(shù)字值一樣有固定的距離,并且每一個字母都是連續(xù)的—在A至Z之間只有字母。然而,后一個結(jié)論對于EBCDI字符集不成立,故這一函數(shù)在EBCDIC字符集不只是轉(zhuǎn)換了字母。附錄B中介紹的標(biāo)準(zhǔn)頭文件<ctype.h>定義了一組用于進(jìn)行獨立于字符集的測試和轉(zhuǎn)換的函數(shù)。例如,tolower(c)函數(shù)用于在c為大寫字母時將之轉(zhuǎn)換成小寫字母,故tolower是上述lower函c>='0'&&c<=我們從現(xiàn)在起要使用<ctype.h>C語言沒有指定char類型變量是無符號量還是有符號量。當(dāng)把一個char類型的值轉(zhuǎn)換成int類型的值時,其結(jié)果是不是為負(fù)整數(shù)?結(jié)果視機(jī)器的不同而有所變化,反映了不同機(jī)器結(jié)構(gòu)之間的區(qū)別。在某些機(jī)器上,如果字符的最左一位為1么就被轉(zhuǎn)換成負(fù)整數(shù)(。在另一些機(jī)器上,采取的是提升的方法,通過在最左邊加上0把字符提升為整數(shù),這樣轉(zhuǎn)換的結(jié)果總是正的。C語言的定義保證了機(jī)器的標(biāo)準(zhǔn)可打印字符集中的字符不會是負(fù)的,故在表達(dá)式中這些字符總是正的。但是,字符變量的位模式在某些機(jī)器上可能是負(fù)的,而在另一些機(jī)器上卻是正的。為了保證程序的可移植性,如果要在char變量中非字符數(shù)據(jù),那么最好指定signed或unsigned關(guān)系表達(dá)式(如i>j)和由&&與||連接的邏輯表達(dá)式的值在其結(jié)果為真時為1,在其結(jié)果為d=c>='0'&&c<=在c的值為數(shù)字時將d置為1,否則將d置為0。然而,諸如isdigit一類的函數(shù)在變元為真時返回的可能是任意非0值。在if、while、for0看,它們沒有什么區(qū)別。+或*等二元運算符的兩個運算分量具有不同的類型,那么在進(jìn)行運算之前先要把“低”的類型提升為“高”的類型。附錄A.6節(jié)嚴(yán)格地給出了轉(zhuǎn)換規(guī)則。然而如果沒有無符號類型的運算分量,那么只要使用如下一組非正式的規(guī)則就夠了:如果某個運算分量的類型為longdouble,那么將另一個運算分量也轉(zhuǎn)換成longdouble類型;否則,將char與short類型的運算分量轉(zhuǎn)換成int然后,如果某個運算分量的類型為long,那么將另一個運算分量也轉(zhuǎn)換成long第PAGE第PAGE35注意,在表達(dá)式中float類型的運算分量不自動轉(zhuǎn)換成double類型,這與原來的定義不同。一般而言,數(shù)學(xué)函數(shù)(如定義在標(biāo)準(zhǔn)頭文件<math.h>中的函數(shù))要使精。用float類的主要原因是為了在使用較大的數(shù)組時節(jié)省單元,有時也為了節(jié)省機(jī)器執(zhí)行時間(雙精度算術(shù)運算特別費時。當(dāng)表達(dá)式中包含unsigned類型的運算分量時,轉(zhuǎn)換規(guī)則要復(fù)雜一些。主要問題是,在有符號值與無符號值之間的比較運算取決于機(jī)器,因為它們?nèi)Q于各個整數(shù)類型的大小。例如,假定int對象占16位,long對象占32位,那么,-1L<1U,這是因為int類型的-1U被提升為signedlong類型;但-1L>1UL,這是因為-1L被提升為unsignedlong類型,因此它是一個比較大的正數(shù)。=右邊的值要轉(zhuǎn)換成左邊變量的類型,后者即賦值表達(dá)式inti;chari=c;c=c的值保持不變,無論是否要進(jìn)行符號擴(kuò)展。然而,如果把兩個賦值語句的次序顛倒一如果x是float類型且i是intx=與i=這兩個賦值表達(dá)式在執(zhí)行時都要引起類型轉(zhuǎn)換,當(dāng)把float類型轉(zhuǎn)換成int類型時要把小數(shù)部分截取掉;由于函數(shù)調(diào)用的變元是表達(dá)式,當(dāng)把變元傳遞給函數(shù)時也可能引起類型轉(zhuǎn)換。在沒有函數(shù)char與short類型轉(zhuǎn)換成infloat類型轉(zhuǎn)換成double類型,這就是即使在函數(shù)是用char與floatin與double類型的原因。最后,在任何表達(dá)式中都可以進(jìn)行顯式類型轉(zhuǎn)換(即所謂的“強(qiáng)制轉(zhuǎn)換”),這時要使用一個叫做強(qiáng)制轉(zhuǎn)換的一元運算符。在如下構(gòu)造中,表達(dá)式被按上述轉(zhuǎn)換規(guī)則轉(zhuǎn)換成由類型名所指名(類型名)強(qiáng)制轉(zhuǎn)換的精確含義是,表達(dá)式首先被賦給類型名指定類型的某個變量,然后再將其用在整個sqrt需要一個double類型的變元,但如果在其他地方作了不適當(dāng)?shù)奶幚?,那么就會產(chǎn)生無意義的結(jié)果(sqrt是在<math.h>中說明的一個函數(shù)。因而,如果n是整數(shù),那么可以用sqrt((double)第PAGE36第PAGE36使得在把n傳遞給sqrt函數(shù)之前先把n的值轉(zhuǎn)換成double類型。注意,強(qiáng)制轉(zhuǎn)換只是以指明的類型產(chǎn)生nn本身的值沒有改變。強(qiáng)制轉(zhuǎn)換運算符與其他一元運算符具有相同的優(yōu)先級,如同本章末尾的表中所總結(jié)的那樣。如果變元是通過函數(shù)原型說明的,那么在通常情況下,當(dāng)該函數(shù)被調(diào)用時,系統(tǒng)對變元自動進(jìn)行強(qiáng)制轉(zhuǎn)換。于是,對于sqrtdoubleroot=不需要強(qiáng)制轉(zhuǎn)換運算符就自動將把整數(shù)2強(qiáng)制轉(zhuǎn)換成double類型的值2.0在標(biāo)準(zhǔn)庫中包含了一個用于實現(xiàn)偽隨機(jī)數(shù)發(fā)生器的函數(shù)rand與一個用于初始化的函unsignedlongintnext=/*rand:返回取值在0~32767之間的偽隨機(jī)數(shù)*/intrand(void){next=next return(unsignedint)(next/65536)%}/*srand:為rand()函數(shù)設(shè)置*/voidsrand(unsignedintseed){next=}練習(xí)2-3 編寫函數(shù)htoi(s),把由十六進(jìn)制數(shù)字組成的字符串(前面可能包含0x或0X)轉(zhuǎn)換成等價的整數(shù)值。字符串中允許的數(shù)字為:0~9,a~f、以及A~F。C語言為變量增加與減少提供了兩個奇特的運算符。加一運算符++1減一運算符--用于使其運算分量減1。我們常常用++運算符來使變量的值加1,如在下述語句中一樣:if(c==++n,也可以用作后綴運算符(用在變量后面,如n++。在這兩種情況下,效果都是使n加1。但是,在n的值被使用之前先使n加1則是在n的值被使用之后再使n加1++n和n++的效果是不x=將x的值置為5x=則將xn的值都是6。加一與減一運算符只能作用于變量,(i+一類的表達(dá)式是的if(c=='\n')中,++作為前綴與后綴效果是一樣的。在有些情況下需要特別指定。例如,考慮下面的函數(shù)/*squeeze:從s中刪除掉cvoidsqueeze(chars[],int{inti,for(i=j=0;s[i]!='\0';i++)if(s[i]!=c)s[j++]=s[j]=}每當(dāng)出現(xiàn)一個不等于c的字符時,就把它拷貝到j(luò)的當(dāng)前值所指向的位置,并將j的值加1,以準(zhǔn)備if(s[i]!=c)s[j]=s[i];}具有類似構(gòu)造的另一個例子是第1章的getline函數(shù)。我們可以將這個函數(shù)中的ifif(c=={s[i]=}用更為精致的ifif(c==s[i++]=作為第三個例子,再看一下標(biāo)準(zhǔn)函數(shù)strcat(s,t)t連接到字符串s的后面。strcat函數(shù)假定在s中有足夠的空間來保存這兩個字符串連接的結(jié)果。下面所編寫的這個函數(shù)沒有返回任何值(在標(biāo)準(zhǔn)庫中,這個函數(shù)要返回一個指向新字符串的指針:/*strcat:把字符串t連接到字符串s的后面;s必須有足夠大的空間*/voidstrcat(chars[],chart[]){inti,i=j=while(s[i]!='\0') /*找到s的末尾*/whiles[it[j /*拷貝t;}在將t中的字符逐個拷貝到s后面時,用后綴運算符++作用于i與j,以保證在循環(huán)過程中i與j均指練習(xí)2-4重寫squeeze(s1,s2)函數(shù),把字符串s1中與字符串s2練習(xí)2-5編寫函數(shù)any(s1,s2),它把字符串s2中任一字符在字符串s1中的第一次出現(xiàn)的位置作為結(jié)果返回。如果s1中沒有包含s2中的字符,那么返回-1(標(biāo)準(zhǔn)庫函數(shù)strpbrk具有同樣的功)C語言提供了六個用于位操作的運算符,這些運算符只能作用于整數(shù)分量,即只能作用于有符號或無符號的char、short、int與long 按位與 按位或 按位異或 左 右求反碼(一元運算符按位與運算符&經(jīng)常用于某些位,例如n=n&用于將n除7個低位外的各位置成0|x=x|用于將x中與SET_ON中為1的位對應(yīng)的那些位也置為1按位異或運算符^用于在其兩個運算分量的對應(yīng)位不相同時置該位為1,否則,置該位為0。須將按位運算符&和|同邏輯運算符&&和||區(qū)分開來,后者用于從左至右求表達(dá)式第PAGE第PAGE39的真值。例如,如果x的值為1,y的值為2,那么,x&y的結(jié)果是0,而x&&y的值則為1移位運算符<<與>>分別用于將左運算分量左移與右移由右運算分量所指定的位數(shù)(右運算分量的值必須是正的。于是,表達(dá)式x

溫馨提示

  • 1. 本站所有資源如無特殊說明,都需要本地電腦安裝OFFICE2007和PDF閱讀器。圖紙軟件為CAD,CAXA,PROE,UG,SolidWorks等.壓縮文件請下載最新的WinRAR軟件解壓。
  • 2. 本站的文檔不包含任何第三方提供的附件圖紙等,如果需要附件,請聯(lián)系上傳者。文件的所有權(quán)益歸上傳用戶所有。
  • 3. 本站RAR壓縮包中若帶圖紙,網(wǎng)頁內(nèi)容里面會有圖紙預(yù)覽,若沒有圖紙預(yù)覽就沒有圖紙。
  • 4. 未經(jīng)權(quán)益所有人同意不得將文件中的內(nèi)容挪作商業(yè)或盈利用途。
  • 5. 人人文庫網(wǎng)僅提供信息存儲空間,僅對用戶上傳內(nèi)容的表現(xiàn)方式做保護(hù)處理,對用戶上傳分享的文檔內(nèi)容本身不做任何修改或編輯,并不能對任何下載內(nèi)容負(fù)責(zé)。
  • 6. 下載文件中如有侵權(quán)或不適當(dāng)內(nèi)容,請與我們聯(lián)系,我們立即糾正。
  • 7. 本站不保證下載資源的準(zhǔn)確性、安全性和完整性, 同時也不承擔(dān)用戶因使用這些下載資源對自己和他人造成任何形式的傷害或損失。

評論

0/150

提交評論