軟件工程課件-編碼_第1頁
軟件工程課件-編碼_第2頁
軟件工程課件-編碼_第3頁
軟件工程課件-編碼_第4頁
軟件工程課件-編碼_第5頁
已閱讀5頁,還剩122頁未讀, 繼續(xù)免費閱讀

下載本文檔

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

文檔簡介

1SoftwareEngineering

第7章編碼2

“編程即是一門科學(xué),也是一門藝術(shù)”這是一項很有藝術(shù)性、獨創(chuàng)性、技巧性的實踐工作.

編程技巧像藝術(shù)技巧一樣,

深不可測、奧妙無窮;

程序員像藝術(shù)家一樣,有發(fā)揮創(chuàng)造性的無限空間。

3主要內(nèi)容:

選擇程序設(shè)計語言

編碼風(fēng)格

結(jié)構(gòu)化程序設(shè)計目的:產(chǎn)生可執(zhí)行的代碼,

真正實現(xiàn)軟件的需求47.1

選擇程序設(shè)計語言(1)應(yīng)用領(lǐng)域(2)項目的特點、類型和范圍(3)根據(jù)分析設(shè)計方法選擇語言(4)用戶的要求(5)編碼人員對語言的了解情況

(語言特點、類型、運行環(huán)境、發(fā)展前景)5發(fā)展前景:

定義嚴(yán)密性書寫規(guī)范,表達正確穩(wěn)定、可靠、安全,異常處理效率高可維護能力可移植性

通用性交互性互用性可測試性可重用性易學(xué)習(xí)

6

編程規(guī)范的格式是寫程序入門的基礎(chǔ).

總的說程序設(shè)計風(fēng)格應(yīng)從追求“聰明和技巧”轉(zhuǎn)為提倡“簡明和直接”在”TheElementsofProgrammingStyle”一書中說明程序正確性、清晰性和效率之間關(guān)系可概括為:

先求正確后求快;

先求清楚后求快;

求快不忘保持程序正確;

保持程序簡單以求快;

書寫清晰,不要為效率犧牲清晰

7.2編碼風(fēng)格7

1962年7月美國飛往金星的火箭控制系統(tǒng)中的指令

DO5i=1,3

誤寫為

DO5i=1.3

導(dǎo)致火箭偏離軌道,被炸毀.

編碼風(fēng)格:源程序文檔化數(shù)據(jù)說明語句結(jié)構(gòu)輸入/輸出

8

標(biāo)識符的命名程序的注釋風(fēng)格7.2.1源程序文檔化9(1)符號名的命名符號名即標(biāo)識符,包括模塊名、變量名、常量名、標(biāo)號名、子程序名、數(shù)據(jù)區(qū)名以及緩沖區(qū)名等。符號名應(yīng)能反映它所代表的實體,意義明確.

如,表示次數(shù)的量用Times,表示總量的用Total,表示平均值的用Average,表示和的量用Sum等。

風(fēng)格10名字應(yīng)當(dāng)非形式的、精煉的、易記的.

必要時可使用縮寫名字,但縮寫規(guī)則要一致,并且要給每一個名字加注釋。在一個程序中,一個變量只應(yīng)用于一種用途。說明性的名字,應(yīng)當(dāng)足夠長.局部變量用短名字注意現(xiàn)實中的約定或本地習(xí)慣如:

指針常用以p結(jié)尾的變量名;

全局變量用大寫開頭的變量名;

常量完全用大寫字母拼寫的變量名;

11

函數(shù)采用動作性名字

now=date.getTime();

putchar(‘\n’);

對返回布爾類型值的函數(shù)命名,應(yīng)反映返回值情況.if(checkoctal(c))…

if(isoctal(c))…如果參數(shù)是八進制返回真,否則假..12(2)程序的注釋

注釋是不可缺少的,是程序員與程序讀者之間通信的重要手段.

一些正規(guī)的程序文本中,注釋行的數(shù)量占到整個源程序的1/3到1/2,甚至更多。注釋分為序言性注釋和功能性注釋。風(fēng)格13序言性注釋

通常置于每個程序模塊的開頭部分,給出程序的整體說明,對于理解程序本身具有引導(dǎo)作用。包括:

程序標(biāo)題;有關(guān)本模塊功能和目的的說明;

主要算法;14

接口說明:包括調(diào)用形式,參數(shù)描述,子程序清單;

有關(guān)數(shù)據(jù)描述:重要的變量及其用途,約束或限制條件,以及其它有關(guān)信息;

給函數(shù)和全局數(shù)據(jù)加注釋

模塊位置:在哪一個源文件中,或隸屬于哪一個軟件包;

開發(fā)簡歷:模塊設(shè)計者,復(fù)審者,復(fù)審日期,修改日期及有關(guān)說明等。15功能性注釋功能性注釋嵌在源程序體中,用以描述其語句或程序段做什么,而不是怎么做。不要大談明顯的東西.澄清情況,不要添亂.

例如,

/*ADDAMOUNTTOTOTAL*/

TOTAL=AMOUNT+TOTAL

不好。

16

如果把月銷售額計入年度總額,下面的寫法易理解:/*ADDMONTHLY-SALESTOANNUAL-TOTAL*/

TOTAL=AMOUNT+TOTAL要點描述一段程序,而不是每一個語句;用縮進和空行,使程序與注釋容易區(qū)別;注釋要正確,不要與代碼矛盾。17例

time(&now);

strcpy(date,ctime(&now));

