C語言中的常見問題_第1頁
C語言中的常見問題_第2頁
C語言中的常見問題_第3頁
C語言中的常見問題_第4頁
C語言中的常見問題_第5頁
已閱讀5頁,還剩142頁未讀 繼續(xù)免費閱讀

下載本文檔

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

文檔簡介

第1章

C語言

本章主要描述C語言一些基本要素。當(dāng)你開始編寫C程序時,你可能對C語言的一些基本問題感到困惑,如C語言所使用的約定、關(guān)鍵字和術(shù)語等。本章將回答這方面你經(jīng)常會遇到的一些問題。

例如,switch語句是最常用的一種C語言構(gòu)件,本章將回答與它有關(guān)的三個常見問題。本章還涉及其它幾個問題,如循環(huán)、分支、運算符的優(yōu)先級和程序塊技術(shù)。在閱讀本章時,請注意有關(guān)switch語句和運算符優(yōu)先級的一些問題,這些問題常常會使C語言的初學(xué)者感到迷惑。

1.1

什么是局部程序塊(localblock)?

局部程序塊是指一對大括號({})之間的一段C語言程序。一個C函數(shù)包含一對大括號,這對大括號之間的所有內(nèi)容都包含在一個局部程序塊中。if語句和swich語句也可以包含一對大括號,每對大括號之間的代碼也屬于一個局部程序塊。此外,你完全可以創(chuàng)建你自己的局部程序塊,而不使用C函數(shù)或基本的C語句。你可以在局部程序塊中說明一些變量,這種變量被稱為局部變量,它們只能在局部程序塊的開始部分說明,并且只在說明它的局部程序塊中有效。如果局部變量與局部程序塊以外的變量重名,則前者優(yōu)先于后者。下面是一個使用局部程序塊的例子:

#include<stdio.h>

voidmain(void);

voidmain()

{

/*Beginlocalblockforfunctionmain()*/

inttest_var=10;

printf("Testvariablebeforetheifstatement:%d\n",test_var);

if(test_var>5)

{

/*Beginlocalblockfor"if"statement*/

inttest_var=5;

printf("Testvariablewithintheifstatement:%d\n",

test_var);

{

/*Beginindependentlocalblock(nottiedto

anyfunctionorkeyword)*/

inttest_var=0;

printf(

"Testvariablewithintheindependentlocalblock:%d\n",

test_var)

}

/*Endindependentlocalblock*/

printf("Testvariableaftertheifstatement:%d\n",test_var);

}/*Endlocalblockforfunctionmain()*/

上例產(chǎn)生如下輸出結(jié)果:

Testvariablebeforetheifstatement:10

Testvariablewithintheifstatement:5

Testvariablewithintheindependentlocalblock:0

Testvariableaftertheifstatement:10

注意,在這個例子中,每次test_var被定義時,它都要優(yōu)先于前面所定義的test_var變量。此外還要注意,當(dāng)if語句的局部程序塊結(jié)束時,程序重新進(jìn)入最初定義的test_var變量的作用范圍,此時test_var的值為10。

請參見:

1.2可以把變量保存在局部程序塊中嗎?1.2

可以把變量保存在局部程序塊中嗎?

用局部程序塊來保存變量是不常見的,你應(yīng)該盡量避免這樣做,但也有極少數(shù)的例外。例如,為了調(diào)試程序,你可能要說明一個全局變量的局部實例,以便在相應(yīng)的函數(shù)體內(nèi)部進(jìn)行測試。為了使程序的某一部分變得更易讀,你也可能要使用局部程序塊,例如,在接近變量被使用的地方說明一個變量有時就會使程序變得更易讀。然而,編寫得較好的程序通常不采用這種方式來說明變量,你應(yīng)該盡量避免使用局部程序塊來保存變量。

請參見:

1.1什么是局部程序塊?1.3

什么時候用一條switch語句比用多條if語句更好?

如果你有兩個以上基于同一個數(shù)字(numeric)型變量的條件表達(dá)式,那么最好使用一條switch語句。例如,與其使用下述代碼:

if(x==l)

printf("xisequaltoone.\n");

elseif(x==2)

printf("xisequaltotwo.\n");

elseif(x==3)

printf("xisequaltothree.\n");

else

printf("xisnotequaltoone,two,orthree.\n");

不如使用下述代碼,它更易于閱讀和維護(hù):

switch(x)

{

case1:

printf("xisequaltoone.\n");

break;

case2:

printf("xisequaltotwo.\n");

break

case3:

printf('xisequaltothree.\n");

break;

default:printf("xisnotequaltoone,two,orthree.\n");

break;

}

注意,使用switch語句的前提是條件表達(dá)式必須基于同一個數(shù)字型變量。例如,盡管下述if語句包含兩個以上的條件,但該例不能使用switch語句,因為該例基于字符串比較,而不是數(shù)字比較:

char*name="Lupto";

if(!stricmp(name,"Isaac"))

printf("Yournamemeans'Laughter'.\n");

elseif(!stricmp(name,"Amy"))

printf("Yournamemeans'Beloved'.\n");

elseif(!stricmp(name,"Lloyd"))

printf("Yournamemeans'Mysterious'.\n");

else

printf("Ihaven'taclueastowhatyournamemeans.\n");

請參見:

1.4switch語句必須包含default分支嗎7

1.5switch語句的最后一個分支可以不要break語句嗎?1.4switch語句必須包含default分支嗎?

不,但是為了進(jìn)行錯誤檢查或邏輯檢查,還是應(yīng)該在switch語句中加入default分支。例如,下述switch語句完全合法:

switch(char_code)

{

casetyt:

case'y':printf("YouansweredYES!\n")

break

case'N':

case'n':printf("YouansweredNO!\n");

break

}

但是,如果一個未知字符被傳遞給這條switch語句,會出現(xiàn)什么情況呢?這時,程序?qū)]有任何輸出。因此,最好還是加入一個default分支,以處理這種情況:

default:printf("Unknownresponse:%d\n",char_code);

break

此外,default分支能給邏輯檢查帶來很多方便。例如,如果用switch語句來處理數(shù)目固定的條件,而且認(rèn)為這些條件之外的值都屬于邏輯錯誤,那么可以加入一個default分支來辨識邏輯錯誤。請看下列:

voidmove_cursor(intdirection)

{

switch(direction)

{

caseUP:

cursor_up()

break

caseDOWN:

cursor_down()

break

caseLEFT:

cursor_left()

break

caseRIGHT:

cursor_right()

break

default:

printf("Logicerroronlinenumber%ld!!!\n",

__LINE__)

break

}

}

請參見:

1.3什么時候用一條switch語句比用多條if語句更好?

1.5Switch語句的最后一個分支可以不要break語句嗎?1.5switch語句的最后一個分支可以不要break語句嗎?

盡管switch語句的最后一個分支不一定需要break語句,但最好還是在switch語句的每個分支后面加上break語句,包括最后一個分支。這樣做的主要原因是:你的程序很可能要讓另一個人來維護(hù),他可能要增加一些新的分支,但沒有注意到最后一個分支沒有break語句,結(jié)果使原來的最后一個分支受到其后新增分支的干擾而失效。在每個分支后面加上break語句將防止發(fā)生這種錯誤并增強程序的安全性。此外,目前大多數(shù)優(yōu)化編譯程序都會忽略最后一條break語句,所以加入這條語句不會影響程序的性能。

