C語(yǔ)言中的常見(jiàn)問(wèn)題_第1頁(yè)
C語(yǔ)言中的常見(jiàn)問(wèn)題_第2頁(yè)
C語(yǔ)言中的常見(jiàn)問(wèn)題_第3頁(yè)
C語(yǔ)言中的常見(jiàn)問(wèn)題_第4頁(yè)
C語(yǔ)言中的常見(jiàn)問(wèn)題_第5頁(yè)
已閱讀5頁(yè),還剩142頁(yè)未讀, 繼續(xù)免費(fèi)閱讀

下載本文檔

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

文檔簡(jiǎn)介

第1章

C語(yǔ)言

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

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

1.1

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

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

#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

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

請(qǐng)參見(jiàn):

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

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

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

請(qǐng)參見(jiàn):

1.1什么是局部程序塊?1.3

什么時(shí)候用一條switch語(yǔ)句比用多條if語(yǔ)句更好?

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

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語(yǔ)句的前提是條件表達(dá)式必須基于同一個(gè)數(shù)字型變量。例如,盡管下述if語(yǔ)句包含兩個(gè)以上的條件,但該例不能使用switch語(yǔ)句,因?yàn)樵摾谧址容^,而不是數(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");

請(qǐng)參見(jiàn):

1.4switch語(yǔ)句必須包含default分支嗎7

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

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

switch(char_code)

{

casetyt:

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

break

case'N':

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

break

}

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

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

break

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

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

}

}

請(qǐng)參見(jiàn):

1.3什么時(shí)候用一條switch語(yǔ)句比用多條if語(yǔ)句更好?

1.5Switch語(yǔ)句的最后一個(gè)分支可以不要break語(yǔ)句嗎?1.5switch語(yǔ)句的最后一個(gè)分支可以不要break語(yǔ)句嗎?

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

請(qǐng)參見(jiàn):

1.3什么時(shí)候用一條switch語(yǔ)句比用多條if語(yǔ)句更好?

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

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

#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")

}

請(qǐng)注意下述語(yǔ)句:

i:(j++,k++)

這條語(yǔ)句一次完成了三個(gè)動(dòng)作,依次為:

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

(2)j自增。

(3)k自增。

此外,還要注意看上去有點(diǎn)奇怪的while語(yǔ)句:

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

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

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

請(qǐng)參見(jiàn):

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

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

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

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

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ì)循環(huán)512次。緊接著循環(huán)的if語(yǔ)句用來(lái)測(cè)試循環(huán)次數(shù),從而判斷循環(huán)是否提前結(jié)束。如果變量x的值小于512,就說(shuō)明循環(huán)出錯(cuò)了。

1.8

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

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

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

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

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

#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á)式。左值位于賦值語(yǔ)句的左側(cè),與其相對(duì)的右值(rvaule,見(jiàn)1.11)則位于賦值語(yǔ)句的右側(cè)。每條賦值語(yǔ)句都必須有一個(gè)左值和一個(gè)右值。左值必須是內(nèi)存中一個(gè)可存儲(chǔ)的變量,而不能是一個(gè)常量。下面給出了一些左值的例子:

intx;

int*p_int;

x=1;

p_int=5;

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

#defineCONST_VAL10

intx

/*example1*/

l=x;

/*example2*/

CONST_VAL=5;

在上述兩條語(yǔ)句中,語(yǔ)句的左側(cè)都是一個(gè)常量,其值不能改變,因?yàn)槌A坎槐硎緝?nèi)存中可

存儲(chǔ)的位置。因此,這兩條賦值語(yǔ)句中沒(méi)有左值,編譯程序會(huì)指出它們是錯(cuò)誤的。請(qǐng)參見(jiàn):

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

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

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

intx[5],y[5];

x=y;

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

inti;

intx[5];

inty[5];

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

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

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

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

與數(shù)組不同,結(jié)構(gòu)(structure)可以作為左值。你可以把一個(gè)結(jié)構(gòu)變量賦給另一個(gè)同類(lèi)型的結(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的全部?jī)?nèi)容被拷貝到結(jié)構(gòu)變量your_name中,其作用和下述語(yǔ)句是相同的:

memcpy(your_name,my_name,sizeof(your_name);

請(qǐng)參見(jiàn):

1.9

什么是左值(lvaule)?

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

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

intX,y;

x=1;

/*

1iSanrvalue,

xisanlvalue

*/

y=(x+1);

/*

(x+1)isanrvalue;yisanlvalue

*/

在1.9中已經(jīng)介紹過(guò),一條賦值語(yǔ)句必須有一個(gè)左值和一個(gè)右值,因此,下述語(yǔ)句無(wú)法通過(guò)編譯,因?yàn)樗鄙僖粋€(gè)右值:

intx;

x=void_function_call();

/*the{unctionvoid—function—call()

returnsnothing*/

如果上例中的函數(shù)返回一個(gè)整數(shù),那么它可以被看作一個(gè)右值,因?yàn)樗姆祷刂悼梢源鎯?chǔ)

到左值x中。

請(qǐng)參見(jiàn):

1.9

什么是左值(lvaule)?

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

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

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

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

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

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

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

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

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

例如,請(qǐng)看一個(gè)使用后綴自增運(yùn)算符的例子:

intx,y;

x=1;

y=(x++*5);

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

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

intx,y;

x=1;

y=(++x*5);

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

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

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

x=15/7;

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

X=15%7;

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

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

intx;

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

if((x%3)==0)

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

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

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

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

作用域有三種:

1.extern(外部的)

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

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

在函數(shù)外部說(shuō)明為static的變量的作用域?yàn)閺亩x點(diǎn)到該文件尾部;在函數(shù)內(nèi)部說(shuō)明為static的變量的作用域?yàn)閺亩x點(diǎn)到該局部程序塊尾部。

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

這是在函數(shù)內(nèi)部說(shuō)明的變量的缺省存儲(chǔ)方式。auto變量的作用域?yàn)閺亩x點(diǎn)到該局部程序塊尾部。

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

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

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

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

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

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

請(qǐng)參見(jiàn):

1.1

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

2.2

變量必須初始化嗎?

2.3

什么是頁(yè)抖動(dòng)(pagethrashing)?

7.20什么是棧(stack)?

7.21什么是堆(heap)7

.2.2

變量必須初始化嗎?

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

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

請(qǐng)參見(jiàn):

1.1

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

7.20什么是棧(stack)?

7.21什么是堆(heap)?2.3

什么是頁(yè)抖動(dòng)(pagethrashing)?

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

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

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

希望你繼續(xù)讀下去,因?yàn)橄旅婢鸵榻B其中的難點(diǎn)了。存取磁盤(pán)比存取RAM要慢得多,所以操作系統(tǒng)會(huì)試圖在RAM中保持盡量多的虛擬內(nèi)存。如果你在運(yùn)行一個(gè)非常大的程序(或者同時(shí)運(yùn)行幾個(gè)小程序),那么可能沒(méi)有足夠的RAM來(lái)承擔(dān)程序要使用的全部?jī)?nèi)存,因此必須把一些頁(yè)從RAM中移到磁盤(pán)上(這被為pagingout,即頁(yè)出)。

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

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

請(qǐng)參見(jiàn):

7.17

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

7.21

什么是堆(heap)?

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

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

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

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

constchar*str="hello";

charc=*str;

/*legal*/

str++;

/*legal*/

*str='a';

/*illegal*/

str[1]='b';

/*illegal*/

前兩條語(yǔ)句是合法的,因?yàn)樗鼈儧](méi)有修改str所指向的數(shù)據(jù);后兩條語(yǔ)句是非法的,因?yàn)樗鼈円薷膕tr所指向的數(shù)據(jù)。

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

my_strlen(constchar*str)

{

intcount=0;

while(*str++)

{

count++;

}

return

count;

}

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

2.7

一個(gè)變量可以同時(shí)被說(shuō)明為const和volatile嗎?

2.8

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

2.14什么時(shí)候不應(yīng)該使用類(lèi)型強(qiáng)制轉(zhuǎn)換(typecast)?

2.18用const說(shuō)明常量有什么好處?2.5

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

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

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

其次,因?yàn)閞egister變量可能不存放在內(nèi)存中,所以不能用取址運(yùn)算符“&”來(lái)獲取register變量的地址。如果你試圖這樣做,編譯程序就會(huì)報(bào)告這是一個(gè)錯(cuò)誤。

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

在某些情況下,把變量保存在寄存器中反而會(huì)降低運(yùn)行速度,因?yàn)楸徽加玫募拇嫫鞑荒茉儆糜谄渌康?,或—者變量被使用的次?shù)不夠多,不足以抵消裝入和存儲(chǔ)變量所帶來(lái)的額外開(kāi)銷(xiāo)。

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

實(shí)際上,許多C編譯程序會(huì)忽略register修飾符,因?yàn)楸M管它完全合法,但它僅僅是暗示而不是命令。