/*getridoftrailingnewlinecharacter*//*copiedfromctime*/i=0;

while(date[i]>=‘’)i++;

date[i]=0;

該注釋與代碼有矛盾,注釋說的是換行,而

代碼則說的是空格

18重寫代碼:

time(&now);

strcpy(date,ctime(&now));

/*getridoftrailingnewlinecharacter*//*copiedfromctime*/

for(i=0;date[i]!=‘\n’;i++)

date[i]=‘\0’

現(xiàn)在注釋和代碼一致了.寫得更直截些.要解決

問題是刪除ctime返回時放在字符串最后的換行字符.注釋和代碼都應(yīng)做這件事.19

time(&now);

strcpy(date,ctime(&now));

/*ctime()putsnewlineatendofstring;*//*deleteit*/date[strlen(date)-1]=‘\0’;

這是在c語言里截去字符串最后字符的習(xí)慣寫法.代碼變短了,注釋也支持它.207.2.2數(shù)據(jù)說明在設(shè)計階段已經(jīng)確定了數(shù)據(jù)結(jié)構(gòu)的組織及其復(fù)雜性。在編寫程序時,則需要注意數(shù)據(jù)說明的風(fēng)格。為了使程序中數(shù)據(jù)說明更易于理解和維護,必須注意以下幾點。

(1)數(shù)據(jù)說明的次序應(yīng)當(dāng)規(guī)范化

(2)說明語句中變量安排有序化

(3)使用注釋說明復(fù)雜數(shù)據(jù)結(jié)構(gòu)風(fēng)格21(1)數(shù)據(jù)說明的次序應(yīng)當(dāng)規(guī)范化:

①常量說明

②簡單變量類型說明

③數(shù)組說明

④公用數(shù)據(jù)塊說明

⑤所有的文件說明(2)在類型說明中,可按如下順序排列:

①整型量說明

②實型量說明

③字符量說明

④邏輯量說明22(3)說明語句中變量安排有序化:當(dāng)多個變量名出現(xiàn)在一個說明語句中時,應(yīng)當(dāng)將這些變量按字母的順序排列。如,把

integersize,length,width,

cost,price

寫成

integer

cost,length,price,

size,width

23(4)使用注釋說明復(fù)雜數(shù)據(jù)結(jié)構(gòu):如果數(shù)據(jù)結(jié)構(gòu)復(fù)雜,應(yīng)當(dāng)使用注釋來說明在程序?qū)崿F(xiàn)時這個數(shù)據(jù)結(jié)構(gòu)的固有特點。如,鏈表結(jié)構(gòu)及用戶自定義的數(shù)據(jù)類型,應(yīng)當(dāng)在注釋中做必要的補充說明。24(5)把數(shù)定義為常數(shù),不要定義為宏

C程序員的傳統(tǒng)方式用#define行來對付神密的數(shù)值.

C語言預(yù)處理程序是一個強有力的工具,但它又有些魯莽.

使用宏進行編程是一種很危險的方式,因為宏會在背地里改變程序的詞法結(jié)構(gòu).

C++中任何類型都可以使用const聲明常數(shù)

constintMAXROW=24,MAXCOL=80C語言也有const值,但它不能用作數(shù)組的界,

這樣enum就是C中惟一可用的選擇了.25

7.2.3表達式和語句

(1)視覺組織(空格、空行和移行)

恰當(dāng)?shù)乩每崭?,可以突出運算的優(yōu)先性,避免發(fā)生運算的錯誤。

例如,將表達式

(A<-17)ANDNOT(B<=49)ORC

寫成

(A<-17)ANDNOT(B<=49)ORC

自然的程序段之間可用空行隔開;

風(fēng)格26

IF(…)

THEN

IF(…)

THEN

……

ELSE

……

ENDIF

……

ELSE

……

ENDIF

移行(向右縮格),使程序邏輯結(jié)構(gòu)具有層次關(guān)系。如:27

如,一段排序程序:FORI:=1TON-1DOBEGINT:=I;FORJ:=I+1TONDOIFA[J]<A[T]THENT:=J;IFT≠ITHENBEGINWORK:=A[T];A[T]:=A[I];A[I]:=WORK;ENDEND;由于一行中包括了多個語句,掩蓋了程序的循環(huán)結(jié)構(gòu)和條件結(jié)構(gòu),使其可讀性變得很差。(2)在一行內(nèi)只寫一條語句

風(fēng)格28FORI:=1TON-1DO

//改進布局

BEGIN

T:=I;

FORJ:=I+1TONDO

IFA[J]<A[T]THENT:=J;

IFT≠ITHEN

BEGIN

WORK:=A[T];

A[T]:=A[I];

A[I]:=WORK;

END

END;29(3)分解復(fù)雜的表達式如:*x+=(*xp=(2*k<n-m)?C[k+1]:d[k--1]));

分解為:

if(2*k<n-m)*xp=C[k+1];else*xp=d[k--1];*x+=*xp30(4)程序編寫首先應(yīng)當(dāng)考慮清晰性

如,有一個程序段:

A[I]=A[I]+A[T];

A[T]=A[I]-A[T];

A[I]=A[I]-A[T];

此段程序不易看懂,有時還需用實際數(shù)據(jù)試驗一下。31

實際上,這段程序的功能是交換

A[I]和A[T]中的內(nèi)容。目的是為了節(jié)省一個工作單元。

若改為:

WORK:=A[T];

A[T]:=A[I];

A[I]:=WORK;

就一目了然了。

32