請參見:

1.3什么時候用一條switch語句比用多條if語句更好?

1.4switch語句必須包含default分支嗎?1.6除了在for語句中之外,在哪些情況下還要使用逗號運算符?

逗號運算符通常用來分隔變量說明、函數(shù)參數(shù)、表達(dá)式以及for語句中的元素。下例給出了使用逗號的多種方式:

#include<stdio.h>

#include<stdlib.h>

voidmain(void);

voidmain()

{

/*Here,thecommaoperatorisusedtoseparate

threevariabledeclarations.

*/

inti,j,k;

/*Noticehowyoucanusethecommaoperatortoperform

multipleinitializationsonthesameline.

*/

i=0,j=1,k=2;

printf("i=%d,j=%d,k=%d\n",i,j,k);

/*Here,thecommaoperatorisusedtoexecutethreeexpressions

inoneline:assignktoi,incrementj,andincrementk.

Thevaluethatireceivesisalwaystherigbtmostexpression.

*/

i=(j++,k++);

printf("i=%d,j=%d,k=%d\n",i,j,k);

/*Here,thewhilestatementusesthecommaoperatorto

assignthevalueofiaswellastestit.

*/

while(i=(rand()%100),i!=50)

printf("iis%d,tryingagain...\n",i)

printf("\nGuesswhat?iis50!\n")

}

請注意下述語句:

i:(j++,k++)

這條語句一次完成了三個動作,依次為:

(1)把k值賦給i。這是因為左值(lvaule)總是等于最右邊的參數(shù),本例的左值等于k。注意,本例的左值不等于k++,因為k++是一個后綴自增表達(dá)式,在把k值賦給j之后k才會自增。如果所用的表達(dá)式是++k,則++k的值會被賦給i,因為++k是一個前綴自增表達(dá)式,k的自增發(fā)生在賦值操作之前。

(2)j自增。

(3)k自增。

此外,還要注意看上去有點奇怪的while語句:

while(i=(rand()%100),i!=50)

printf("iis%d,tryingagain...\n");

這里,逗號運算符將兩個表達(dá)式隔開,while語句的每次循環(huán)都將計算這兩個表達(dá)式的值。逗號左邊是第一個表達(dá)式,它把0至99之間的一個隨機數(shù)賦給i;第二個表達(dá)式在while語句中更常見,它是一個條件表達(dá)式,用來判斷i是否不等于50。while語句每一次循環(huán)都要賦予i一個新的隨機數(shù),并且檢查其值是否不等于50。最后,i將被隨機地賦值為50,而while語句也將結(jié)束循環(huán)。

請參見:

1.12運算符的優(yōu)先級總能保證是“自左至右”或“自右至左”的順序嗎?

1.13++var和var++有什么區(qū)別?1.7

怎樣才能知道循環(huán)是否提前結(jié)束了?

循環(huán)通常依賴于一個或多個變量,你可以在循環(huán)外檢查這些變量,以確保循環(huán)被正確執(zhí)行。請看下例:

intx

char*cp[REQUESTED_BLOCKS]

/*Attempt(invain,Imustadd...)to

allocate51210KBblocksinmemory.

*/

for(x=0;

x<REQUESTED_BLOCKS;x++)

{

cpi[x]=(char*)malloc(10000,1)

if(cp[x]==(char*)NULL)

break

}

/*IfxislessthanREQUESTED-BLOCKS,

theloophasendedprematurely.

*/

if(x<REQUESTED_BLOCKS)

printf("Bummer!Myloopendedprematurely!\n");

注意,如果上述循環(huán)執(zhí)行成功,它一定會循環(huán)512次。緊接著循環(huán)的if語句用來測試循環(huán)次數(shù),從而判斷循環(huán)是否提前結(jié)束。如果變量x的值小于512,就說明循環(huán)出錯了。

1.8

goto,longjmp()和setjmp()之間有什么區(qū)別?

goto語句實現(xiàn)程序執(zhí)行中的近程跳轉(zhuǎn)(localjump),longjmp()和setjmp()函數(shù)實現(xiàn)程序執(zhí)行中的遠(yuǎn)程跳轉(zhuǎn)(nonlocaljump,也叫farjump)。通常你應(yīng)該避免任何形式的執(zhí)行中跳轉(zhuǎn),因為在程序中使用goto語句或longjmp()函數(shù)不是一種好的編程習(xí)慣。

goto語句會跳過程序中的一段代碼并轉(zhuǎn)到一個預(yù)先指定的位置。為了使用goto語句,你要預(yù)先指定一個有標(biāo)號的位置作為跳轉(zhuǎn)位置,這個位置必須與goto語句在同一個函數(shù)內(nèi)。在不同的函數(shù)之間是無法實現(xiàn)goto跳轉(zhuǎn)的。下面是一個使用goto語句的例子:

voidbad_programmers_function(void)

{

intx

printf("ExcusemewhileIcountto5000...\n");

xl~

while(1)

{

printf("%d\n",x)

if(x==5000)

gotoall_done

else

x=x+1;

}

all_done:

prinft("Whew!Thatwasn'tsobad,wasit?\n");

}如果不使用goto語句,是例可以編寫得更好。下面就是一個改進(jìn)了實現(xiàn)的例子:voidbetter_function(void)

{

intx

printf("ExcusemewhileIcountto5000...\n");

for(x=1;x<=5000,x++)

printf("%d\n",x)

printf("Whew!Thatwasn'tsobad,wasit?\n");

}

前面已經(jīng)提到,longjmp()和setjmp()函數(shù)實現(xiàn)程序執(zhí)行中的遠(yuǎn)程跳轉(zhuǎn)。當(dāng)你在程序中調(diào)用setjmp()時,程序當(dāng)前狀態(tài)將被保存到一個jmp_buf類型的結(jié)構(gòu)中。此后,你可以通過調(diào)用longjmp()函數(shù)恢復(fù)到調(diào)用setjmp()時的程序狀態(tài)。與goto語句不同,longjmp()和setjmp()函數(shù)實現(xiàn)的跳轉(zhuǎn)不一定在同一個函數(shù)內(nèi)。然而,使用這兩個函數(shù)有一個很大的缺陷,當(dāng)程序恢復(fù)到它原來所保存的狀態(tài)時,它將失去對所有在longjmp()和setjmp()之間動態(tài)分配的內(nèi)存的控制,也就是說這將浪費所有在longjmp()和setjmp()之間用malloc()和calloc()分配所得的內(nèi)存,從而使程序的效率大大降低。因此,你應(yīng)該盡量避免使用longjmp()和setjmp()函數(shù),它們和goto語句一樣,都是不良編程習(xí)慣的表現(xiàn)。

下面是使用longjmp()函數(shù)和setjmp()函數(shù)的一個例子:

#include<stdio.h>

#include<setjmp.h>

jmp_bufsaved_state;

voidmain(void);

voidcall_longjmp(void);voidmain(void)