在極罕見(jiàn)的情況下,程序運(yùn)行速度很慢,而你也知道這是因?yàn)橛幸粋€(gè)變量被存儲(chǔ)在內(nèi)存中,也許你最后會(huì)試圖在該變量前面加上register修飾符,但是,如果這并沒(méi)有加快程序的運(yùn)行速度,你也不要感到奇怪。

請(qǐng)參見(jiàn):

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

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

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

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

如果一個(gè)指針變量指向普通內(nèi)存以外的位置,如指向一個(gè)外圍設(shè)備的內(nèi)存映射端口,那么冗余裝入和存儲(chǔ)的優(yōu)化對(duì)它來(lái)說(shuō)可能是有害的。例如,為了調(diào)整某個(gè)操作的時(shí)間,可能會(huì)用到下述函數(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í)際上是一個(gè)硬件計(jì)數(shù)器,其值隨時(shí)間增加。該函數(shù)執(zhí)行1000次把a(bǔ)值加到x上的操作,然后返回t->value在這1000次加法的執(zhí)行期間所增加的值。

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

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

請(qǐng)參見(jiàn):

2.7

一個(gè)變量可以同時(shí)被說(shuō)明為const和volatile嗎?

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

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

請(qǐng)參見(jiàn):

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

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

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

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

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

{

/*somecodehere*/

strq++;

}

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

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

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

請(qǐng)參見(jiàn):

2.7

一個(gè)變量可以同時(shí)被說(shuō)明為const和volatile嗎?

2.14什么時(shí)候不應(yīng)該使用類(lèi)型強(qiáng)制轉(zhuǎn)換(typecast)?

2.18用const說(shuō)明常量有什么好處?

2.9

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

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

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

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

#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可能會(huì)從稍小于last的一個(gè)數(shù)增加到一個(gè)稍大于last的數(shù)),這樣,循環(huán)會(huì)跳過(guò)last。加入不等式"f<last+1.0"就是為了防止在這種情況發(fā)生后程序繼續(xù)運(yùn)行很長(zhǎng)時(shí)間。如果運(yùn)行該程序并且被打印出來(lái)的f值是71或更大的數(shù)值,就說(shuō)明已經(jīng)發(fā)生了這種情況。

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

float

f;

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

;

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

float

f;

int

count=(last-first)/small;

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

;

請(qǐng)參見(jiàn):

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

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

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

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)制最大值

對(duì)于整數(shù)類(lèi)型,在使用2的補(bǔ)碼運(yùn)算的機(jī)器(你將使用的機(jī)器幾乎都屬此類(lèi))上,一個(gè)有符號(hào)類(lèi)型可以容納的數(shù)字范圍為-2位數(shù)-1到(+2位數(shù)-1-1),一個(gè)無(wú)符號(hào)類(lèi)型可以容納的數(shù)字范圍為0到(+2位數(shù)-1)。例如,一個(gè)16位有符號(hào)整數(shù)可以容納的數(shù)字范圍為--215(即-32768)到(+215-1)(即+32767)。請(qǐng)參見(jiàn):

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

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

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

對(duì)不同類(lèi)型的變量進(jìn)行算術(shù)運(yùn)算會(huì)有問(wèn)題嗎?

C有三類(lèi)固有的數(shù)據(jù)類(lèi)型:指針類(lèi)型、整數(shù)類(lèi)型和浮點(diǎn)類(lèi)型;

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

-

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

+

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

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

+

-

*

/

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

%

取?;蚯笥?/p>

>>

右移

<<

左移

&

按位與

|

按位或

^

按位異或

!

邏輯非

~

取反

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

請(qǐng)參見(jiàn):

2.12什么是運(yùn)算符升級(jí)(operatorpromotion)?

2.12什么是運(yùn)算符升級(jí)(operatorpromotion)?

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

運(yùn)算分量1

運(yùn)算分量2

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

longdouble

其它任何類(lèi)型

longdouble

double

任何更小的類(lèi)型

double

float

任何更小的類(lèi)

float

unsignedlong

任何整數(shù)類(lèi)

unsignedlong

long

unsigned>LONG_MAX

unsignedlong

long

任何更小的類(lèi)型

long

unsigned

任何有符號(hào)類(lèi)型

unsigned

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

#include<stdio.h>

main()

{

floatf1=3/4;

floatf2=3/4.0

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

}

請(qǐng)參見(jiàn):

2.11對(duì)不同類(lèi)型的變量進(jìn)行算術(shù)運(yùn)算會(huì)有問(wèn)題嗎?