恰當(dāng)?shù)氖褂门R時變量,否則會使可讀性下降。如,為了追求效率,把表達式

A[I]+1/A[I];

寫成:AI=A[I];X=AI+1/AI;

這樣,將一句分成兩句寫,會產(chǎn)生意想不到的問題。33(5)程序要直接說明程序員的用意

DO5I=1,N

DO5J=1,N

5V(I,J)=(I/J)*(J/I)

除法運算(/)在除數(shù)和被除數(shù)都是整型量時,其結(jié)果只取整數(shù)部分,而得到整型量。34

當(dāng)I<J時,I/J=0

當(dāng)J<I時,J/I=0

得到的數(shù)組

當(dāng)I≠J時

V(I,J)=(I/J)*(J/I)=0

當(dāng)I=J時

V(I,J)=(I/J)*(J/I)=1

這樣得到的結(jié)果V是一個單位矩陣。1

1..1001.01.0..1.00.00.035寫成以下的形式,就能讓讀者直接了解程序編寫者的意圖。

DO5I=1,N

DO5J=1,N

IF(I.EQ.J)THEN

V(I,J)=1.0

ELSE

V(I,J)=0.0

ENDIF

5CONTINUE36(6)讓編譯程序做簡單的優(yōu)化。(7)盡可能使用庫函數(shù)(8)避免不必要的轉(zhuǎn)移。慎用

GOTO語句。

如,求三個數(shù)中最小值的程序片段:

37

IF(X<Y)GOTO30

IF(Y<Z)GOTO50

SMALL=ZGOTO7030IF(X<

Z)GOTO60

SMALL=Z

GOTO7050SMALL=Y(jié)GOTO7060SMALL=X70CONTINUE輸入x、y、zX<yX<zy<zSMALL=xSMALL=ySMALL=z輸出SMALL<≥<6030≥≥<50結(jié)束開始38

程序只需編寫成:

SMALL=X

IF(Y.LT.SMALL)SMALL=Y(jié)

IF(Z.LT.SMALL)SMALL=Z

程序應(yīng)當(dāng)簡單,不必過于深奧,避免使用GOTO語句繞來繞去。(9)應(yīng)當(dāng)使用IF-THEN-ELSE來實現(xiàn)選擇結(jié)構(gòu);使用DO-UNTIL或DO-WHILE

來實現(xiàn)循環(huán)結(jié)構(gòu)。39

(10)避免使用空的ELSE語句和IF…

THENIF…的語句。這種結(jié)構(gòu)容易使讀者產(chǎn)生誤解。例如

IF(CHAR>='A’)THENIF(CHAR<=‘Z’)THEN

PRINT“Thisisaletter。”

ELSE

PRINT“Thisisnotaletter。”

可能產(chǎn)生二義性問題。

40(11)避免采用過于復(fù)雜的條件測試。(12)盡量減少使用“否定”條件的條件語句。例如,如果在程序中出現(xiàn)

IFNOT((CHAR<‘0’)OR(CHAR>‘9’))THEN……

改成

IF(CHAR>='0’)AND(CHAR<='9’)THEN……

不要讓讀者繞彎子想。41(13)為了一致性,使用習(xí)慣用法如:處理一數(shù)組中各個元素的代碼,對些這元素初始化.i=0;while(i<=n-1)

array[i++]=1.0;

或?qū)懗?for(i=0;i<n;)

array[i++]=1.0;

習(xí)慣寫法:for(i=0;i<n;i++)

array[i]=1.0;

也可能寫成:for(i=n;--i>=0;)

array[i]=1.0;42(14)可用通俗易懂的偽碼來描述程序的流

程,然后再翻譯成必須使用的語言。(15)數(shù)據(jù)結(jié)構(gòu)要有利于程序的簡化。(16)模塊化,使模塊功能單一化,模塊間的耦合能夠清晰可見。(17)利用信息隱蔽,確保每一個模塊的獨立性。

對于無窮循環(huán),喜歡用

for(;;)

但while(1)也很流行,不要使用其他形式.43(18)從數(shù)據(jù)出發(fā)去構(gòu)造程序。(19)不要修補不好的程序,要重新編寫。也不要一味地追求代碼的復(fù)用,要重新組織。(20)對太大的程序,要分塊編寫、測試,然后再集成。(21)對遞歸定義的數(shù)據(jù)結(jié)構(gòu)盡量使用遞歸過程。447.2.4輸入和輸出

不論是批處理的輸入/輸出方式,還是交互式的輸入/輸出方式,在設(shè)計和程序編碼時都應(yīng)考慮下列原則:

(1)對所有的輸入數(shù)據(jù)都要進行檢驗,識別錯誤的輸入,以保證每個數(shù)據(jù)的有效性;

(2)檢查輸入項的各種重要組合的合理

性,必要時報告輸入狀態(tài)信息;45

(3)輸入數(shù)據(jù)時,輸入格式要簡單

(4)應(yīng)允許缺省值;

(5)輸入一批數(shù)據(jù)時,最好使用輸入結(jié)束標(biāo)志,而不要由用戶指定輸入數(shù)據(jù)數(shù)目;

(6)在交互式輸入輸出時,要在屏幕上使用提示符明確提示交互輸入的請求,指明可使用選擇項的種類和取值范圍。

(7)給輸出加注解,設(shè)計輸出報表格式。

(8)輸入/輸出風(fēng)格還受到其它因素的影響。如輸入/輸出設(shè)備,圖形設(shè)備等.467.2.5程序效率程序的效率是指程序的執(zhí)行速度及程序所需占用的內(nèi)存的存儲空間。