{

intret_code;

printf("Thecurrentstateoftheprogramisbeingsaved...\n");

ret_code=setjmp(saved_state)

if(ret_code==1)

{

printf("Thelongjmpfunctionhasbeencalled.\n")

printf("Theprogram'spreviousstatehasbeenrestored.\n");

exit(0)

}

printf("Iamabouttocalllongjmpand\n");

printf('returntothepreviousprogramstate...\n")

call_longjmp()

}

voidcall_longjmp(void)

{

longjmp(saved_state,1)

}1.9

什么是左值(lvaule)?

左值是指可以被賦值的表達(dá)式。左值位于賦值語句的左側(cè),與其相對的右值(rvaule,見1.11)則位于賦值語句的右側(cè)。每條賦值語句都必須有一個左值和一個右值。左值必須是內(nèi)存中一個可存儲的變量,而不能是一個常量。下面給出了一些左值的例子:

intx;

int*p_int;

x=1;

p_int=5;

變量x是一個整數(shù),它對應(yīng)于內(nèi)存中的一個可存儲位置,因此,在語句“x=1”中,x就是一個左值。注意,在第二個賦值語句“*p_int=5"中,通過“*”修飾符訪問p_int所指向的內(nèi)存區(qū)域;因此,p_int是一個左值。相反,下面的幾個例子就不是左值:

#defineCONST_VAL10

intx

/*example1*/

l=x;

/*example2*/

CONST_VAL=5;

在上述兩條語句中,語句的左側(cè)都是一個常量,其值不能改變,因為常量不表示內(nèi)存中可

存儲的位置。因此,這兩條賦值語句中沒有左值,編譯程序會指出它們是錯誤的。請參見:

1.10數(shù)組(array)可以是左值嗎?

1.11什么是右值(rvaule)?1.10數(shù)組(array)可以是左值嗎?

在1.9中,左值被定義為可被賦值的表達(dá)式。那么,數(shù)組是可被賦值的表達(dá)式嗎?不是,因為數(shù)組是由若干獨立的數(shù)組元素組成的,這些元素不能作為一個整體被賦值。下述語句是非法的:

intx[5],y[5];

x=y;

不過,你可以通過for循環(huán)來遍歷數(shù)組中的每個元素,并分別對它們賦值,例如:

inti;

intx[5];

inty[5];

for(i=0;i<5,i++)

x[i]=y(tǒng)[i];

此外,你可能想一次拷貝整個數(shù)組,這可以通過象memcpy()這樣的函數(shù)來實現(xiàn),例如:

memcpy(x,y,sizeof(y));

與數(shù)組不同,結(jié)構(gòu)(structure)可以作為左值。你可以把一個結(jié)構(gòu)變量賦給另一個同類型的結(jié)構(gòu)變量,例如:

typedefstructt_name

{

charlast_name[25];

charfirst_name[15];

charmiddle-init[2];

}NAME

...

NAMEmy_name,your_name;

...

your_name=my_name;

...

在上例中,結(jié)構(gòu)變量my_name的全部內(nèi)容被拷貝到結(jié)構(gòu)變量your_name中,其作用和下述語句是相同的:

memcpy(your_name,my_name,sizeof(your_name);

請參見:

1.9

什么是左值(lvaule)?

1.11什么是右值(rvaule)?1.11什么是右值(rvaule)?

在1.9中,左值被定義為可被賦值的表達(dá)式,你也可以認(rèn)為左值是出現(xiàn)在賦值語句左邊的表達(dá)式。這樣,右值就可以被定義為能賦值的表達(dá)式,它出現(xiàn)在賦值語句的右邊。與左值不同,右值可以是常量或表達(dá)式:例如:

intX,y;

x=1;

/*

1iSanrvalue,

xisanlvalue

*/

y=(x+1);

/*

(x+1)isanrvalue;yisanlvalue

*/

在1.9中已經(jīng)介紹過,一條賦值語句必須有一個左值和一個右值,因此,下述語句無法通過編譯,因為它缺少一個右值:

intx;

x=void_function_call();

/*the{unctionvoid—function—call()

returnsnothing*/

如果上例中的函數(shù)返回一個整數(shù),那么它可以被看作一個右值,因為它的返回值可以存儲

到左值x中。

請參見:

1.9

什么是左值(lvaule)?

1.10數(shù)組可以是左值嗎?

1.12運算符的優(yōu)先級總能保證是“自左至右”或“自右至左”的順序嗎?

對這個問題的簡單回答是:這兩種順序都無法保證。C語言并不總是自左至右或自右至左求值,一般說來,它首先求函數(shù)值,其次求復(fù)雜表達(dá)式的值,最后求簡單表達(dá)式的值。此外,為了進(jìn)一步優(yōu)化代碼,目前流行的大多數(shù)C編譯程序常常會改變表達(dá)式的求值順序。因此,你應(yīng)該用括號明確地指定運算符的優(yōu)先級。例如,請看下述表達(dá)式:

a=b+c/d/function—call()*5

上述表達(dá)式的求值順序非常模糊,你很可能得不到所要的結(jié)果,因此,你最好明確地指定運算符的優(yōu)先級:

a=b+(((c/d)/function—call())*5)

這樣,就能確保表達(dá)式被正確求值,而且編譯程序不會為了優(yōu)化代碼而重新安排運算符的優(yōu)先級了。1.13

++var和var++有什么區(qū)別?

“++”運算符被稱為自增運算符。如果“++”運算符出現(xiàn)在變量的前面(++var),那么在表達(dá)式使用變量之前,變量的值將增加1。如果“++”運算符出現(xiàn)在變量之后(var++),那么先對表達(dá)式求值,然后變量的值才增加1。對自減運算符(--)來說,情況完全相同。如果運算符出現(xiàn)在變量的前面,則相應(yīng)的運算被稱為前綴運算;反之,則稱為后綴運算。

例如,請看一個使用后綴自增運算符的例子:

intx,y;

x=1;

y=(x++*5);

上例使用了后綴自增運算符,在求得表達(dá)式的值之后,x的值才增加1,因此,y的值為1乘以5,等于5。在求得表達(dá)式的值之后,x自增為2。

現(xiàn)在看一個使用前綴自增運算符的例子:

intx,y;

x=1;

y=(++x*5);

這個例子和前一個相同,只不過使用了前綴自增運算符,而不是后綴自增運算符,因此,x的值先增加1,變?yōu)?,然后才求得表達(dá)式的值。這樣,y的值為2乘以5,等于10。1.14

取模運算符(modulusoperator)“%”的作用是什么?

取模運算符“%”的作用是求兩個數(shù)相除的余數(shù)。例如,請看下面這段代碼:

x=15/7;

如果x是一個整數(shù),x的值將為2。然而,如果用取模運算符代替除法運算符"/",得到的結(jié)果就不同了:

X=15%7;

這個表達(dá)式的結(jié)果為15除以7的余數(shù),等于1。這就是說,15除以7得2余1。

取模運算符通常用來判斷一個數(shù)是否被另一個數(shù)整除。例如,如果你要打印字母表中序號為3的倍數(shù)的字母,你可以使用下面這段代碼:

intx;

for(x=1;x<=26;x++)

if((x%3)==0)

printf("%c";x+64);

上例將輸出字符串"cfilorux",即字母表中序號為3的倍數(shù)的所有字母。第2章

變量和數(shù)據(jù)存儲

C語言的強大功能之一是可以靈活地定義數(shù)據(jù)的存儲方式。C語言從兩個方面控制變量的性質(zhì):作用域(scope)和生存期(lifetime)。作用域是指可以存取變量的代碼范圍,生存期是指可以存取變量的時間范圍。

作用域有三種:

1.extern(外部的)

這是在函數(shù)外部定義的變量的缺省存儲方式。extern變量的作用域是整個程序。

2.static(靜態(tài)的)

在函數(shù)外部說明為static的變量的作用域為從定義點到該文件尾部;在函數(shù)內(nèi)部說明為static的變量的作用域為從定義點到該局部程序塊尾部。

3.a(chǎn)uto(自動的)

這是在函數(shù)內(nèi)部說明的變量的缺省存儲方式。auto變量的作用域為從定義點到該局部程序塊尾部。

變量的生存期也有三種,但它們不象作用域那樣有預(yù)定義的關(guān)鍵字名稱。第一種是extern和static變量的生存期,它從main()函數(shù)被調(diào)用之前開始,到程序退出時為止。第二種是函數(shù)參數(shù)和auto變量的生存期,它從函數(shù)調(diào)用時開始,到函數(shù)返回時為止。第三種是動態(tài)分配的數(shù)據(jù)的生存期,它從程序調(diào)用malloc()或calloc()為數(shù)據(jù)分配存儲空間時開始,到程序調(diào)用free()或程序退出時為止。2.1

變量存儲在內(nèi)存(memory)中的什么地方?

變量可以存儲在內(nèi)存中的不同地方,這依賴于它們的生存期。在函數(shù)外部定義的變量(全局變量或靜態(tài)外部變量)和在函數(shù)內(nèi)部定義的static變量,其生存期就是程序運行的全過程,這些變量被存儲在數(shù)據(jù)段(datasegment)中。數(shù)據(jù)段是在內(nèi)存中為這些變量留出的一段大小固定的空間,它分為兩部分,一部分用來存放初始化變量,另一部分用來存放未初始化變量。

在函數(shù)內(nèi)部定義的auto變量(沒有用關(guān)鍵字static定義的變量)的生存期從程序開始執(zhí)行其所在的程序塊代碼時開始,到程序離開該程序塊時為止。作為函數(shù)參數(shù)的變量只在調(diào)用該函數(shù)期間存在。這些變量被存儲在棧(stack)中。棧是內(nèi)存中的一段空間,開始很小,以后逐漸自動增大,直到達(dá)到某個預(yù)定義的界限。在象DOS這樣的沒有虛擬內(nèi)存(virtualmemory)的系統(tǒng)中,這個界限由系統(tǒng)決定,并且通常非常大,因此程序員不必?fù)?dān)心用盡棧空間。關(guān)于虛擬內(nèi)存的討論,請參見2.3。