2.13什么時(shí)候應(yīng)該使用類(lèi)型強(qiáng)制轉(zhuǎn)換(typecast)?

2.13什么時(shí)候應(yīng)該使用類(lèi)型強(qiáng)制轉(zhuǎn)換(typecast)?

在兩種情況下需要使用類(lèi)型強(qiáng)制轉(zhuǎn)換。第一種情況是改變運(yùn)算分量的類(lèi)型,從而使運(yùn)算能正確地進(jìn)行。下面的程序與2.12中的例子相似,但有不同之處。變量n被賦值為整數(shù)i除以整數(shù)j的結(jié)果,因?yàn)槭钦麛?shù)相除,所以結(jié)果為0。變量f2也被賦值為i除以j的結(jié)果,但本例通過(guò)(float)類(lèi)型強(qiáng)制轉(zhuǎn)換把i轉(zhuǎn)換成一個(gè)float類(lèi)型,因此執(zhí)行的是浮點(diǎn)數(shù)除法運(yùn)算(見(jiàn)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);

}

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

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

請(qǐng)參見(jiàn):

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

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

2.11對(duì)不同類(lèi)型的變量進(jìn)行算術(shù)運(yùn)算會(huì)有問(wèn)題嗎?

2.12什么是運(yùn)算符升級(jí)(operatorpromotion)?

2.14什么時(shí)候不應(yīng)該使用類(lèi)型強(qiáng)制轉(zhuǎn)換(typecast)?

7.5

什么是void指針?

7.6

什么時(shí)候使用void指針?

7.21什么是堆(heap)?

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

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

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

請(qǐng)參見(jiàn):

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

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

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

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

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

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

請(qǐng)參見(jiàn):

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

2.17可以在頭文件中說(shuō)明static變量嗎?

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

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

externintdecll;

/*thisisadeclaration*/

structdecl2{

intmember;

};

/*thisjustdeclaresthetype--novariablementioned*/

intdef1=8;

/*thisisadefinition*/

intdef2;

/*thisisadefinition*/

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

一個(gè)變量可以被說(shuō)明許多次,但只能被定義一次。因此,不應(yīng)該在頭文件中定義變量,因?yàn)橐粋€(gè)頭文件可能會(huì)被一個(gè)程序的許多源文件所包含。

請(qǐng)參見(jiàn);

2.17可以在頭文件中說(shuō)明static變量嗎?2.17

可以在頭文件中說(shuō)明static變量嗎?

如果說(shuō)明了一個(gè)static變量,就必須在同一個(gè)文件中定義該變量(因?yàn)榇鎯?chǔ)類(lèi)型修飾符static和extern是互斥的)。你可以在頭文件中定義一個(gè)static變量,但這會(huì)使包含該頭文件的源文件都得到該變量的一份私有拷貝,而這通常不是你想得到的結(jié)果。

請(qǐng)參見(jiàn):

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

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

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

請(qǐng)參見(jiàn):

2.7

一個(gè)變量可以同時(shí)被說(shuō)明為const和volatile嗎?

2.8

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

2.14什么時(shí)候不應(yīng)該使用類(lèi)型強(qiáng)制轉(zhuǎn)換(typecast)?

9.9

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

排序與查找

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

排序

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

·插入排序(insertionsort.)

·交換排序(exchangesOrt)

·選擇排序(selectionsort)

·歸并排序(mergesort)

·分布排序(distributionsort)

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

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

交換排序的過(guò)程為:

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

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

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

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

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

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

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

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

(1)自然的(natural)

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

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

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

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

MaryJones

MarySmith

TomJones

SusieQueue

如果用穩(wěn)定的排序算法按姓對(duì)上述名單進(jìn)行排序,那么在排好序后"MaryJones”和"TomJones”將保持原來(lái)的Jr順序,因?yàn)樗鼈兊男帐窍嗤摹?/p>

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

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

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

查找

和排序算法一樣,查找(searching)算法也是計(jì)算機(jī)科學(xué)中研究得最多的問(wèn)題之一。查找算法和排序算法是有聯(lián)系的,因?yàn)樵S多查找算法依賴(lài)于要查找的數(shù)據(jù)集的有序程度?;镜牟檎宜惴ㄓ幸韵?種:

·順序查找(sequentialsearching)。

·比較查找(comparisonsearching)

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

·哈希查找(hashing)

下面仍然以一付亂序的牌為例來(lái)描述這些算法的工作過(guò)程。

順序查找的過(guò)

溫馨提示

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

評(píng)論

0/150

提交評(píng)論