提高效率的準(zhǔn)則:

軟件效率以需求為準(zhǔn).

好的設(shè)計可以提高效率

程序的效率與程序的簡單性相關(guān)。

選擇有利于提高效率的算法47

在編程序前,盡可能化簡有關(guān)的算術(shù)表達式和邏輯表達式;

仔細檢查算法中的嵌套的循環(huán),盡可能將某些語句或表達式移到循環(huán)外面;

盡量避免使用多維數(shù)組;

④盡量避免使用指針和復(fù)雜的表;

⑤采用“快速”的算術(shù)運算;

(1)算法對效率的影響48

不要混淆數(shù)據(jù)類型,避免在表達式中出現(xiàn)類型混雜;

盡量采用整數(shù)算術(shù)表達式和布爾表達式;

選用等效的高效率算法;許多編譯程序具有“優(yōu)化”功能,可以自動生成高效率的目標(biāo)代碼。49(2)影響存儲器效率的因素在大中型計算機系統(tǒng)中,存儲限制不再是主要問題。在這種環(huán)境下,對內(nèi)存采取基于操作系統(tǒng)的分頁功能的虛擬存儲管理。存儲效率與操作系統(tǒng)的分頁功能直接有關(guān)。50采用結(jié)構(gòu)化程序設(shè)計,將程序功能合理分塊,使每個模塊或一組密切相關(guān)模塊的程序體積大小與每頁的容量相匹配,可減少頁面調(diào)度,減少內(nèi)外存交換,提高存儲效率。

存儲器效率51在微型計算機系統(tǒng)中,存儲器的容量對軟件設(shè)計和編碼的制約很大。因此要選擇可生成較短目標(biāo)代碼且存儲壓縮性能優(yōu)良的編譯程序,有時需采用匯編程序。提高存儲器效率的關(guān)鍵是:

程序的簡單性。存儲器效率52(3)影響輸入/輸出的因素

輸入/輸出可分為兩種類型:面向人(操作員)的輸入/輸出如果輸入數(shù)據(jù)方便、簡單,輸出信息直觀,則可以說面向人的輸入/輸出,是高效的。

面向設(shè)備的輸入/輸出53

提高面向設(shè)備的輸入/輸出效率的指導(dǎo)原則:

輸入/輸出的請求應(yīng)當(dāng)最小化;對于所有的輸入/輸出操作,安排適當(dāng)?shù)木彌_區(qū),以減少頻繁的信息交換。對輔助存儲(例如磁盤),選擇盡可能簡單的,可接受的存取方法;對輔助存儲的輸入/輸出,應(yīng)當(dāng)成塊傳送;對終端或打印機的輸入/輸出,應(yīng)考慮設(shè)備特性,盡可能改善輸入/輸出的質(zhì)量和速度;

547.3結(jié)構(gòu)化程序設(shè)計

7.3.1

定義:

由若干個基本控制結(jié)構(gòu)(順序、分支、循環(huán))嵌套連接成具有復(fù)雜的層次結(jié)構(gòu).而且每種控制結(jié)構(gòu)都嚴(yán)格遵循

“一個入口,一個出口”的原則,

并完成單一的操作.

557.3.2結(jié)構(gòu)化設(shè)計的風(fēng)格

清楚支持自頂向下支持模塊化存在問題:

效率不高重用性差567.3.3結(jié)構(gòu)化程序設(shè)計的主要原則

使用語言中的基本控制結(jié)構(gòu)(順序、

選擇、重復(fù))表示程序的邏輯。復(fù)雜結(jié)構(gòu)應(yīng)該用基本控制結(jié)構(gòu)進行

組合嵌套來實現(xiàn)。程序語句組成容易識別的塊,每塊只有一個入口和一個出口。

嚴(yán)格控制GOTO語句57例1打印A,B,C三數(shù)中最小者的程序開始

輸入A、B、CA<BB<CA<C輸出B輸出C輸出ATFFFTT結(jié)束

程序1

片段

if(A<

B)

goto120;

if(

B<

C)goto110;

100write(C);

goto140;

110write(B);

goto

140;

120if(A

<

C)goto130;

goto100;

130write(A);

140end

58

程序2片段

if(A

<B)and(A<C)thenwrite(A)

else if(A

B)and(B

<C)then

write(B)

else

write(C)

endif