第三種(也是最后一種)內(nèi)存空間實際上并不存儲變量,但是可以用來存儲變量所指向的數(shù)據(jù)。如果把調(diào)用malloc()函數(shù)的結(jié)果賦給一個指針變量,那么這個指針變量將包含一塊動態(tài)分配的內(nèi)存的地址,這塊內(nèi)存位于一段名為“堆(heap)”的內(nèi)存空間中。堆開始時也很小,但當(dāng)程序員調(diào)用malloc()或calloc()等內(nèi)存分配函數(shù)時它就會增大。堆可以和數(shù)據(jù)段或棧共用一個內(nèi)存段(memorysegment),也可以有它自己的內(nèi)存段,這完全取決于編譯選項和操作系統(tǒng)。

與棧相似,堆也有一個增長界限,并且決定這個界限的規(guī)則與棧相同。

請參見:

1.1

什么是局部程序塊(10calblock)?

2.2

變量必須初始化嗎?

2.3

什么是頁抖動(pagethrashing)?

7.20什么是棧(stack)?

7.21什么是堆(heap)7

.2.2

變量必須初始化嗎?

不。使用變量之前應(yīng)該給變量一個值,一個好的編譯程序?qū)椭惆l(fā)現(xiàn)那些還沒有被給定一個值就被使用的變量。不過,變量不一定需要初始化。在函數(shù)外部定義的變量或者在函數(shù)內(nèi)部用static關(guān)鍵字定義的變量(被定義在數(shù)據(jù)段中的那些變量,見2.1)在沒有明確地被程序初始化之前都已被系統(tǒng)初始化為0了。在函數(shù)內(nèi)部或程序塊內(nèi)部定義的不帶static關(guān)鍵字的變量都是自動變量,如果你沒有明確地初始化這些變量,它們就會具有未定義值。如果你沒有初始化一個自動變量,在使用它之前你就必須保證先給它賦值。

調(diào)用malloc()函數(shù)從堆中分配到的空間也包含未定義的數(shù)據(jù),因此在使用它之前必須先進(jìn)行初始化,但調(diào)用calloc()函數(shù)分配到的空間在分配時就已經(jīng)被初始化為0了。

請參見:

1.1

什么是局部程序塊(10calblock)?

7.20什么是棧(stack)?

7.21什么是堆(heap)?2.3

什么是頁抖動(pagethrashing)?

有些操作系統(tǒng)(如UNIX和增強模式下的Windows)使用虛擬內(nèi)存,這是一種使機器的作業(yè)地址空間大于實際內(nèi)存的技術(shù),它是通過用磁盤空間模擬RAM(random—accessmemory)來實現(xiàn)的。

在80386和更高級的IntelCPU芯片中,在現(xiàn)有的大多數(shù)其它微處理器(如Motorola68030,sparc和PowerPC)中,都有一個被稱為內(nèi)存管理單元(MemoryManagementUnit,縮寫為MMU)的器件。MMU把內(nèi)存看作是由一系列“頁(page)”組成的來處理。一頁內(nèi)存是指一個具有一定大小的連續(xù)的內(nèi)存塊,通常為4096或8192字節(jié)。操作系統(tǒng)為每個正在運行的程序建立并維護(hù)一張被稱為進(jìn)程內(nèi)存映射(ProcessMemoryMap,縮與為PMM)的表,表中記錄了程序可以存取的所有內(nèi)存頁以及它們的實際位置。

每當(dāng)程序存取一塊內(nèi)存時,它會把相應(yīng)的地址(虛擬地址,virtualaddress)傳送給MMU,MMU會在PMM中查找這塊內(nèi)存的實際位置(物理地址,physicaladdress),物理地址可以是由操作系統(tǒng)指定的在內(nèi)存中或磁盤上的任何位置。如果程序要存取的位置在磁盤上,就必須把包含該地址的頁從磁盤上讀到內(nèi)存中,并且必須更新PMM以反映這個變化(這被稱為pagefault,即頁錯)。

希望你繼續(xù)讀下去,因為下面就要介紹其中的難點了。存取磁盤比存取RAM要慢得多,所以操作系統(tǒng)會試圖在RAM中保持盡量多的虛擬內(nèi)存。如果你在運行一個非常大的程序(或者同時運行幾個小程序),那么可能沒有足夠的RAM來承擔(dān)程序要使用的全部內(nèi)存,因此必須把一些頁從RAM中移到磁盤上(這被為pagingout,即頁出)。

操作系統(tǒng)會試圖去判斷哪些頁可能暫時不會被使用(通?;谶^去使用內(nèi)存的情況),如果它判斷錯了,或者程序正在很多地方存取很多內(nèi)存,那么為了讀入已調(diào)出的頁,就會產(chǎn)生大量頁錯動作。因為RAM已被全部使用,所以為了調(diào)入要存取的一頁,必須調(diào)出另一頁,而這將導(dǎo)致更多的頁錯動作,因為此時不同的一頁已被移到磁盤上。在短時間內(nèi)出現(xiàn)大量頁錯動作的情形被稱為頁抖動,它將大大降低系統(tǒng)的執(zhí)行效率。

頻繁存取內(nèi)存中大量散布的位置的程序更容易在系統(tǒng)中造成頁抖動。如果同時運行許多小程序,而實際上已經(jīng)不再使用這些程序,也很容易造成頁抖動。為了減少頁抖動,你應(yīng)該減少同時運行的程序的數(shù)目。對于大的程序,你應(yīng)該改變它的工作方式,以盡量使操作系統(tǒng)能準(zhǔn)確地判斷出哪些頁不再需要。為此,你可以使用高速緩沖存儲技術(shù),或者改變用于大型數(shù)據(jù)結(jié)構(gòu)的查找算法,或者使用效率更高的malloc()函數(shù)。當(dāng)然,你也可以考慮增加系統(tǒng)的RAM,以減少頁出動作。

請參見:

7.17

怎樣說明一個大于640KB的數(shù)組?

7.21

什么是堆(heap)?

18.14怎樣才能使DOS程序獲得超過64KB的可用內(nèi)存?

21.31Windows是怎樣組織內(nèi)存的?2.4什么是const指針?

如果希望一個變量在被初始化后其值不會被修改,程序員就會通過cons,修飾符和編譯程序達(dá)成默契。編譯程序會努力去保證這種默契——它將禁止程序中出現(xiàn)對說明為const的變量進(jìn)行修改的代碼。

const指針的準(zhǔn)確提法應(yīng)該是指向const數(shù)據(jù)的指針,即它所指向的數(shù)據(jù)不能被修改。只要在指針說明的開頭加入const修飾符,就可說明一個cosnt指針。盡管const指針?biāo)赶虻臄?shù)據(jù)不能被修改,但cosnt指針本身是可以修改的。下面給出了const指針的一些合法和非法的用法例子:

constchar*str="hello";

charc=*str;

/*legal*/

str++;

/*legal*/

*str='a';

/*illegal*/

str[1]='b';

/*illegal*/

前兩條語句是合法的,因為它們沒有修改str所指向的數(shù)據(jù);后兩條語句是非法的,因為它們要修改str所指向的數(shù)據(jù)。

在說明函數(shù)參數(shù)時,常常要使用const指針。例如,一個計算字符串長度的函數(shù)不必改變字符串內(nèi)容,它可以寫成這樣:

my_strlen(constchar*str)

{

intcount=0;

while(*str++)

{

count++;

}

return

count;

}

注意,如果有必要,一個非const指針可以被隱式地轉(zhuǎn)換為const指針,但一個const指針不能被轉(zhuǎn)換成非const指針。這就是說,在調(diào)用my_strlen()時,它的參數(shù)既可以是一個const指針,也可以是一個非const指針。請參見:

2.7

一個變量可以同時被說明為const和volatile嗎?

2.8

什么時候應(yīng)該使用const修飾符?

2.14什么時候不應(yīng)該使用類型強制轉(zhuǎn)換(typecast)?

2.18用const說明常量有什么好處?2.5

什么時候應(yīng)該使用register修飾符?它真的有用嗎?

register修飾符暗示編譯程序相應(yīng)的變量將被頻繁使用,如果可能的話,應(yīng)將其保存在CPU的寄存器中,以加快其存取速度。但是,使用register修飾符有幾點限制。

首先,register變量必須是能被CPU寄存器所接受的類型。這通常意味著register變量必須是一個單個的值,并且其長度應(yīng)小于或等于整型的長度。但是,有些機器的寄存器也能存放浮點數(shù)。

其次,因為register變量可能不存放在內(nèi)存中,所以不能用取址運算符“&”來獲取register變量的地址。如果你試圖這樣做,編譯程序就會報告這是一個錯誤。

register修飾符的用處有多大還受其它一些規(guī)則的影響。因為寄存器的數(shù)量是有限的,而且某些寄存器只能接受特定類型的數(shù)據(jù)(如指針和浮點數(shù)),因此,真正能起作用的register修飾符的數(shù)目和類型都依賴于運行程序的機器,而任何多余的register修飾符都將被編譯程序所忽略。

在某些情況下,把變量保存在寄存器中反而會降低運行速度,因為被占用的寄存器不能再用于其它目的,或—者變量被使用的次數(shù)不夠多,不足以抵消裝入和存儲變量所帶來的額外開銷。

那么,什么時候應(yīng)該使用register修飾符呢?回答是,對現(xiàn)有的大多數(shù)編譯程序來說,永遠(yuǎn)不要使用register修飾符。早期的C編譯程序不會把變量保存在寄存器中,除非你命令它這樣做,這時register修飾符是C語言的一種很有價值的補充。然而,隨著編譯程序設(shè)計技術(shù)的進(jìn)步,在決定哪些變量應(yīng)該被存到寄存器中時,現(xiàn)在的C編譯程序能比程序員作出更好的決定。

實際上,許多C編譯程序會忽略register修飾符,因為盡管它完全合法,但它僅僅是暗示而不是命令。

在極罕見的情況下,程序運行速度很慢,而你也知道這是因為有一個變量被存儲在內(nèi)存中,也許你最后會試圖在該變量前面加上register修飾符,但是,如果這并沒有加快程序的運行速度,你也不要感到奇怪。

請參見:

2.6什么時候應(yīng)該使用volatile修飾符?2.6

什么時候應(yīng)該使用volatile修飾符?

volatile修飾符告訴編譯程序不要對該變量所參與的操作進(jìn)行某些優(yōu)化。在兩種特殊的情況下需要使用volatile修飾符:第一種情況涉及到內(nèi)存映射硬件(memory-mappedhardware,如圖形適配器,這類設(shè)備對計算機來說就好象是內(nèi)存的一部分一樣),第二種情況涉及到共享內(nèi)存(sharedmemory,即被兩個以上同時運行的程序所使用的內(nèi)存)。

大多數(shù)計算機擁有一系列寄存器,其存取速度比計算機主存更快。好的編譯程序能進(jìn)行一種被稱為“冗余裝入和存儲的刪去”(redundantloadandstoreremoval)的優(yōu)化,即編譯程序會·在程序中尋找并刪去這樣兩類代碼:一類是可以刪去的從內(nèi)存裝入數(shù)據(jù)的指令,因為相應(yīng)的數(shù)據(jù)已經(jīng)被存放在寄存器中;另一種是可以刪去的將數(shù)據(jù)存入內(nèi)存的指令,因為相應(yīng)的數(shù)據(jù)在再次被改變之前可以一直保留在寄存器中。

如果一個指針變量指向普通內(nèi)存以外的位置,如指向一個外圍設(shè)備的內(nèi)存映射端口,那么冗余裝入和存儲的優(yōu)化對它來說可能是有害的。例如,為了調(diào)整某個操作的時間,可能會用到下述函數(shù):

time_ttime_addition(volatileconststructtimer*t,inta),

{

int

n

int

x

time_t

then

x=O;

then=t->value

for(n=O;n<1O00;n++)

{

x=x+a;

}

returnt->value-then;

}

在上述函數(shù)中,變量t->value實際上是一個硬件計數(shù)器,其值隨時間增加。該函數(shù)執(zhí)行1000次把a值加到x上的操作,然后返回t->value在這1000次加法的執(zhí)行期間所增加的值。