endif結(jié)構(gòu)化程序59例2用二分法求方程f(x=0

在區(qū)間[a..b]中的根的程序

假設(shè)在閉區(qū)間[a..b]上函數(shù)f(x)有惟一的一個零點.abf(x)60

f0=f(a);f1=f(b); //程序1片段

if(f0*f1<=0){

x0=a;x1=b;

for(i=1;i<=n;i++){

xm=(x0+x1)/2;fm=f(xm);

if(abs(fm)<eps||abs(x1-x0)<eps)

goto

finish;

if(f0*fm>0){x0=xm;f0=fm;}

elsex1=xm;

}

61

finish:printf

(“\nTherootofthisequationis%d\n”,xm

);

}單入口,兩出口正常出口是循環(huán)達到n次,非正常出口是循環(huán)中途控制轉(zhuǎn)出到標(biāo)號finish所在位置.可讀性好62

f0=f(a);f1=f(b);//程序2片段

if(f0*f1<=0){

x0=a;x1=b;

for(i=1;i<=n;i++){//正常出口

xm=(x0+x1)/2;fm=f(xm);

if(abs(fm)<eps||abs(x1-x0)<eps)break;//非正常出口

if(f0*fm>0)

{x0=xm;f0=fm;}

else

x1=xm;

}}請改成結(jié)構(gòu)化程序63

f0=f(a);f1=f(b);//程序3片段

if(f0*f1<=0){

x0=a;x1=b;i=1;finished=0;

while(i<=n&&finished==0){

xm=(x0+x1)/2;fm=f(xm);

if(abs(fm)<eps||abs(x1-x0)<eps)

finished=1;

if(finished==0)

64

if(f0*fm>0)

{x0=xm;f0=fm;}

else

x1=xm

;

}}引入布爾變量finished,改for

型循環(huán)為while型,將單入口多出口結(jié)構(gòu)改為單入口單出口結(jié)構(gòu)。

65自頂向下,逐步求精把一個模塊的功能逐步分解,細化為一系列具體的步驟,進而翻譯成一系列用某種程序設(shè)計語言寫成的程序。66例用篩選法求100以內(nèi)的素數(shù)篩選法就是從2到100中去掉2,3,…,9,10的倍數(shù),剩下的就是100以內(nèi)的素數(shù)。為了解決這個問題,可先按程序功能寫出一個框架。67main

(){//程序框架

建立2到100的數(shù)組A[],其中A[i]=i;-----1

建立2到10的素數(shù)表B[],其中存放2

到10以內(nèi)的素數(shù);-----2

若A[i]=i是B[]中任一數(shù)的倍數(shù),則剔除A[i];-----3

輸出A[]中所有沒有被剔除的數(shù);-----4}

68main(){

/*建立2到100的數(shù)組A[],其中A[i]=i*/

for

(i=2;i<=100;i++)A[i]=i;

/*建立2到10的素數(shù)表B[],其中存放2到

10以內(nèi)的素數(shù)*/

B[1]=2;B[2]=3;B[3]=5;B[4]=7;

/*若A[i]=i是B[]中任一數(shù)的倍數(shù),則剔除A[i]*/

for(j=1;j<=4;j++)

檢查A[]所有的數(shù)能否被B[]整除,并將能被整除的數(shù)從A[]中剔除;-----3.1

69

/*輸出A[]中所有沒有被剔除的數(shù)*/for(i=2;i<=100;i++)

若A[i]沒有被剔除,則輸出之---4.1}對框架中的局部再做細化,得到整個程序。70

for(i=2;i<=100;i++)

if(A[i]/B[j]*B[j]==A[i])

A[i]=0;

/*輸出A[]中所有沒有被剔除的數(shù)*/for(i=2;

i<=100;i++)

/*若A[i]沒有被剔除,則輸出之*/

if(A[i]!=0)

printf(

“A[%d]=%d\n”,i,A[i]);

}71自頂向下,逐步求精方法的優(yōu)點:符合人們解決復(fù)雜問題的規(guī)律,提高軟件開發(fā)的成功率和生產(chǎn)率.用先整體后部分,先抽象后具體的逐步求精的思想開發(fā)出來的程序具有清晰的層次結(jié)構(gòu),程序容易閱讀和理解.72程序自頂向下,逐步細化,分解成一個樹形結(jié)構(gòu)。同一層節(jié)點上的細化工作相互獨立。有利于編碼、測試和集成.程序清晰和模塊化,使得在修改和重新設(shè)計一個軟件時,可復(fù)用的代碼量大.模塊化,有利于設(shè)計的分工和組織.73實例陷阱與缺陷

(CTraps

and

Pitfalls)AndrewKoenig著

1詞法“陷阱”

(1)=(賦值符)

和==(比較符)不同

如:檢查新變量的初值是否為零

if(x=y)

foo();

應(yīng)寫作:if((x=y)!=0)

foo();

74例

跳過文件中空格、制表符和換行號

while(c==‘\t’‖c=‘‘‖c==‘\n’)c=getc(f);理解為:while((c==‘\t’‖c)=(‘‘‖c==‘\n’))c=getc(f);若寫為:while(c=‘‘‖c==‘\t’‖c==‘\n’)c=getc(f);會出現(xiàn)什么問題?直接了當(dāng)表明意圖

while(c==‘\t’‖c==‘‘‖c==‘\n’)c=getc(f);

75

(2)詞法分析中的“貪心法”區(qū)分單字符符號和多字符符號問題1:表達式a---b與a---b的含義相同,

而與a---b的含義不同,為什么?