如果不使用volatile修飾符,一個聰明的編譯程序可能就會認(rèn)為t->value在該函數(shù)執(zhí)行期間不會改變,因為該函數(shù)內(nèi)沒有明確地改變t->value的語句。這樣,編譯程序就會認(rèn)為沒有必要再次從內(nèi)存中讀入t->value并將其減去then,因為答案永遠(yuǎn)是0。因此,編譯程序可能會對該函數(shù)進(jìn)行“優(yōu)化”,結(jié)果使得該函數(shù)的返回值永遠(yuǎn)是0。

如果一個指針變量指向共享內(nèi)存中的數(shù)據(jù),那么冗余裝入和存儲的優(yōu)化對它來說可能也是有害的,共享內(nèi)存通常用來實現(xiàn)兩個程序之間的互相通訊,即讓一個程序把數(shù)據(jù)存到共享的那塊內(nèi)存中,而讓另一個程序從這塊內(nèi)存中讀數(shù)據(jù)。如果從共享內(nèi)存裝入數(shù)據(jù)或把數(shù)據(jù)存入共享內(nèi)存的代碼被編譯程序優(yōu)化掉了,程序之間的通訊就會受到影響。

請參見:

2.7

一個變量可以同時被說明為const和volatile嗎?

2.14什么時候不應(yīng)該使用類型強制轉(zhuǎn)換(typecast)?2.7一個變量可以同時被說明為const和volatile嗎?

可以。const修飾符的含義是變量的值不能被使用了const修飾符的那段代碼修改,但這并不意味著它不能被這段代碼以外的其它手段修改。例如,在2.6的例子中,通過一個volatileconst指針t來存取timer結(jié)構(gòu)。函數(shù)time_addition()本身并不修改t->value的值,因此t->value被說明為const。不過,計算機的硬件會修改這個值,因此t->value又被說明為volatile。如果同時用const和volatile來說明一個變量,那么這兩個修飾符隨便哪個在先都行,

請參見:

2.6什么時候應(yīng)該使用volatile修飾符?

2.8什么時候應(yīng)該使用const修飾符?

2.14什么時候不應(yīng)該使用類型強制轉(zhuǎn)換(typecast)?2.8什么時候應(yīng)該使用const修飾符?

使用const修飾符有幾個原因,第一個原因是這樣能使編譯程序找出程序中不小心改變變量值的錯誤。請看下例:

while(*str=0)/*programmermeanttowrite*str!=0*/

{

/*somecodehere*/

strq++;

}

其中的“=”符號是輸入錯誤。如果在說明str時沒有使用const修飾符,那么相應(yīng)的程序能通過編譯但不能被正確執(zhí)行。

第二個原因是效率。如果編譯程序知道某個變量不會被修改,那么它可能會對生成的代碼進(jìn)行某些優(yōu)化。

如果一個函數(shù)參數(shù)是一個指針,并且你不希望它所指向的數(shù)據(jù)被該函數(shù)或該函數(shù)所調(diào)用的函數(shù)修改,那么你應(yīng)該把該參數(shù)說明為const指針。如果一個函數(shù)參數(shù)通過值(而不是通過指針)被傳遞給函數(shù),并且你不希望其值被該函數(shù)所調(diào)用的函數(shù)修改,那么你應(yīng)該把該參數(shù)說明為const。然而,在實際編程中,只有在編譯程序通過指針存取這些數(shù)據(jù)的效率比拷貝這些數(shù)據(jù)更高時,才把這些參數(shù)說明為const。

請參見:

2.7

一個變量可以同時被說明為const和volatile嗎?

2.14什么時候不應(yīng)該使用類型強制轉(zhuǎn)換(typecast)?

2.18用const說明常量有什么好處?

2.9

浮點數(shù)比較(floating-pointcomparisons)的可靠性如何?

浮點數(shù)是計算機編程中的“魔法(blackart)”,原因之一是沒有一種理想的方式可以表示一個任意的數(shù)字。電子電氣工程協(xié)會(IEEE)已經(jīng)制定出浮點數(shù)的表示標(biāo)準(zhǔn),但你不能保證所使用的每臺機器都遵循這一標(biāo)準(zhǔn)。

即使你使用的機器遵循這一標(biāo)準(zhǔn),還存在更深的問題。從數(shù)學(xué)意義上講,兩個不同的數(shù)字之間有無窮個實數(shù)。計算機只能區(qū)分至少有一位(bit)不同的兩個數(shù)字。如果要表示那些無窮無盡的各不相同的數(shù)字,就要使用無窮數(shù)目的位。計算機只能用較少的位(通常是32位或64位)來表示一個很大的范圍內(nèi)的數(shù)字,因此它只能近似地表示大多數(shù)數(shù)字。

由于浮點數(shù)是如此難對付,因此比較一個浮點數(shù)和某個值是否相等或不等通常是不好的編程習(xí)慣。但是,判斷一個浮點數(shù)是否大于或小于某個值就安全多了。例如,如果你想以較小的步長依次使用一個范圍內(nèi)的數(shù)字,你可能會編寫這樣一個程序:

#include<stdio.h>

constfloatfirst=O.O;

constfloatlast=70.0

constfloatsmall=O.007

main()

{

float

f;

for(f=first;f!=last&&f<last+1.O;f+=small)

printf("fisnow%g\n",f);

}

然而,舍入誤差(roundingerror)和變量small的表示誤差可能導(dǎo)致f永遠(yuǎn)不等于last(f可能會從稍小于last的一個數(shù)增加到一個稍大于last的數(shù)),這樣,循環(huán)會跳過last。加入不等式"f<last+1.0"就是為了防止在這種情況發(fā)生后程序繼續(xù)運行很長時間。如果運行該程序并且被打印出來的f值是71或更大的數(shù)值,就說明已經(jīng)發(fā)生了這種情況。

一種較安全的方法是用不等式"f<last"作為條件來終止循環(huán),例如:

float

f;

for(f=first;f<last;f+=small)

;

你甚至可以預(yù)先算出循環(huán)次數(shù),然后通過這個整數(shù)進(jìn)行循環(huán)計數(shù):

float

f;

int

count=(last-first)/small;

for(f=first;count-->0;f+=small)

;

請參見:

2.11對不同類型的變量進(jìn)行算術(shù)運算會有問題嗎?2.10

怎樣判斷一個數(shù)字型變量可以容納的最大值?

要判斷某種特定類型可以容納的最大值或最小值,一種簡便的方法是使用ANSI標(biāo)準(zhǔn)頭文件limits.h中的預(yù)定義值。該文件包含一些很有用的常量,它們定義了各種類型所能容納的值,下表列出了這些常量:

CHAR—BIT

char的位數(shù)(bit)

CHAR—MAX

char的十進(jìn)制整數(shù)最大值

CHAR—MIN

char的十進(jìn)制整數(shù)最小值

MB—LEN—MAX

多字節(jié)字符的最大字節(jié)(byte)數(shù)

INT—MAX

int的十進(jìn)制最大值

INT—MIN

int的十進(jìn)制最小值

LONG—MAX

long的十進(jìn)制最大值

LONG—MIN

long的十進(jìn)制最小值

SCHAR—MAX

signedchar的十進(jìn)制整數(shù)最大值

SCHAR—MIN

signedchar的十進(jìn)制整數(shù)最小值

SHRT—MIN

short的十進(jìn)制最小值

SHRT—MAX

short的十進(jìn)制最大值

UCHAR—MAX

unsignedchar的十進(jìn)制整數(shù)最大值

UINT—MAX

unsignedint的十進(jìn)制最大值

ULONG—MAX

unsignedlongint的十進(jìn)制最大值

USHRT—MAX

unsignedshortint的十進(jìn)制最大值

對于整數(shù)類型,在使用2的補碼運算的機器(你將使用的機器幾乎都屬此類)上,一個有符號類型可以容納的數(shù)字范圍為-2位數(shù)-1到(+2位數(shù)-1-1),一個無符號類型可以容納的數(shù)字范圍為0到(+2位數(shù)-1)。例如,一個16位有符號整數(shù)可以容納的數(shù)字范圍為--215(即-32768)到(+215-1)(即+32767)。請參見:

10.1用什么方法存儲標(biāo)志(flag)效率最高?

10.2什么是“位屏幕(bitmasking)”?

10.616位和32位的數(shù)是怎樣存儲的?2.11

對不同類型的變量進(jìn)行算術(shù)運算會有問題嗎?

C有三類固有的數(shù)據(jù)類型:指針類型、整數(shù)類型和浮點類型;

指針類型的運算限制最嚴(yán),只限于以下兩種運算:

-

兩個指針相減,僅在兩個指針指向同一數(shù)組中的元素時有效。運算結(jié)果與對應(yīng)于兩個指針的數(shù)組下標(biāo)相減的結(jié)果相同。

+

指針和整數(shù)類型相加。運算結(jié)果為一個指針,該指針與原指針之間相距n個元素,n就是與原指針相加的整數(shù)。

浮點類型包括float,double和longdouble這三種固有類型。整數(shù)類型包括char,unsignedchar,short,unsignedshort,int,unsignedint,long和unsignedlong。對這些類型都可進(jìn)行以下4種算術(shù)運算:

+

-

*

/

對整數(shù)類型不僅可以進(jìn)行上述4種運算,還可進(jìn)行以下幾種運算:

%

取模或求余

>>

右移

<<

左移

&

按位與

|

按位或

^

按位異或

!

邏輯非

~

取反

盡管C允許你使用“混合模式”的表達(dá)式(包含不同類型的算術(shù)表達(dá)式),但是,在進(jìn)行運算之前,它會把不同的類型轉(zhuǎn)換成同一類型(前面提到的指針運算除外)。這種自動轉(zhuǎn)換類型的過程被稱為“運算符升級(operatorpromotion)”。

請參見:

2.12什么是運算符升級(operatorpromotion)?

2.12什么是運算符升級(operatorpromotion)?

當(dāng)兩個不同類型的運算分量(operand)進(jìn)行運算時,它們會被轉(zhuǎn)換為能容納它們的最小的類型,并且運算結(jié)果也是這種類型。下表列出了其中的規(guī)則,在應(yīng)用這些規(guī)則時,你應(yīng)該從表的頂端開始往下尋找,直到找到第一條適用的規(guī)則。

運算分量1

運算分量2

轉(zhuǎn)換結(jié)果

longdouble

其它任何類型

longdouble

double

任何更小的類型

double

float

任何更小的類

float

unsignedlong

任何整數(shù)類

unsignedlong

long

unsigned>LONG_MAX

unsignedlong

long

任何更小的類型

long

unsigned

任何有符號類型

unsigned

下面的程序中就有幾個運算符升級的例子。變量n被賦值為3/4,因為3和4都是整數(shù),所以先進(jìn)行整數(shù)除法運算,結(jié)果為整數(shù)0。變量f2被賦值為3/4.0,因為4.0是一個float類型,所以整數(shù)3也被轉(zhuǎn)換為float類型,結(jié)果為float類型0.75。

#include<stdio.h>

main()

{

floatf1=3/4;

floatf2=3/4.0

printf("3/4==%gor%gdependingonthetypeused.\n",f1,f2);

}

請參見:

2.11對不同類型的變量進(jìn)行算術(shù)運算會有問題嗎?

2.13什么時候應(yīng)該使用類型強制轉(zhuǎn)換(typecast)?

2.13什么時候應(yīng)該使用類型強制轉(zhuǎn)換(typecast)?

在兩種情況下需要使用類型強制轉(zhuǎn)換。第一種情況是改變運算分量的類型,從而使運算能正確地進(jìn)行。下面的程序與2.12中的例子相似,但有不同之處。變量n被賦值為整數(shù)i除以整數(shù)j的結(jié)果,因為是整數(shù)相除,所以結(jié)果為0。變量f2也被賦值為i除以j的結(jié)果,但本例通過(float)類型強制轉(zhuǎn)換把i轉(zhuǎn)換成一個float類型,因此執(zhí)行的是浮點數(shù)除法運算(見2.11),結(jié)果為0.75。

#include<stdio.h>

main()

{

int

i=3;

int

j=4

floatf1=i/j;

floatf2=(float)i/j;

printf("3/4==%gor%gdependingonthetypeused.\n",f1,f2);

}

第二種情況是在指針類型和void*類型之間進(jìn)行強制轉(zhuǎn)換,從而與期望或返回void指針的函數(shù)進(jìn)行正確的交接。例如,下述語句就把函數(shù)malloc()的返回值強制轉(zhuǎn)換為一個指向foo結(jié)構(gòu)的指針:

structfoo*p=(structfoo*)malloc(sizeof(structfoo));

請參見:

2.6什么時候應(yīng)該使用volatile修飾符?

2.8什么時候應(yīng)該使用const修飾符?

2.11對不同類型的變量進(jìn)行算術(shù)運算會有問題嗎?

2.12什么是運算符升級(operatorpromotion)?

2.14什么時候不應(yīng)該使用類型強制轉(zhuǎn)換(typecast)?

7.5

什么是void指針?

7.6

什么時候使用void指針?

7.21什么是堆(heap)?

7.27可以對void指針進(jìn)行算術(shù)運算嗎?2.14什么時候不應(yīng)該使用類型強制轉(zhuǎn)換(typecast)?

不應(yīng)該對用const或volatile說明了的對象進(jìn)行類型強制轉(zhuǎn)換,否則程序就不能正確運行。

不應(yīng)該用類型強制轉(zhuǎn)換把指向一種結(jié)構(gòu)類型或數(shù)據(jù)類型的指針轉(zhuǎn)換成指向另一種結(jié)構(gòu)類型或數(shù)據(jù)類型的指針。在極少數(shù)需要進(jìn)行這種類型強制轉(zhuǎn)換的情況下,用共用體(union)來存放有關(guān)數(shù)據(jù)能更清楚地表達(dá)程序員的意圖。

請參見:

2.6什么時候應(yīng)該使用volatile修飾符?

2.8什么時候應(yīng)該使用const修飾符?2.15

可以在頭文件中說明或定義變量嗎?

被多個文件存取的全局變量可以并且應(yīng)該在一個頭文件中說明,并且必須在一個源文件中定義。變量不應(yīng)該在頭文件中定義,因為一個頭文件可能被多個源文件包含,而這將導(dǎo)致變量被多次定義。如果變量的初始化只發(fā)生一次,ANSIC標(biāo)準(zhǔn)允許變量有多次外部定義;但是,這樣做沒有任何好處,因此最好避免這樣做,以使程序有更強的可移植性。

注意:變量的說明和定義是兩個不同的概念,在2.16中將講解兩者之間的區(qū)別。