問題2:用x除以p所指向的值,所得的商再賦給y:y=x/*p/*p指向除數(shù)*//*p理解注釋

y=x/*py=x/(*p)哪種表示能表達原意?為什么?

若寫為:while(‘\t’==c‖‘‘==c‖‘\n’==c)c=getc(f);若把==寫作=編譯器會怎樣處理?

76問題3:為什么n-->0的含義是n-->0,

而不是n-->0?

編譯器讀>之前,己將--作為單個字符了.

問題4:a+++++b

的含義是什么?

有意義的解析是:a+++++b

大嘴法規(guī)則為:a+++++b

等價于:((a++)++)+b但語法上無意義.a++結(jié)果不能作為左值,編譯器不接受

a++作為后面的++運算符的操作數(shù).

在編程實踐中慎用這種結(jié)構(gòu).

77

2

語法”陷阱”

(1)理解函數(shù)聲明

(2)運算符的優(yōu)先級問題

(3)語句結(jié)束標(biāo)志的分號

(4)switch語句、函數(shù)調(diào)用、else引發(fā)的問題.3語義”陷阱”

(1)指針與數(shù)組

(2)非數(shù)組的指針

(3)作為參數(shù)的數(shù)組聲明

(4)空指針

(5)邊界計算等784.可移植性缺餡

C能方便地在不同環(huán)境中移植

C的編譯器如此之多,各有差異.雖有ANSIc

但不是萬驗靈藥可移植性是寬泛的主題.MarkHorton著的

HowtoWritePortableSoftwareinC(Prentice-Hall)中有詳細闡述下面是從語言屬性上而不是從函數(shù)庫屬性上講述可移植性問題.79例1.在square函數(shù)中計算它的雙精度類型參數(shù)的平方值.doublesquare(doublex){returnx*x;}

這個程序在某些編譯器上不能通過.80

若改為doublesquare(x)doublex;{returnx*x;}

舊風(fēng)格寫的,增加了可移植性.在調(diào)用square函數(shù)時要聲明:doublesquare();main(){

printf(“%g\n”,square(3));}函數(shù)square的聲明中沒有對參數(shù)類型說明.編譯器無法知道

square參數(shù)是double而不是

int.打印垃圾信息.81若改為doublesquare(double);main(){

printf(“%g\n,”,square(3));}3被自動轉(zhuǎn)換為double類型另一種寫法doublesquare();main(){

printf(“%g/n,”,square(3.0));}82例2.接受兩個參數(shù):一個是long型整數(shù)和一個函數(shù)指針.把long

型整數(shù)轉(zhuǎn)為其10進制表示,并對10進制表示中的每個字符都調(diào)用函數(shù)指針?biāo)赶虻暮瘮?shù).

void

printnum(longn,void(*p)()){if(n<0){(*p)(‘-’);n=-n;}if(n>=10)

printnum(n/10,p);(*p)((int)(n%10)+‘0’);

}83

為了使*p能夠處理正確參數(shù)類型,把表達式n%10

的類型轉(zhuǎn)為int類型.在ANSIC標(biāo)準(zhǔn)中并不必要.

但主要是為了避免某些人可能只是簡單地改寫一下printrum的函數(shù)頭,就將程序移植到早期的C上.

可移植性問題1,通過n%10來得到末位數(shù)字的值,

但給它加上’0’來得到對應(yīng)的字符表示不一定合適.

程序中的加法操作實際上假定在機器的字符集中數(shù)字是順序排列、無間隔,這樣才有’0’+5的值與’5’的值相同.這種假定對ASCII字符集和EBCDIC

字符集是正確的,對符合ANSI的C實現(xiàn)也是正確的.

但對某些機器可能出錯,為此可將上程序中的函數(shù)84

Printnum改為如下:

printnum(n/10,p)(*p)(“0123456789”[n%10]);問題2,n<0先打印一個負號,然后n=-n,可能會溢出.如一個long型整數(shù)有k位及一個符號位,該

long型整數(shù)能表示-2卻不能表示2.

解決方法1:

把-n賦給一個unsignedliog型的變量,然

后對這個變量進行操作。但不能對-n求值,因

為這樣做將引起溢出.kk85

解決方法2:改變一個負數(shù)符號對,不將n轉(zhuǎn)為對應(yīng)的正數(shù).

以同樣方式來處理正數(shù)和負數(shù),只不過當(dāng)n為

負數(shù)時需打印一個負號.程序在打印負號后強制n

為負數(shù),讓所有的算術(shù)運算都是針對負數(shù)進行的.

即保證打印負號的操作所對應(yīng)的程序只被執(zhí)行一次,現(xiàn)把程序分解為兩個函數(shù),printnum函數(shù)只檢查n是否為負,是,打印負號。無論n為正為負,

printnum函數(shù)將調(diào)用printneg函數(shù),以n的絕對值的相反數(shù)為參數(shù),這樣printneg函數(shù)就滿足了n總為負或零的條件:

86

void

printneg(longn,void(*p)()){if(n<=-10)

printneg(n/10,p);(*p)(“0123456789”[-(n%10)]);}

87

void

printnum(longn,void(*p)()){if(n<0){(*p)(‘-’);

printneg(n,p);}else

printneg(-n,p);}88

還要考慮:

當(dāng)n為負數(shù)時,n%10有可能是一個正數(shù),

此時-(n%10)就是一個負數(shù),

“0123456789”[-(n%10)]就不在數(shù)組之中.

現(xiàn)創(chuàng)建兩個臨時變置來保存商和余數(shù).

除法運算完后,檢查余數(shù)是否在合理范圍內(nèi),

若不是,則適當(dāng)調(diào)整兩個變量.

只需改動printneg函數(shù)即可89

void

printneg(longn,void(*p)()){longq;

intr;q=n/10;r=n%10;if(r>0){r-=10;q++;}

if(n<=-10)

printneg(q,p);(*p)(“0123456789”[-r]);}

由此可見為了滿足可移植性,做了太多的工作.

但編程環(huán)境不斷在變化,

提高軟件的可移植性,便延長了軟件的生命周期

90

例3檢測內(nèi)存耗盡的異常情況的簡單程序

char*

Malloc(unsignedn){char*p,*malloc(unsigned);p=malloc(n);if(p==NULL)panic(“outofmemory”);returnp;}

程序的意圖是在調(diào)用malloc函數(shù)分配內(nèi)存的地方,改為調(diào)用Malloc函數(shù).若malloc調(diào)用失敗,則panic被調(diào)用,終止程序,打印一條出錯信息.

這樣客戶不必在每次調(diào)用malloc時都進行檢查.91

考慮兩點:若這個函數(shù)的編譯環(huán)境不區(qū)分大小寫的字符時,

會出現(xiàn)怎樣情況?

在Malloc函數(shù)中調(diào)用庫函數(shù)malloc時,實際上調(diào)用的卻是Malloc函數(shù)自身.若這個函數(shù)的編譯環(huán)境區(qū)分大小寫的字符時,雖

能正常工作,但結(jié)果卻是程序在第一次試圖分配

內(nèi)存時對Malloc函數(shù)的調(diào)用將引起一分系列的遞歸調(diào)用,而這些遞歸調(diào)用又不存在一個返回點,

會引起災(zāi)難性的后果.

92例4整數(shù)的大小

C語言提供三種不同長度的整數(shù):short型、int型、long型.ANSI標(biāo)準(zhǔn)要求long型整數(shù)長度至少是32位,

而short型和int型整數(shù)的長度至少應(yīng)是16位這對編程實踐有什么意義?

如在一個非正式情況下,上述整型長度也不一定能保證,程序員可以用一個int整型數(shù)來表示一個數(shù)據(jù)表格的大小或數(shù)組下標(biāo).

若一個變量需要存放千萬數(shù)據(jù)量級的數(shù)據(jù),該如何?93

一個可移植性比較好的方法是聲明該變量為long型,定義一個“新的”類型更為清晰:

typedeflongtenmil;

用這個新類型聲明所有此類變量.94例5內(nèi)存位置0

null指針并不指向任何對象,除非是用于賦值或比較運算符.

如p或q是一個null指針,那么strcmp(p,q)

的值是未定義的.

這種情況,會得到什么結(jié)果?取決于編譯器.

某些C語言實現(xiàn)了對內(nèi)存位置0強加了硬件級的讀保護,在其上工作的程序如果錯誤的使用了

null指針,將立即終止執(zhí)行.

另一些C語言實現(xiàn)上對內(nèi)存位置0只允許讀,不能寫.一個

null指針指向某個字符串,其內(nèi)容是一推“垃圾信息”.

95

還有一些C語言實現(xiàn)對內(nèi)存位置0既允許讀也允

許寫,在其上工作的程序若錯誤使用了一個null

指針,則可能覆蓋了操作系統(tǒng)的部分內(nèi)容,造成徹底的災(zāi)難.

在所有C語言程序中誤用null指針的效果都是未定義的.然而,這樣的程序可能在某個C語言實現(xiàn)

上”似乎”能夠工作,只有當(dāng)該程序轉(zhuǎn)移到另一臺機器上運行時才會暴露問題.

解決辦法:把程序移到不允許讀取內(nèi)存位置0

的機器上運行.96

#include<stdio.h>main(){char*p;p=NULL;

printf(“Location0contains%d\n”,*p);}

在禁止讀取內(nèi)存地址的機器上,這個程序?qū)?zhí)行失敗.在其他機器上,這個程序?qū)?0進制的格式打印出內(nèi)存位置0中存儲的字符內(nèi)容.97例6除法運算時發(fā)生的截斷

q=a/b,r=a%b,b>0a、b、q、r之間關(guān)系:(1)q*b+r==a滿足定義余數(shù)的關(guān)系.(2)改變a的正負號,希望會改變q的符號,

但不改變q的絕對值.(3)當(dāng)b>0時,應(yīng)保證r=0且r<b.

如,余數(shù)用于哈希表的索引,確保它是一個有效的索引值很重要.

上述為整數(shù)除法和余數(shù)操作所具備的,

但不可能同時成立.

98

如:3/2,商為1,余數(shù)也為1.滿足(1).(-3)/2=?

滿足(2)商為-1,余數(shù)必定為-1.不滿足(3).

若先滿足(3),即余數(shù)是1,根據(jù)(1)商是-2,那么(2)無法滿足了.

在實現(xiàn)整數(shù)除法截斷運算時,大多數(shù)C編譯器

選擇放棄(3).

然而C語言定義只保證(1),以及當(dāng)a>=0且b>0

時,保證︱r︱<b以及r>=0.后面的保證與性質(zhì)

(2)和(3)比起來,限制性要弱得多.99

C語言的這種不靈活性,編程者應(yīng)清楚知道做什么,該做什么,這個定義對讓整數(shù)除法運算滿足其需要來說還是夠用的.

如,有一個數(shù)n,見代表標(biāo)識符中的字符經(jīng)過某種函數(shù)運算后的結(jié)果,我們希望通過除法運算得到哈希表的條目h,滿足0<=h<HASHSIZE.

若N恒為非負,有H=N%HASHSIZE.;

若N有可能為負數(shù),此時H也有可能為負,

這樣做就不一定總是合適的了.不過,我們己知:H>-HASHSIZE,這樣可以寫:

100

H=N%HASHSIZE;IF(h<0)h+=HASHSIZE;

更好的辦法是:

程序在設(shè)計時就應(yīng)避免n的值為負這種情形,并聲明n為無符號數(shù).101例7編寫一個函數(shù),對一個己排序的整數(shù)表執(zhí)行二分查找.

函數(shù)的輸入包括一個指向表頭的指針,表中的元素個數(shù),及待查找的數(shù)值.

函數(shù)的輸出是一個指向滿足查找要求的元素的指針,當(dāng)未查找到要求的數(shù)值時,輸出一個null指針.

下面的二分查找用到不對稱邊界,

一個是數(shù)組下標(biāo),另一個是指針.

102

設(shè)搜索元素為x,若x存在于數(shù)組中,它在數(shù)組中

下標(biāo)為k.0<=k<n

假定lo和hi是不對稱邊界的兩頭.要求lo<=k<hi

若lo=hi,此時可能范圍己縮為空,可判定x不在表中.

若lo<hi,可能范圍存在一個元素.

設(shè)mid為可能范圍的中值如果x<表中下標(biāo)為mid的元素,可hi=mid

如果x>表中下標(biāo)為mid的元素,設(shè)置lo=mid+1

如果x=表中下標(biāo)為mid的元素,完成搜索.

0lokhin103

是否可以設(shè)置mid=(hi+lo)/2?hi與lo相隔較遠,可以.hi與lo相隔很近,會怎樣?

討論:若hi=lo;x的可能范圍為空;

若hi=lo+2;也不成問題;

若hi+lo=2×lo+2;這是個偶數(shù),因此

(hi+lo)/2=lo+1;

當(dāng)hi=lo+1時,情況如何?

在這種情況下,可能范圍中的惟一元素就是lo,因此如果(hi+lo)/2=lo這個結(jié)果是可接受的.

104

由于hi+lo恒為正數(shù),(hi+lo)/2會得到希望結(jié)果lo.在這種情況下,整數(shù)除法肯定將會被截斷處理.

因此,(hi+lo)/2等價于(lo+1)+lo)/2

亦即(2xlo+1)/2=lo.

程序為:

105

int*bsearch(int*t,intn,intx){

intlo=0,hi=n;while(lo<hi){

intmid=(hi+lo)/2;if(x<t[mid])hi=mid;elseif(x>t[mid])lo=mid+1;elsereturnt+mid;}returnNULL;}

106

注意:

intmid=(hi+lo)/2;中的除法運算可以用移位運算代替:

intmid=(hi+lo)>>1;

可以提高程序的運行速度.

在很多機器上,下標(biāo)運算比指針運算慢.

可以把t+mid的值存儲在一個局部變量中,

不需要每次重新計算,略減小一些地址運算:107

int*bsearch(int*t,intn,intx){

intlo=0,hi=n;while(lo<hi){

intmid=(hi+lo)/2;

int*p=t+mid;if(x<*p)hi=mid;elseif(x>*p)lo=mid+1;elsereturnp;}returnNULL;}

108

int*bsearch(int*t,intn,intx){

int*lo=t,*hi=t+n;while(lo<hi){

int*mid=(hi+lo)/2;if(x<*mid)hi=mid;elseif(x>*mid)lo=mid+1;elsereturnmid;}returnNULL;}

為進一步減少尋址運算,在整個程序中用指針代替下標(biāo).

109

討論:mid=(lo+hi)/2;是非法的.它試圖把兩個指針相加.正確方法:

mid=lo+(hi–lo)/2;

對大多數(shù)C編譯器都足夠“智能”,會自動將這類除法運算實現(xiàn)為移位運算以優(yōu)化程序性能.

但對這里的除2運算,編譯器不夠“智能”,不會實現(xiàn)移位運算.編譯器所知道的只是hi-lo可能為負,對負數(shù),除2運算和移位運算會得到不同結(jié)果.自己應(yīng)寫成移位運算的形式:

mid=lo+(hi-lo)>>1;

完整程序如下:110

int*bsearch(int*t,intn,intx){

int*lo=t,*hi=t+n;while(lo<hi){

int*mid=lo+(hi-lo)>>1);if(x<*mid)hi=mid;elseif(x>*mid)lo=mid+1;elsereturnmid;}returnNULL;}

111

說明:

二分查找經(jīng)常用對稱邊界來表達,因為采用對稱邊界后得到的程序要整齊許多.如下程序

若將其改為純指針形式就要遇到麻煩.

不能把hi初始化為t+n-1,因為當(dāng)n為0時,

這是個無效地址!因此把程序改為指針形式,

就必須對這種情形單獨測試,這從另一個角度說明了采用不對稱邊界的原因.112

int*bsearch(int*t,intn,intx){

intlo=0,hi=n-1;while(lo<=hi){

intmid=(hi+lo)/2;if(x<t[mid])hi=mid-1;elseif(x>t[mid])lo=mid+1;elsereturnt+mid;}returnNULL;}

113

CHILDREN附錄ComputationalProblemHardwareProblemI/OandfileProblemLibraryfunctionProblemDatainputProblemReturn-valueProblem:function/ProcedurecallExternalUser/clientProblemNullpointerandmemoryProblemsExceptionfault114Dividebyzero

UninitializedvariableSquarerootofanegativenumberTypemismatchInsufficientprecisionOverflow/UnderflowComputationalProblem115

EmptydatafileIncorrectdelimitersNon-ASCIINon-numericinnumericfieldExtraneousdataMissingdataData

溫馨提示

  • 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)方式做保護處理,對用戶上傳分享的文檔內(nèi)容本身不做任何修改或編輯,并不能對任何下載內(nèi)容負責(zé)。
  • 6. 下載文件中如有侵權(quán)或不適當(dāng)內(nèi)容,請與我們聯(lián)系,我們立即糾正。
  • 7. 本站不保證下載資源的準(zhǔn)確性、安全性和完整性, 同時也不承擔(dān)用戶因使用這些下載資源對自己和他人造成任何形式的傷害或損失。

評論

0/150

提交評論