僅供一個文件使用的“全局”變量應(yīng)該被說明為static,而且不應(yīng)該出現(xiàn)在頭文件中。

請參見:

2.16說明一個變量和定義一個變量有什么區(qū)別?

2.17可以在頭文件中說明static變量嗎?

2.16說明一個變量和定義一個變量有什么區(qū)別?

說明一個變量意味著向編譯程序描述變量的類型,但并不為變量分配存儲空間。定義一個變量意味著在說明變量的同時還要為變量分配存儲空間。在定義一個變量的同時還可以對變量進(jìn)行初始化。下例說明了一個變量和一個結(jié)構(gòu),定義了兩個變量,其中一個定義帶初始化:

externintdecll;

/*thisisadeclaration*/

structdecl2{

intmember;

};

/*thisjustdeclaresthetype--novariablementioned*/

intdef1=8;

/*thisisadefinition*/

intdef2;

/*thisisadefinition*/

換句話說,說明一個變量相當(dāng)于告訴編譯程序“在程序的某個位置將用到一個變量,這里給出了它的名稱和類型”,定義一個變量則相當(dāng)于告訴編譯程序“具有這個名稱和這種類型的變量就在這里”。

一個變量可以被說明許多次,但只能被定義一次。因此,不應(yīng)該在頭文件中定義變量,因為一個頭文件可能會被一個程序的許多源文件所包含。

請參見;

2.17可以在頭文件中說明static變量嗎?2.17

可以在頭文件中說明static變量嗎?

如果說明了一個static變量,就必須在同一個文件中定義該變量(因為存儲類型修飾符static和extern是互斥的)。你可以在頭文件中定義一個static變量,但這會使包含該頭文件的源文件都得到該變量的一份私有拷貝,而這通常不是你想得到的結(jié)果。

請參見:

2.16說明一個變量和定義一個變量有什么區(qū)別?2.18用const說明常量有什么好處?

使用關(guān)鍵字const有兩個好處;第一,如果編譯程序知道一個變量的值不會改變,編譯程.序就能對程序進(jìn)行優(yōu)化;第二,編譯程序會試圖保證該變量的值不會因為程序員的疏忽而被改變。

當(dāng)然,用#define來定義常量也有同樣的好處。用const而不用#define來定義常量的原因是const變量可以是任何類型(如結(jié)構(gòu),而用#define定義的常量不能表示結(jié)構(gòu))。此外,const變量是真正的變量,它有可供使用的地址,并且該地址是唯一的(有些編譯程序在每次使用用#define定義的字符串時都會生成一份新的拷貝,見9.9)。

請參見:

2.7

一個變量可以同時被說明為const和volatile嗎?

2.8

什么時候應(yīng)該使用const修飾符?

2.14什么時候不應(yīng)該使用類型強制轉(zhuǎn)換(typecast)?

9.9

字符串和數(shù)組有什么不同?第3章

排序與查找

在計算機科學(xué)中,排序(sorting)是研究得最多的問題之一,許多書籍都深入討論了這個問題。本章僅僅是一個介紹,重點放在C語言的實際應(yīng)用上。

排序

程序員可以使用的基本排序算法有5種:

·插入排序(insertionsort.)

·交換排序(exchangesOrt)

·選擇排序(selectionsort)

·歸并排序(mergesort)

·分布排序(distributionsort)

為了形象地解釋每種排序算法是怎樣工作的,讓我們來看一看怎樣用這些方法對桌上一付亂序的牌進(jìn)行排序。牌既要按花色排序(依次為梅花、方塊、紅桃和黑心),還要按點數(shù)排序(從2到A)。

插入排序的過程為:從一堆牌的上面開始拿牌,每次拿一張牌,按排序原則把牌放到手中正確的位置。桌上的牌拿完后,手中的牌也就排好序了。

交換排序的過程為:

(1)先拿兩張牌放到手中。如果左邊的牌要排在右邊的牌的后面,就交換這兩張牌的位置。

(2)然后拿下一張牌,并比較最右邊兩張牌,如果有必要就交換這兩張牌的位置。

(3)重復(fù)第(2)步,直到把所有的牌都拿到手中。

(4)如果不再需要交換手中任何兩張牌的位置,就說明牌已經(jīng)排好序了;否則,把手中的牌放到桌上,重復(fù)(1)至(4)步,直到手中的牌排好序。

選擇排序的過程為:在桌上的牌中找出最小的一張牌,拿在手中;重復(fù)這種操作,直到把所有牌都拿在手中。

歸并排序的過程為:把桌上的牌分為52堆,每堆為一張牌。因為每堆牌都是有序的(記住,此時每堆中只有一張牌),所以如果把相鄰的兩堆牌合并為一堆,并對每堆牌進(jìn)行排序,就可以得到26堆已排好序的牌,此時每一堆中有兩張牌。重復(fù)這種合并操作,就可以依次得到13堆牌(每一堆中有4張牌),7堆牌(有6堆是8張牌,還有一堆是4張牌),最后將得到52張的一堆牌。

分布排序(也被稱作radixsort,即基數(shù)排序)的過程為:先將牌按點數(shù)分成13堆,然后將這13堆牌按點數(shù)順序疊在一起;再將牌按花色分成4堆,然后將這4堆牌按花色順序疊在一起,牌就排好序了。

在選用排序算法時,你還需要了解以下幾個術(shù)語:

(1)自然的(natural)

如果某種排序算法對有序的數(shù)據(jù)排序速度較快(工作量變小),對無序的數(shù)據(jù)排序速度卻較慢(工作變量大),我們就稱這種排序算法是自然的。如果數(shù)據(jù)已接近有序,就需要考慮選用自然的排序算法。

(2)穩(wěn)定的(stable)

如果某種排序算法能保持它認(rèn)為相等的數(shù)據(jù)的前后順序,我們就稱這種排序算法是穩(wěn)定的。

例如,現(xiàn)有以下名單:

MaryJones

MarySmith

TomJones

SusieQueue

如果用穩(wěn)定的排序算法按姓對上述名單進(jìn)行排序,那么在排好序后"MaryJones”和"TomJones”將保持原來的Jr順序,因為它們的姓是相同的。

穩(wěn)定的排序算法可按主、次關(guān)鍵字對數(shù)據(jù)進(jìn)行排序,例如按姓和名排序(換句話說,主要按姓排序,但對姓相同的數(shù)據(jù)還要按名排序)。在具體實現(xiàn)時,就是先按次關(guān)鍵字排序,再按主關(guān)鍵字排序。

(3)內(nèi)部排序(internalsort)和外部排序(externalsort)

待排數(shù)據(jù)全部在內(nèi)存中的排序方法被稱為內(nèi)部排序,待排數(shù)據(jù)在磁盤、磁帶和其它外存中的排序方法被稱為外部排序。

查找

和排序算法一樣,查找(searching)算法也是計算機科學(xué)中研究得最多的問題之一。查找算法和排序算法是有聯(lián)系的,因為許多查找算法依賴于要查找的數(shù)據(jù)集的有序程度。基本的查找算法有以下4種:

·順序查找(sequentialsearching)。

·比較查找(comparisonsearching)

·基數(shù)查找(radixsearching)

·哈希查找(hashing)

下面仍然以一付亂序的牌為例來描述這些算法的工作過程。

順序查找的過

溫馨提示

  • 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

提交評論