C語言測試是招聘嵌入式系統(tǒng)程序員過程中必須而且有效的方法。這些年,我既參加也組織了許多這種測試,在這過程中我意識到這些測試能為面試者和被面試者提供許多有用信息,此外,撇開面試的壓力不談,這種測試也是相當(dāng)有趣的。
從被面試者的角度來講,你能了解許多關(guān)于出題者或監(jiān)考者的情況。這個測試只是出題者為顯示其對ANSI標(biāo)準(zhǔn)細(xì)節(jié)的知識而不是技術(shù)技巧而設(shè)計(jì)嗎?這是個愚蠢的問題嗎?如要你答出某個字符的ASCII值。這些問題著重考察你的系統(tǒng)調(diào)用和內(nèi)存分配策略方面的能力嗎?這標(biāo)志著出題者也許花時間在微機(jī)上而不是在嵌入式系統(tǒng)上。如果上述任何問題的答案是"是"的話,那么我知道我得認(rèn)真考慮我是否應(yīng)該去做這份工作。
從面試者的角度來講,一個測試也許能從多方面揭示應(yīng)試者的素質(zhì):最基本的,你能了解應(yīng)試者C語言的水平。不管怎么樣,看一下這人如何回答他不會的問題也是滿有趣。應(yīng)試者是以好的直覺做出明智的選擇,還是只是瞎蒙呢?當(dāng)應(yīng)試者在某個問題上卡住時是找借口呢,還是表現(xiàn)出對問題的真正的好奇心,把這看成學(xué)習(xí)的機(jī)會呢?我發(fā)現(xiàn)這些信息與他們的測試成績一樣有用。
有了這些想法,我決定出一些真正針對嵌入式系統(tǒng)的考題,希望這些令人頭痛的考題能給正在找工作的人一點(diǎn)幫助。這些問題都是我這些年實(shí)際碰到的。其中有些題很難,但它們應(yīng)該都能給你一點(diǎn)啟迪。
這個測試適于不同水平的應(yīng)試者,大多數(shù)初級水平的應(yīng)試者的成績會很差,經(jīng)驗(yàn)豐富的程序員應(yīng)該有很好的成績。為了讓你能自己決定某些問題的偏好,每個問題沒有分配分?jǐn)?shù),如果選擇這些考題為你所用,請自行按你的意思分配分?jǐn)?shù)。
預(yù)處理器(Preprocessor)
1 . 用預(yù)處理指令#define 聲明一個常數(shù),用以表明1年中有多少秒(忽略閏年問題)
#define SECONDS_PER_YEAR (60 * 60 * 24 * 365)UL
我在這想看到幾件事情:
1) #define 語法的基本知識(例如:不能以分號結(jié)束,括號的使用,等等)
2)懂得預(yù)處理器將為你計(jì)算常數(shù)表達(dá)式的值,因此,直接寫出你是如何計(jì)算一年中有多少秒而不是計(jì)算出實(shí)際的值,是更清晰而沒有代價的。
3) 意識到這個表達(dá)式將使一個16位機(jī)的整型數(shù)溢出-因此要用到長整型符號L,告訴編譯器這個常數(shù)是的長整型數(shù)。
4) 如果你在你的表達(dá)式中用到UL(表示無符號長整型),那么你有了一個好的起點(diǎn)。記住,第一印象很重要。
2 . 寫一個"標(biāo)準(zhǔn)"宏MIN ,這個宏輸入兩個參數(shù)并返回較小的一個。
#define MIN(A,B) ((A) <= (B) ? (A) : (B))
這個測試是為下面的目的而設(shè)的:
1) 標(biāo)識#define在宏中應(yīng)用的基本知識。這是很重要的。因?yàn)樵?嵌入(inline)操作符 變?yōu)闃?biāo)準(zhǔn)C的一部分之前,宏是方便產(chǎn)生嵌入代碼的唯一方法,對于嵌入式系統(tǒng)來說,為了能達(dá)到要求的性能,嵌入代碼經(jīng)常是必須的方法。
2)三重條件操作符的知識。這個操作符存在C語言中的原因是它使得編譯器能產(chǎn)生比if-then-else更優(yōu)化的代碼,了解這個用法是很重要的。
3) 懂得在宏中小心地把參數(shù)用括號括起來
4) 我也用這個問題開始討論宏的副作用,例如:當(dāng)你寫下面的代碼時會發(fā)生什么事?
least = MIN(*p++, b);
3. 預(yù)處理器標(biāo)識#error的目的是什么?
如果你不知道答案,請看參考文獻(xiàn)1。這問題對區(qū)分一個正常的伙計(jì)和一個書呆子是很有用的。只有書呆子才會讀C語言課本的附錄去找出象這種問題的答案。當(dāng)然如果你不是在找一個書呆子,那么應(yīng)試者最好希望自己不要知道答案。
死循環(huán)(Infinite loops)
4. 嵌入式系統(tǒng)中經(jīng)常要用到無限循環(huán),你怎么樣用C編寫死循環(huán)呢?
這個問題用幾個解決方案。我首選的方案是:
while(1)
{
}
一些程序員更喜歡如下方案:
for(;;)
{
}
這個實(shí)現(xiàn)方式讓我為難,因?yàn)檫@個語法沒有確切表達(dá)到底怎么回事。如果一個應(yīng)試者給出這個作為方案,我將用這個作為一個機(jī)會去探究他們這樣做的基本原理。如果他們的基本答案是:"我被教著這樣做,但從沒有想到過為什么。"這會給我留下一個壞印象。
第三個方案是用 goto
Loop:
...
goto Loop;
應(yīng)試者如給出上面的方案,這說明或者他是一個匯編語言程序員(這也許是好事)或者他是一個想進(jìn)入新領(lǐng)域的BASIC/FORTRAN程序員。
數(shù)據(jù)聲明(Data declarations)
5. 用變量a給出下面的定義
a) 一個整型數(shù)(An integer)
b)一個指向整型數(shù)的指針( A pointer to an integer)
c)一個指向指針的的指針,它指向的指針是指向一個整型數(shù)( A pointer to a pointer to an intege)r
d)一個有10個整型數(shù)的數(shù)組( An array of 10 integers)
e) 一個有10個指針的數(shù)組,該指針是指向一個整型數(shù)的。(An array of 10 pointers to integers)
f) 一個指向有10個整型數(shù)數(shù)組的指針( A pointer to an array of 10 integers)
g) 一個指向函數(shù)的指針,該函數(shù)有一個整型參數(shù)并返回一個整型數(shù)(A pointer to a function that takes an integer as an argument and returns an integer)
h) 一個有10個指針的數(shù)組,該指針指向一個函數(shù),該函數(shù)有一個整型參數(shù)并返回一個整型數(shù)( An array of ten pointers to functions that take an integer argument and return an integer )
答案是:
a) int a; // An integer
b) int *a; // A pointer to an integer
c) int **a; // A pointer to a pointer to an integer
d) int a[10]; // An array of 10 integers
e) int *a[10]; // An array of 10 pointers to integers
f) int (*a)[10]; // A pointer to an array of 10 integers
g) int (*a)(int); // A pointer to a function a that takes an integer argument and returns an integer
h) int (*a[10])(int); // An array of 10 pointers to functions that take an integer argument and return an integer
人們經(jīng)常聲稱這里有幾個問題是那種要翻一下書才能回答的問題,我同意這種說法。當(dāng)我寫這篇文章時,為了確定語法的正確性,我的確查了一下書。但是當(dāng)我被面試的時候,我期望被問到這個問題(或者相近的問題)。因?yàn)樵诒幻嬖嚨倪@段時間里,我確定我知道這個問題的答案。應(yīng)試者如果不知道所有的答案(或至少大部分答案),那么也就沒有為這次面試做準(zhǔn)備,如果該面試者沒有為這次面試做準(zhǔn)備,那么他又能為什么做出準(zhǔn)備呢?
Static
6. 關(guān)鍵字static的作用是什么?
這個簡單的問題很少有人能回答完全。在C語言中,關(guān)鍵字static有三個明顯的作用:
1)在函數(shù)體,一個被聲明為靜態(tài)的變量在這一函數(shù)被調(diào)用過程中維持其值不變。
2) 在模塊內(nèi)(但在函數(shù)體外),一個被聲明為靜態(tài)的變量可以被模塊內(nèi)所用函數(shù)訪問,但不能被模塊外其它函數(shù)訪問。它是一個本地的全局變量。
3) 在模塊內(nèi),一個被聲明為靜態(tài)的函數(shù)只可被這一模塊內(nèi)的其它函數(shù)調(diào)用。那就是,這個函數(shù)被限制在聲明它的模塊的本地范圍內(nèi)使用。
大多數(shù)應(yīng)試者能正確回答第一部分,一部分能正確回答第二部分,同是很少的人能懂得第三部分。這是一個應(yīng)試者的嚴(yán)重的缺點(diǎn),因?yàn)樗@然不懂得本地化數(shù)據(jù)和代碼范圍的好處和重要性。
Const
7.關(guān)鍵字const有什么含意?
我只要一聽到被面試者說:"const意味著常數(shù)",我就知道我正在和一個業(yè)余者打交道。去年Dan Saks已經(jīng)在他的文章里完全概括了const的所有用法,因此ESP(譯者:Embedded Systems Programming)的每一位讀者應(yīng)該非常熟悉const能做什么和不能做什么.如果你從沒有讀到那篇文章,只要能說出const意味著"只讀"就可以了。盡管這個答案不是完全的答案,但我接受它作為一個正確的答案。(如果你想知道更詳細(xì)的答案,仔細(xì)讀一下Saks的文章吧。)
如果應(yīng)試者能正確回答這個問題,我將問他一個附加的問題:
下面的聲明都是什么意思?
const int a;
int const a;
const int *a;
int * const a;
int const * a const;
/******/
前兩個的作用是一樣,a是一個常整型數(shù)。第三個意味著a是一個指向常整型數(shù)的指針(也就是,整型數(shù)是不可修改的,但指針可以)。第四個意思a是一個指向整型數(shù)的常指針(也就是說,指針指向的整型數(shù)是可以修改的,但指針是不可修改的)。最后一個意味著a是一個指向常整型數(shù)的常指針(也就是說,指針指向的整型數(shù)是不可修改的,同時指針也是不可修改的)。如果應(yīng)試者能正確回答這些問題,那么他就給我留下了一個好印象。順帶提一句,也許你可能會問,即使不用關(guān)鍵字 const,也還是能很容易寫出功能正確的程序,那么我為什么還要如此看重關(guān)鍵字const呢?我也如下的幾下理由:
1) 關(guān)鍵字const的作用是為給讀你代碼的人傳達(dá)非常有用的信息,實(shí)際上,聲明一個參數(shù)為常量是為了告訴了用戶這個參數(shù)的應(yīng)用目的。如果你曾花很多時間清理其它人留下的垃圾,你就會很快學(xué)會感謝這點(diǎn)多余的信息。(當(dāng)然,懂得用const的程序員很少會留下的垃圾讓別人來清理的。)
2) 通過給優(yōu)化器一些附加的信息,使用關(guān)鍵字const也許能產(chǎn)生更緊湊的代碼。
3) 合理地使用關(guān)鍵字const可以使編譯器很自然地保護(hù)那些不希望被改變的參數(shù),防止其被無意的代碼修改。簡而言之,這樣可以減少bug的出現(xiàn)。
Volatile
8. 關(guān)鍵字volatile有什么含意?并給出三個不同的例子。
一個定義為volatile的變量是說這變量可能會被意想不到地改變,這樣,編譯器就不會去假設(shè)這個變量的值了。精確地說就是,優(yōu)化器在用到這個變量時必須每次都小心地重新讀取這個變量的值,而不是使用保存在寄存器里的備份。下面是volatile變量的幾個例子:
1) 并行設(shè)備的硬件寄存器(如:狀態(tài)寄存器)
2) 一個中斷服務(wù)子程序中會訪問到的非自動變量(Non-automatic variables)
3) 多線程應(yīng)用中被幾個任務(wù)共享的變量
回答不出這個問題的人是不會被雇傭的。我認(rèn)為這是區(qū)分C程序員和嵌入式系統(tǒng)程序員的最基本的問題。搞嵌入式的家伙們經(jīng)常同硬件、中斷、RTOS等等打交道,所有這些都要求用到volatile變量。不懂得volatile的內(nèi)容將會帶來災(zāi)難。
假設(shè)被面試者正確地回答了這是問題(嗯,懷疑是否會是這樣),我將稍微深究一下,看一下這家伙是不是直正懂得volatile完全的重要性。
1)一個參數(shù)既可以是const還可以是volatile嗎?解釋為什么。
2); 一個指針可以是volatile 嗎?解釋為什么。
3); 下面的函數(shù)有什么錯誤:
int square(volatile int *ptr)
{
return *ptr * *ptr;
}
下面是答案:
1)是的。一個例子是只讀的狀態(tài)寄存器。它是volatile因?yàn)樗赡鼙灰庀氩坏降馗淖?。它是const因?yàn)槌绦虿粦?yīng)該試圖去修改它。
2); 是的。盡管這并不很常見。一個例子是當(dāng)一個中服務(wù)子程序修該一個指向一個buffer的指針時。
3) 這段代碼有點(diǎn)變態(tài)。這段代碼的目的是用來返指針*ptr指向值的平方,但是,由于*ptr指向一個volatile型參數(shù),編譯器將產(chǎn)生類似下面的代碼:
int square(volatile int *ptr)
{
int a,b;
a = *ptr;
b = *ptr;
return a * b;
}
由于*ptr的值可能被意想不到地該變,因此a和b可能是不同的。結(jié)果,這段代碼可能返不是你所期望的平方值!正確的代碼如下:
long square(volatile int *ptr)
{
int a;
a = *ptr;
return a * a;
}
位操作(Bit manipulation)
9. 嵌入式系統(tǒng)總是要用戶對變量或寄存器進(jìn)行位操作。給定一個整型變量a,寫兩段代碼,第一個設(shè)置a的bit 3,第二個清除a 的bit 3。在以上兩個操作中,要保持其它位不變。
對這個問題有三種基本的反應(yīng)
1)不知道如何下手。該被面者從沒做過任何嵌入式系統(tǒng)的工作。
2) 用bit fields。Bit fields是被扔到C語言死角的東西,它保證你的代碼在不同編譯器之間是不可移植的,同時也保證了的你的代碼是不可重用的。我最近不幸看到 Infineon為其較復(fù)雜的通信芯片寫的驅(qū)動程序,它用到了bit fields因此完全對我無用,因?yàn)槲业木幾g器用其它的方式來實(shí)現(xiàn)bit fields的。從道德講:永遠(yuǎn)不要讓一個非嵌入式的家伙粘實(shí)際硬件的邊。
3) 用 #defines 和 bit masks 操作。這是一個有極高可移植性的方法,是應(yīng)該被用到的方法。最佳的解決方案如下:
#define BIT3 (0x1 << 3)
static int a;
void set_bit3(void)
{
a |= BIT3;
}
void clear_bit3(void)
{
a &= ~BIT3;
}
一些人喜歡為設(shè)置和清除值而定義一個掩碼同時定義一些說明常數(shù),這也是可以接受的。我希望看到幾個要點(diǎn):說明常數(shù)、|=和&=~操作。
訪問固定的內(nèi)存位置(Accessing fixed memory locations)
10. 嵌入式系統(tǒng)經(jīng)常具有要求程序員去訪問某特定的內(nèi)存位置的特點(diǎn)。在某工程中,要求設(shè)置一絕對地址為0x67a9的整型變量的值為0xaa66。編譯器是一個純粹的ANSI編譯器。寫代碼去完成這一任務(wù)。
這一問題測試你是否知道為了訪問一絕對地址把一個整型數(shù)強(qiáng)制轉(zhuǎn)換(typecast)為一指針是合法的。這一問題的實(shí)現(xiàn)方式隨著個人風(fēng)格不同而不同。典型的類似代碼如下:
int *ptr;
ptr = (int *)0x67a9;
*ptr = 0xaa55;
A more obscure approach is:
一個較晦澀的方法是:
*(int * const)(0x67a9) = 0xaa55;
即使你的品味更接近第二種方案,但我建議你在面試時使用第一種方案。
中斷(Interrupts)
11. 中斷是嵌入式系統(tǒng)中重要的組成部分,這導(dǎo)致了很多編譯開發(fā)商提供一種擴(kuò)展—讓標(biāo)準(zhǔn)C支持中斷。具代表事實(shí)是,產(chǎn)生了一個新的關(guān)鍵字 __interrupt。下面的代碼就使用了__interrupt關(guān)鍵字去定義了一個中斷服務(wù)子程序(ISR),請?jiān)u論一下這段代碼的。
__interrupt double compute_area (double radius)
{
double area = PI * radius * radius;
printf("nArea = %f", area);
return area;
}
這個函數(shù)有太多的錯誤了,以至讓人不知從何說起了:
1)ISR 不能返回一個值。如果你不懂這個,那么你不會被雇用的。
2) ISR 不能傳遞參數(shù)。如果你沒有看到這一點(diǎn),你被雇用的機(jī)會等同第一項(xiàng)。
3) 在許多的處理器/編譯器中,浮點(diǎn)一般都是不可重入的。有些處理器/編譯器需要讓額處的寄存器入棧,有些處理器/編譯器就是不允許在ISR中做浮點(diǎn)運(yùn)算。此外,ISR應(yīng)該是短而有效率的,在ISR中做浮點(diǎn)運(yùn)算是不明智的。
4) 與第三點(diǎn)一脈相承,printf()經(jīng)常有重入和性能上的問題。如果你丟掉了第三和第四點(diǎn),我不會太為難你的。不用說,如果你能得到后兩點(diǎn),那么你的被雇用前景越來越光明了。
代碼例子(Code examples)
12 . 下面的代碼輸出是什么,為什么?
void foo(void)
{
unsigned int a = 6;
int b = -20;
(a+b > 6) ? puts("> 6") : puts("<= 6");
}
這個問題測試你是否懂得C語言中的整數(shù)自動轉(zhuǎn)換原則,我發(fā)現(xiàn)有些開發(fā)者懂得極少這些東西。不管如何,這無符號整型問題的答案是輸出是 ">6"。原因是當(dāng)表達(dá)式中存在有符號類型和無符號類型時所有的操作數(shù)都自動轉(zhuǎn)換為無符號類型。因此-20變成了一個非常大的正整數(shù),所以該表達(dá)式計(jì)算出的結(jié)果大于6。這一點(diǎn)對于應(yīng)當(dāng)頻繁用到無符號數(shù)據(jù)類型的嵌入式系統(tǒng)來說是豐常重要的。如果你答錯了這個問題,你也就到了得不到這份工作的邊緣。
13. 評價下面的代碼片斷:
unsigned int zero = 0;
unsigned int compzero = 0xFFFF;
/*1's complement of zero */
對于一個int型不是16位的處理器為說,上面的代碼是不正確的。應(yīng)編寫如下:
unsigned int compzero = ~0;
這一問題真正能揭露出應(yīng)試者是否懂得處理器字長的重要性。在我的經(jīng)驗(yàn)里,好的嵌入式程序員非常準(zhǔn)確地明白硬件的細(xì)節(jié)和它的局限,然而PC機(jī)程序往往把硬件作為一個無法避免的煩惱。
到了這個階段,應(yīng)試者或者完全垂頭喪氣了或者信心滿滿志在必得。如果顯然應(yīng)試者不是很好,那么這個測試就在這里結(jié)束了。但如果顯然應(yīng)試者做得不錯,那么我就扔出下面的追加問題,這些問題是比較難的,我想僅僅非常優(yōu)秀的應(yīng)試者能做得不錯。提出這些問題,我希望更多看到應(yīng)試者應(yīng)付問題的方法,而不是答案。不管如何,你就當(dāng)是這個娛樂吧...
動態(tài)內(nèi)存分配(Dynamic memory allocation)
14. 盡管不像非嵌入式計(jì)算機(jī)那么常見,嵌入式系統(tǒng)還是有從堆(heap)中動態(tài)分配內(nèi)存的過程的。那么嵌入式系統(tǒng)中,動態(tài)分配內(nèi)存可能發(fā)生的問題是什么?
這里,我期望應(yīng)試者能提到內(nèi)存碎片,碎片收集的問題,變量的持行時間等等。這個主題已經(jīng)在ESP雜志中被廣泛地討論過了(主要是 P.J. Plauger, 他的解釋遠(yuǎn)遠(yuǎn)超過我這里能提到的任何解釋),所有回過頭看一下這些雜志吧!讓應(yīng)試者進(jìn)入一種虛假的安全感覺后,我拿出這么一個小節(jié)目:
下面的代碼片段的輸出是什么,為什么?
char *ptr;
if ((ptr = (char *)malloc(0)) == NULL)
puts("Got a null pointer");
else
puts("Got a valid pointer");
這是一個有趣的問題。最近在我的一個同事不經(jīng)意把0值傳給了函數(shù)malloc,得到了一個合法的指針之后,我才想到這個問題。這就是上面的代碼,該代碼的輸出是"Got a valid pointer"。我用這個來開始討論這樣的一問題,看看被面試者是否想到庫例程這樣做是正確。得到正確的答案固然重要,但解決問題的方法和你做決定的基本原理更重要些。
Typedef
15 Typedef 在C語言中頻繁用以聲明一個已經(jīng)存在的數(shù)據(jù)類型的同義字。也可以用預(yù)處理器做類似的事。例如,思考一下下面的例子:
#define dPS struct s *
typedef struct s * tPS;
以上兩種情況的意圖都是要定義dPS 和 tPS 作為一個指向結(jié)構(gòu)s指針。哪種方法更好呢?(如果有的話)為什么?這是一個非常微妙的問題,任何人答對這個問題(正當(dāng)?shù)脑颍┦菓?yīng)當(dāng)被恭喜的。答案是:typedef更好。思考下面的例子:
dPS p1,p2;
tPS p3,p4;
第一個擴(kuò)展為
struct s * p1, p2;
.
上面的代碼定義p1為一個指向結(jié)構(gòu)的指,p2為一個實(shí)際的結(jié)構(gòu),這也許不是你想要的。第二個例子正確地定義了p3 和p4 兩個指針。
晦澀的語法
16 . C語言同意一些令人震驚的結(jié)構(gòu),下面的結(jié)構(gòu)是合法的嗎,如果是它做些什么?
int a = 5, b = 7, c;
c = a+++b;
這個問題將做為這個測驗(yàn)的一個愉快的結(jié)尾。不管你相不相信,上面的例子是完全合乎語法的。問題是編譯器如何處理它?水平不高的編譯作者實(shí)際上會爭論這個問題,根據(jù)最處理原則,編譯器應(yīng)當(dāng)能處理盡可能所有合法的用法。因此,上面的代碼被處理成:
c = a++ + b;
因此, 這段代碼持行后a = 6, b = 7, c = 12。
如果你知道答案,或猜出正確答案,做得好。如果你不知道答案,我也不把這個當(dāng)作問題。我發(fā)現(xiàn)這個問題的最大好處是這是一個關(guān)于代碼編寫風(fēng)格,代碼的可讀性,代碼的可修改性的好的話題。
好了,伙計(jì)們,你現(xiàn)在已經(jīng)做完所有的測試了。這就是我出的C語言測試題,我懷著愉快的心情寫完它,希望你以同樣的心情讀完它。如果是認(rèn)為這是一個好的測試,那么盡量都用到你的找工作的過程中去吧。天知道也許過個一兩年,我就不做現(xiàn)在的工作,也需要找一個。
參考文獻(xiàn)
1) Jones, Nigel, "In Praise of the #error directive," Embedded Systems Programming, September 1999, p. 114.
2) Jones, Nigel, " Efficient C Code for Eight-bit MCUs ," Embedded Systems Programming, November 1998, p. 66.
如何優(yōu)化C語言代碼(程序員必讀)
AlexanderWu 發(fā)表于2006 2月, 11 12:37 [C資料庫]
1、選擇合適的算法和數(shù)據(jù)結(jié)構(gòu)
應(yīng)該熟悉算法語言,知道各種算法的優(yōu)缺點(diǎn),具體資料請參見相應(yīng)的參考資料,有
很多計(jì)算機(jī)書籍上都有介紹。將比較慢的順序查找法用較快的二分查找或亂序查找
法代替,插入排序或冒泡排序法用快速排序、合并排序或根排序代替,都可以大大
提高程序執(zhí)行的效率。.選擇一種合適的數(shù)據(jù)結(jié)構(gòu)也很重要,比如你在一堆隨機(jī)存
放的數(shù)中使用了大量的插入和刪除指令,那使用鏈表要快得多。
數(shù)組與指針語句具有十分密碼的關(guān)系,一般來說,指針比較靈活簡潔,而數(shù)組則比
較直觀,容易理解。對于大部分的編譯器,使用指針比使用數(shù)組生成的代碼更短,
執(zhí)行效率更高。但是在Keil中則相反,使用數(shù)組比使用的指針生成的代碼更短。。
3、使用盡量小的數(shù)據(jù)類型
能夠使用字符型(char)定義的變量,就不要使用整型(int)變量來定義;能夠使用
整型變量定義的變量就不要用長整型(long int),能不使用浮點(diǎn)型(float)變量就
不要使用浮點(diǎn)型變量。當(dāng)然,在定義變量后不要超過變量的作用范圍,如果超過變
量的范圍賦值,C編譯器并不報(bào)錯,但程序運(yùn)行結(jié)果卻錯了,而且這樣的錯誤很難
發(fā)現(xiàn)。
在ICCAVR中,可以在Options中設(shè)定使用printf參數(shù),盡量使用基本型參數(shù)(%c、
%d、%x、%X、%u和%s格式說明符),少用長整型參數(shù)(%ld、%lu、%lx和%lX格式說明
符),至于浮點(diǎn)型的參數(shù)(%f)則盡量不要使用,其它C編譯器也一樣。在其它條件不
變的情況下,使用%f參數(shù),會使生成的代碼的數(shù)量增加很多,執(zhí)行速度降低。
4、使用自加、自減指令
通常使用自加、自減指令和復(fù)合賦值表達(dá)式(如a-=1及a+=1等)都能夠生成高質(zhì)量的
程序代碼,編譯器通常都能夠生成inc和dec之類的指令,而使用a=a+1或a=a-1之類
的指令,有很多C編譯器都會生成二到三個字節(jié)的指令。在AVR單片適用的ICCAVR、
GCCAVR、IAR等C編譯器以上幾種書寫方式生成的代碼是一樣的,也能夠生成高質(zhì)量
的inc和dec之類的的代碼。
5、減少運(yùn)算的強(qiáng)度
可以使用運(yùn)算量小但功能相同的表達(dá)式替換原來復(fù)雜的的表達(dá)式。如下:
(1)、求余運(yùn)算。
a=a%8;
可以改為:
a=a&7;
說明:位操作只需一個指令周期即可完成,而大部分的C編譯器的“%”運(yùn)算均是調(diào)
用子程序來完成,代碼長、執(zhí)行速度慢。通常,只要求是求2n方的余數(shù),均可使用
位操作的方法來代替。
(2)、平方運(yùn)算
a=pow(a,2.0);
可以改為:
a=a*a;
說明:在有內(nèi)置硬件乘法器的單片機(jī)中(如51系列),乘法運(yùn)算比求平方運(yùn)算快得多
,因?yàn)楦↑c(diǎn)數(shù)的求平方是通過調(diào)用子程序來實(shí)現(xiàn)的,在自帶硬件乘法器的AVR單片
機(jī)中,如ATMega163中,乘法運(yùn)算只需2個時鐘周期就可以完成。既使是在沒有內(nèi)置
硬件乘法器的AVR單片機(jī)中,乘法運(yùn)算的子程序比平方運(yùn)算的子程序代碼短,執(zhí)行
速度快。
如果是求3次方,如:
a=pow(a,3.0);
更改為:
a=a*a*a;
則效率的改善更明顯。
(3)、用移位實(shí)現(xiàn)乘除法運(yùn)算
a=a*4;
b=b/4;
可以改為:
a=a<<2;
b=b>>2;
說明:通常如果需要乘以或除以2n,都可以用移位的方法代替。在ICCAVR中,如果
乘以2n,都可以生成左移的代碼,而乘以其它的整數(shù)或除以任何數(shù),均調(diào)用乘除法
子程序。用移位的方法得到代碼比調(diào)用乘除法子程序生成的代碼效率高。實(shí)際上,
只要是乘以或除以一個整數(shù),均可以用移位的方法得到結(jié)果,如:
a=a*9
可以改為:
a=(a<<3)+a
6、循環(huán)
(1)、循環(huán)語
對于一些不需要循環(huán)變量參加運(yùn)算的任務(wù)可以把它們放到循環(huán)外面,這里的任務(wù)包
括表達(dá)式、函數(shù)的調(diào)用、指針運(yùn)算、數(shù)組訪問等,應(yīng)該將沒有必要執(zhí)行多次的操作
全部集合在一起,放到一個init的初始化程序中進(jìn)行。
(2)、延時函數(shù):
通常使用的延時函數(shù)均采用自加的形式:
void delay (void)
{
unsigned int i;
for (i=0;i<1000;i++)
;
}
將其改為自減延時函數(shù):
void delay (void)
{
unsigned int i;
for (i=1000;i>0;i--)
;
}
兩個函數(shù)的延時效果相似,但幾乎所有的C編譯對后一種函數(shù)生成的代碼均比前一
種代碼少1~3個字節(jié),因?yàn)閹缀跛械腗CU均有為0轉(zhuǎn)移的指令,采用后一種方式能
夠生成這類指令。
在使用while循環(huán)時也一樣,使用自減指令控制循環(huán)會比使用自加指令控制循環(huán)生
成的代碼更少1~3個字母。
但是在循環(huán)中有通過循環(huán)變量“i”讀寫數(shù)組的指令時,使用預(yù)減循環(huán)時有可能使
數(shù)組超界,要引起注意。
(3)while循環(huán)和do…while循環(huán)
用while循環(huán)時有以下兩種循環(huán)形式:
unsigned int i;
i=0;
while (i<1000)
{
i++;
//用戶程序
}
或:
unsigned int i;
i=1000;
do
i--;
//用戶程序
while (i>0);
在這兩種循環(huán)中,使用do…while循環(huán)編譯后生成的代碼的長度短于while循環(huán)。
7、查表
在程序中一般不進(jìn)行非常復(fù)雜的運(yùn)算,如浮點(diǎn)數(shù)的乘除及開方等,以及一些復(fù)雜的
數(shù)學(xué)模型的插補(bǔ)運(yùn)算,對這些即消耗時間又消費(fèi)資源的運(yùn)算,應(yīng)盡量使用查表的方
式,并且將數(shù)據(jù)表置于程序存儲區(qū)。如果直接生成所需的表比較困難,也盡量在啟
了,減少了程序執(zhí)行過程中重復(fù)計(jì)算的工作量。
8、其它
比如使用在線匯編及將字符串和一些常量保存在程序存儲器中,均有利于優(yōu)化
-- C語言的文件操作
文件的基本概念
所謂“文件”是指一組相關(guān)數(shù)據(jù)的有序集合。 這個數(shù)據(jù)集有一個名稱,叫做文件名。 實(shí)際上在前面的各章中我們已經(jīng)多次使用了文件,例如源程序文件、目標(biāo)文件、可執(zhí)行文件、庫文件 (頭文件)等。文件通常是駐留在外部介質(zhì)(如磁盤等)上的, 在使用時才調(diào)入內(nèi)存中來。從不同的角度可對文件作不同的分類。從用戶的角度看,文件可分為普通文件和設(shè)備文件兩種。
普通文件是指駐留在磁盤或其它外部介質(zhì)上的一個有序數(shù)據(jù)集,可以是源文件、目標(biāo)文件、可執(zhí)行程序; 也可以是一組待輸入處理的原始數(shù)據(jù),或者是一組輸出的結(jié)果。對于源文件、目標(biāo)文件、 可執(zhí)行程序可以稱作程序文件,對輸入輸出數(shù)據(jù)可稱作數(shù)據(jù)文件。
設(shè)備文件是指與主機(jī)相聯(lián)的各種外部設(shè)備,如顯示器、打印機(jī)、鍵盤等。在操作系統(tǒng)中,把外部設(shè)備也看作是一個文件來進(jìn)行管理,把它們的輸入、輸出等同于對磁盤文件的讀和寫。 通常把顯示器定義為標(biāo)準(zhǔn)輸出文件, 一般情況下在屏幕上顯示有關(guān)信息就是向標(biāo)準(zhǔn)輸出文件輸出。如前面經(jīng)常使用的printf,putchar 函數(shù)就是這類輸出。鍵盤通常被指定標(biāo)準(zhǔn)的輸入文件, 從鍵盤上輸入就意味著從標(biāo)準(zhǔn)輸入文件上輸入數(shù)據(jù)。scanf,getchar函數(shù)就屬于這類輸入。
從文件編碼的方式來看,文件可分為ASCII碼文件和二進(jìn)制碼文件兩種。
ASCII文件也稱為文本文件,這種文件在磁盤中存放時每個字符對應(yīng)一個字節(jié),用于存放對應(yīng)的ASCII碼。例如,數(shù)5678的存儲形式為:
ASC碼: 00110101 00110110 00110111 00111000
↓ ↓ ↓ ↓
十進(jìn)制碼: 5 6 7 8 共占用4個字節(jié)。ASCII碼文件可在屏幕上按字符顯示, 例如源程序文件就是ASCII文件,用DOS命令TYPE可顯示文件的內(nèi)容。 由于是按字符顯示,因此能讀懂文件內(nèi)容。
二進(jìn)制文件是按二進(jìn)制的編碼方式來存放文件的。 例如, 數(shù)5678的存儲形式為: 00010110 00101110只占二個字節(jié)。二進(jìn)制文件雖然也可在屏幕上顯示, 但其內(nèi)容無法讀懂。C系統(tǒng)在處理這些文件時,并不區(qū)分類型,都看成是字符流,按字節(jié)進(jìn)行處理。 輸入輸出字符流的開始和結(jié)束只由程序控制而不受物理符號(如回車符)的控制。 因此也把這種文件稱作“流式文件”。
本章討論流式文件的打開、關(guān)閉、讀、寫、 定位等各種操作。文件指針在C語言中用一個指針變量指向一個文件, 這個指針稱為文件指針。通過文件指針就可對它所指的文件進(jìn)行各種操作。 定義說明文件指針的一般形式為: FILE* 指針變量標(biāo)識符; 其中FILE應(yīng)為大寫,它實(shí)際上是由系統(tǒng)定義的一個結(jié)構(gòu), 該結(jié)構(gòu)中含有文件名、文件狀態(tài)和文件當(dāng)前位置等信息。 在編寫源程序時不必關(guān)心FILE結(jié)構(gòu)的細(xì)節(jié)。例如:FILE *fp; 表示fp是指向FILE結(jié)構(gòu)的指針變量,通過fp 即可找存放某個文件信息的結(jié)構(gòu)變量,然后按結(jié)構(gòu)變量提供的信息找到該文件, 實(shí)施對文件的操作。習(xí)慣上也籠統(tǒng)地把fp稱為指向一個文件的指針。文件的打開與關(guān)閉文件在進(jìn)行讀寫操作之前要先打開,使用完畢要關(guān)閉。 所謂打開文件,實(shí)際上是建立文件的各種有關(guān)信息, 并使文件指針指向該文件,以便進(jìn)行其它操作。關(guān)閉文件則斷開指針與文件之間的聯(lián)系,也就禁止再對該文件進(jìn)行操作。
在C語言中,文件操作都是由庫函數(shù)來完成的。 在本章內(nèi)將介紹主要的文件操作函數(shù)。
文件打開函數(shù)fopen
fopen函數(shù)用來打開一個文件,其調(diào)用的一般形式為: 文件指針名=fopen(文件名,使用文件方式) 其中,“文件指針名”必須是被說明為FILE 類型的指針變量,“文件名”是被打開文件的文件名。 “使用文件方式”是指文件的類型和操作要求?!拔募笔亲址A炕蜃址?dāng)?shù)組。例如:
FILE *fp;
fp=("file a","r");
其意義是在當(dāng)前目錄下打開文件file a, 只允許進(jìn)行“讀”操作,并使fp指向該文件。
FILE *fphzk
fphzk=("c:hzk16',"rb")
其意義是打開C驅(qū)動器磁盤的根目錄下的文件hzk16, 這是一個二進(jìn)制文件,只允許按二進(jìn)制方式進(jìn)行讀操作。兩個反斜線“ ”中的第一個表示轉(zhuǎn)義字符,第二個表示根目錄。使用文件的方式共有12種,下面給出了它們的符號和意義。
文件使用方式 意 義
“rt” 只讀打開一個文本文件,只允許讀數(shù)據(jù)
“wt” 只寫打開或建立一個文本文件,只允許寫數(shù)據(jù)
“at” 追加打開一個文本文件,并在文件末尾寫數(shù)據(jù)
“rb” 只讀打開一個二進(jìn)制文件,只允許讀數(shù)據(jù)
“wb” 只寫打開或建立一個二進(jìn)制文件,只允許寫數(shù)據(jù)
“ab” 追加打開一個二進(jìn)制文件,并在文件末尾寫數(shù)據(jù)
“rt+” 讀寫打開一個文本文件,允許讀和寫
“wt+” 讀寫打開或建立一個文本文件,允許讀寫
“at+” 讀寫打開一個文本文件,允許讀,或在文件末追加數(shù) 據(jù)
“rb+” 讀寫打開一個二進(jìn)制文件,允許讀和寫
“wb+” 讀寫打開或建立一個二進(jìn)制文件,允許讀和寫
“ab+” 讀寫打開一個二進(jìn)制文件,允許讀,或在文件末追加數(shù)據(jù)
對于文件使用方式有以下幾點(diǎn)說明:
1. 文件使用方式由r,w,a,t,b,+六個字符拼成,各字符的含義是:
r(read): 讀
w(write): 寫
a(append): 追加
t(text): 文本文件,可省略不寫
b(banary): 二進(jìn)制文件
+: 讀和寫
2. 凡用“r”打開一個文件時,該文件必須已經(jīng)存在, 且只能從該文件讀出。
3. 用“w”打開的文件只能向該文件寫入。 若打開的文件不存在,則以指定的文件名建立該文件,若打開的文件已經(jīng)存在,則將該文件刪去,重建一個新文件。
4. 若要向一個已存在的文件追加新的信息,只能用“a ”方式打開文件。但此時該文件必須是存在的,否則將會出錯。
5. 在打開一個文件時,如果出錯,fopen將返回一個空指針值NULL。在程序中可以用這一信息來判別是否完成打開文件的工作,并作相應(yīng)的處理。因此常用以下程序段打開文件:
if((fp=fopen("c:hzk16","rb")==NULL)
{
printf("nerror on open c:hzk16 file!");
getch();
exit(1);
}
這段程序的意義是,如果返回的指針為空,表示不能打開C盤根目錄下的hzk16文件,則給出提示信息“error on open c: hzk16file!”,下一行g(shù)etch()的功能是從鍵盤輸入一個字符,但不在屏幕上顯示。在這里,該行的作用是等待, 只有當(dāng)用戶從鍵盤敲任一鍵時,程序才繼續(xù)執(zhí)行, 因此用戶可利用這個等待時間閱讀出錯提示。敲鍵后執(zhí)行exit(1)退出程序。
6. 把一個文本文件讀入內(nèi)存時,要將ASCII碼轉(zhuǎn)換成二進(jìn)制碼, 而把文件以文本方式寫入磁盤時,也要把二進(jìn)制碼轉(zhuǎn)換成ASCII碼,因此文本文件的讀寫要花費(fèi)較多的轉(zhuǎn)換時間。對二進(jìn)制文件的讀寫不存在這種轉(zhuǎn)換。
7. 標(biāo)準(zhǔn)輸入文件(鍵盤),標(biāo)準(zhǔn)輸出文件(顯示器 ),標(biāo)準(zhǔn)出錯輸出(出錯信息)是由系統(tǒng)打開的,可直接使用。文件關(guān)閉函數(shù)fclose文件一旦使用完畢,應(yīng)用關(guān)閉文件函數(shù)把文件關(guān)閉, 以避免文件的數(shù)據(jù)丟失等錯誤。
fclose函數(shù)
調(diào)用的一般形式是: fclose(文件指針); 例如:
fclose(fp); 正常完成關(guān)閉文件操作時,fclose函數(shù)返回值為0。如返回非零值則表示有錯誤發(fā)生。文件的讀寫對文件的讀和寫是最常用的文件操作。
在C語言中提供了多種文件讀寫的函數(shù):
·字符讀寫函數(shù) :fgetc和fputc
·字符串讀寫函數(shù):fgets和fputs
·數(shù)據(jù)塊讀寫函數(shù):freed和fwrite
·格式化讀寫函數(shù):fscanf和fprinf
下面分別予以介紹。使用以上函數(shù)都要求包含頭文件stdio.h。字符讀寫函數(shù)fgetc和fputc字符讀寫函數(shù)是以字符(字節(jié))為單位的讀寫函數(shù)。 每次可從文件讀出或向文件寫入一個字符。
一、讀字符函數(shù)fgetc
fgetc函數(shù)的功能是從指定的文件中讀一個字符,函數(shù)調(diào)用的形式為: 字符變量=fgetc(文件指針); 例如:ch=fgetc(fp);其意義是從打開的文件fp中讀取一個字符并送入ch中。
對于fgetc函數(shù)的使用有以下幾點(diǎn)說明:
1. 在fgetc函數(shù)調(diào)用中,讀取的文件必須是以讀或讀寫方式打開的。
2. 讀取字符的結(jié)果也可以不向字符變量賦值,例如:fgetc(fp);但是讀出的字符不能保存。
3. 在文件內(nèi)部有一個位置指針。用來指向文件的當(dāng)前讀寫字節(jié)。在文件打開時,該指針總是指向文件的第一個字節(jié)。使用fgetc 函數(shù)后, 該位置指針將向后移動一個字節(jié)。 因此可連續(xù)多次使用fgetc函數(shù),讀取多個字符。 應(yīng)注意文件指針和文件內(nèi)部的位置指針不是一回事。文件指針是指向整個文件的,須在程序中定義說明,只要不重新賦值,文件指針的值是不變的。文件內(nèi)部的位置指針用以指示文件內(nèi)部的當(dāng)前讀寫位置,每讀寫一次,該指針均向后移動,它不需在程序中定義說明,而是由系統(tǒng)自動設(shè)置的。
[例10.1]讀入文件e10-1.c,在屏幕上輸出。
#include<stdio.h>
main()
{
FILE *fp;
char ch;
if((fp=fopen("e10_1.c","rt"))==NULL)
{
printf("Cannot open file strike any key exit!");
getch();
exit(1);
}
ch=fgetc(fp);
while (ch!=EOF)
{
putchar(ch);
ch=fgetc(fp);
}
fclose(fp);
}
本例程序的功能是從文件中逐個讀取字符,在屏幕上顯示。 程序定義了文件指針fp,以讀文本文件方式打開文件“e10_1.c”, 并使fp指向該文件。如打開文件出錯, 給出提示并退出程序。程序第12行先讀出一個字符,然后進(jìn)入循環(huán), 只要讀出的字符不是文件結(jié)束標(biāo)志(每個文件末有一結(jié)束標(biāo)志EOF)就把該字符顯示在屏幕上,再讀入下一字符。每讀一次,文件內(nèi)部的位置指針向后移動一個字符,文件結(jié)束時,該指針指向EOF。執(zhí)行本程序?qū)@示整個文件。
二、寫字符函數(shù)fputc
fputc函數(shù)的功能是把一個字符寫入指定的文件中,函數(shù)調(diào)用的 形式為: fputc(字符量,文件指針); 其中,待寫入的字符量可以是字符常量或變量,例如:fputc('a',fp);其意義是把字符a寫入fp所指向的文件中。
對于fputc函數(shù)的使用也要說明幾點(diǎn):
1. 被寫入的文件可以用、寫、讀寫,追加方式打開,用寫或讀寫方式打開一個已存在的文件時將清除原有的文件內(nèi)容,寫入字符從文件首開始。如需保留原有文件內(nèi)容,希望寫入的字符以文件末開始存放,必須以追加方式打開文件。被寫入的文件若不存在,則創(chuàng)建該文件。
2. 每寫入一個字符,文件內(nèi)部位置指針向后移動一個字節(jié)。
3. fputc函數(shù)有一個返回值,如寫入成功則返回寫入的字符, 否則返回一個EOF??捎么藖砼袛鄬懭胧欠癯晒?。
[例10.2]從鍵盤輸入一行字符,寫入一個文件, 再把該文件內(nèi)容讀出顯示在屏幕上。
#include<stdio.h>
main()
{
FILE *fp;
char ch;
if((fp=fopen("string","wt+"))==NULL)
{
printf("Cannot open file strike any key exit!");
getch();
exit(1);
}
printf("input a string:n");
ch=getchar();
while (ch!='n')
{
fputc(ch,fp);
ch=getchar();
}
rewind(fp);
ch=fgetc(fp);
while(ch!=EOF)
{
putchar(ch);
ch=fgetc(fp);
}
printf("n");
fclose(fp);
}
程序中第6行以讀寫文本文件方式打開文件string。程序第13行從鍵盤讀入一個字符后進(jìn)入循環(huán),當(dāng)讀入字符不為回車符時, 則把該字符寫入文件之中,然后繼續(xù)從鍵盤讀入下一字符。 每輸入一個字符,文件內(nèi)部位置指針向后移動一個字節(jié)。寫入完畢, 該指針已指向文件末。如要把文件從頭讀出,須把指針移向文件頭, 程序第19行rewind函數(shù)用于把fp所指文件的內(nèi)部位置指針移到文件頭。 第20至25行用于讀出文件中的一行內(nèi)容。
[例10.3]把命令行參數(shù)中的前一個文件名標(biāo)識的文件, 復(fù)制到后一個文件名標(biāo)識的文件中, 如命令行中只有一個文件名則把該文件寫到標(biāo)準(zhǔn)輸出文件(顯示器)中。
#include<stdio.h>
main(int argc,char *argv[])
{
FILE *fp1,*fp2;
char ch;
if(argc==1)
{
printf("have not enter file name strike any key exit");
getch();
exit(0);
}
if((fp1=fopen(argv[1],"rt"))==NULL)
{
printf("Cannot open %sn",argv[1]);
getch();
exit(1);
}
if(argc==2) fp2=stdout;
else if((fp2=fopen(argv[2],"wt+"))==NULL)
{
printf("Cannot open %sn",argv[1]);
getch();
exit(1);
}
while((ch=fgetc(fp1))!=EOF)
fputc(ch,fp2);
fclose(fp1);
fclose(fp2);
}
本程序?yàn)閹⒌膍ain函數(shù)。程序中定義了兩個文件指針 fp1 和fp2,分別指向命令行參數(shù)中給出的文件。如命令行參數(shù)中沒有給出文件名,則給出提示信息。程序第18行表示如果只給出一個文件名,則使fp2指向標(biāo)準(zhǔn)輸出文件(即顯示器)。程序第25行至28行用循環(huán)語句逐個讀出文件1中的字符再送到文件2中。再次運(yùn)行時,給出了一個文件名(由例10.2所建立的文件), 故輸出給標(biāo)準(zhǔn)輸出文件stdout,即在顯示器上顯示文件內(nèi)容。第三次運(yùn)行,給出了二個文件名,因此把string中的內(nèi)容讀出,寫入到OK之中??捎肈OS命令type顯示OK的內(nèi)容:字符串讀寫函數(shù)fgets和fputs
一、讀字符串函數(shù)fgets函數(shù)的功能是從指定的文件中讀一個字符串到字符數(shù)組中,函數(shù)調(diào)用的形式為: fgets(字符數(shù)組名,n,文件指針); 其中的n是一個正整數(shù)。表示從文件中讀出的字符串不超過 n-1個字符。在讀入的最后一個字符后加上串結(jié)束標(biāo)志''。例如:fgets(str,n,fp);的意義是從fp所指的文件中讀出n-1個字符送入字符數(shù)組str中。
[例10.4]從e10_1.c文件中讀入一個含10個字符的字符串。
#include<stdio.h>
main()
{
FILE *fp;
char str[11];
if((fp=fopen("e10_1.c","rt"))==NULL)
{
printf("Cannot open file strike any key exit!");
getch();
exit(1);
}
fgets(str,11,fp);
printf("%s",str);
fclose(fp);
}
本例定義了一個字符數(shù)組str共11個字節(jié),在以讀文本文件方式打開文件e101.c后,從中讀出10個字符送入str數(shù)組,在數(shù)組最后一個單元內(nèi)將加上'',然后在屏幕上顯示輸出str數(shù)組。輸出的十個字符正是例10.1程序的前十個字符。
對fgets函數(shù)有兩點(diǎn)說明:
1. 在讀出n-1個字符之前,如遇到了換行符或EOF,則讀出結(jié)束。
2. fgets函數(shù)也有返回值,其返回值是字符數(shù)組的首地址。
二、寫字符串函數(shù)fputs
fputs函數(shù)的功能是向指定的文件寫入一個字符串,其調(diào)用形式為: fputs(字符串,文件指針) 其中字符串可以是字符串常量,也可以是字符數(shù)組名, 或指針 變量,例如:
fputs(“abcd“,fp);
其意義是把字符串“abcd”寫入fp所指的文件之中。[例10.5]在例10.2中建立的文件string中追加一個字符串。
#include<stdio.h>
main()
{
FILE *fp;
char ch,st[20];
if((fp=fopen("string","at+"))==NULL)
{
printf("Cannot open file strike any key exit!");
getch();
exit(1);
}
printf("input a string:n");
scanf("%s",st);
fputs(st,fp);
rewind(fp);
ch=fgetc(fp);
while(ch!=EOF)
{
putchar(ch);
ch=fgetc(fp);
}
printf("n");
fclose(fp);
}
本例要求在string文件末加寫字符串,因此,在程序第6行以追加讀寫文本文件的方式打開文件string 。 然后輸入字符串, 并用fputs函數(shù)把該串寫入文件string。在程序15行用rewind函數(shù)把文件內(nèi)部位置指針移到文件首。 再進(jìn)入循環(huán)逐個顯示當(dāng)前文件中的全部內(nèi)容。
數(shù)據(jù)塊讀寫函數(shù)fread和fwrite
C語言還提供了用于整塊數(shù)據(jù)的讀寫函數(shù)。 可用來讀寫一組數(shù)據(jù),如一個數(shù)組元素,一個結(jié)構(gòu)變量的值等。讀數(shù)據(jù)塊函數(shù)調(diào)用的一般形式為: fread(buffer,size,count,fp); 寫數(shù)據(jù)塊函數(shù)調(diào)用的一般形式為: fwrite(buffer,size,count,fp); 其中buffer是一個指針,在fread函數(shù)中,它表示存放輸入數(shù)據(jù)的首地址。在fwrite函數(shù)中,它表示存放輸出數(shù)據(jù)的首地址。 size 表示數(shù)據(jù)塊的字節(jié)數(shù)。count 表示要讀寫的數(shù)據(jù)塊塊數(shù)。fp 表示文件指針。
例如:
fread(fa,4,5,fp); 其意義是從fp所指的文件中,每次讀4個字節(jié)(一個實(shí)數(shù))送入實(shí)數(shù)組fa中,連續(xù)讀5次,即讀5個實(shí)數(shù)到fa中。
[例10.6]從鍵盤輸入兩個學(xué)生數(shù)據(jù),寫入一個文件中, 再讀出這兩個學(xué)生的數(shù)據(jù)顯示在屏幕上。
#include<stdio.h>
struct stu
{
char name[10];
int num;
int age;
char addr[15];
}boya[2],boyb[2],*pp,*qq;
main()
{
FILE *fp;
char ch;
int i;
pp=boya;
qq=boyb;
if((fp=fopen("stu_list","wb+"))==NULL)
{
printf("Cannot open file strike any key exit!");
getch();
exit(1);
}
printf("ninput datan");
for(i=0;i<2;i++,pp++)
scanf("%s%d%d%s",pp->name,&pp->num,&pp->age,pp->addr);
pp=boya;
fwrite(pp,sizeof(struct stu),2,fp);
rewind(fp);
fread(qq,sizeof(struct stu),2,fp);
printf("nnnametnumber age addrn");
for(i=0;i<2;i++,qq++)
printf("%st%5d%7d%sn",qq->name,qq->num,qq->age,qq->addr);
fclose(fp);
}
本例程序定義了一個結(jié)構(gòu)stu,說明了兩個結(jié)構(gòu)數(shù)組boya和 boyb以及兩個結(jié)構(gòu)指針變量pp和qq。pp指向boya,qq指向boyb。程序第16行以讀寫方式打開二進(jìn)制文件“stu_list”,輸入二個學(xué)生數(shù)據(jù)之后,寫入該文件中, 然后把文件內(nèi)部位置指針移到文件首,讀出兩塊學(xué)生數(shù)據(jù)后,在屏幕上顯示。
格式化讀寫函數(shù)fscanf和fprintf
fscanf函數(shù),fprintf函數(shù)與前面使用的scanf和printf 函數(shù)的功能相似,都是格式化讀寫函數(shù)。 兩者的區(qū)別在于 fscanf 函數(shù)和fprintf函數(shù)的讀寫對象不是鍵盤和顯示器,而是磁盤文件。這兩個函數(shù)的調(diào)用格式為: fscanf(文件指針,格式字符串,輸入表列); fprintf(文件指針,格式字符串,輸出表列); 例如:
fscanf(fp,"%d%s",&i,s);
fprintf(fp,"%d%c",j,ch);
用fscanf和fprintf函數(shù)也可以完成例10.6的問題。修改后的程序如例10.7所示。
[例10.7]
#include<stdio.h>
struct stu
{
char name[10];
int num;
int age;
char addr[15];
}boya[2],boyb[2],*pp,*qq;
main()
{
FILE *fp;
char ch;
int i;
pp=boya;
qq=boyb;
if((fp=fopen("stu_list","wb+"))==NULL)
{
printf("Cannot open file strike any key exit!");
getch();
exit(1);
}
printf("ninput datan");
for(i=0;i<2;i++,pp++)
scanf("%s%d%d%s",pp->name,&pp->num,&pp->age,pp->addr);
pp=boya;
for(i=0;i<2;i++,pp++)
fprintf(fp,"%s %d %d %sn",pp->name,pp->num,pp->age,pp->
addr);
rewind(fp);
for(i=0;i<2;i++,qq++)
fscanf(fp,"%s %d %d %sn",qq->name,&qq->num,&qq->age,qq->addr);
printf("nnnametnumber age addrn");
qq=boyb;
for(i=0;i<2;i++,qq++)
printf("%st%5d %7d %sn",qq->name,qq->num, qq->age,
qq->addr);
fclose(fp);
}
與例10.6相比,本程序中fscanf和fprintf函數(shù)每次只能讀寫一個結(jié)構(gòu)數(shù)組元素,因此采用了循環(huán)語句來讀寫全部數(shù)組元素。 還要注意指針變量pp,qq由于循環(huán)改變了它們的值,因此在程序的25和32行分別對它們重新賦予了數(shù)組的首地址。
文件的隨機(jī)讀寫
前面介紹的對文件的讀寫方式都是順序讀寫, 即讀寫文件只能從頭開始,順序讀寫各個數(shù)據(jù)。 但在實(shí)際問題中常要求只讀寫文件中某一指定的部分。 為了解決這個問題可移動文件內(nèi)部的位置指針到需要讀寫的位置,再進(jìn)行讀寫,這種讀寫稱為隨機(jī)讀寫。 實(shí)現(xiàn)隨機(jī)讀寫的關(guān)鍵是要按要求移動位置指針,這稱為文件的定位。文件定位移動文件內(nèi)部位置指針的函數(shù)主要有兩個, 即 rewind 函數(shù)和fseek函數(shù)。
rewind函數(shù)前面已多次使用過,其調(diào)用形式為: rewind(文件指針); 它的功能是把文件內(nèi)部的位置指針移到文件首。 下面主要介紹
fseek函數(shù)。
fseek函數(shù)用來移動文件內(nèi)部位置指針,其調(diào)用形式為: fseek(文件指針,位移量,起始點(diǎn)); 其中:“文件指針”指向被移動的文件。 “位移量”表示移動的字節(jié)數(shù),要求位移量是long型數(shù)據(jù),以便在文件長度大于64KB 時不會出錯。當(dāng)用常量表示位移量時,要求加后綴“L”?!捌鹗键c(diǎn)”表示從何處開始計(jì)算位移量,規(guī)定的起始點(diǎn)有三種:文件首,當(dāng)前位置和文件尾。
其表示方法如表10.2。
起始點(diǎn) 表示符號 數(shù)字表示
——————————————————————————
文件首 SEEK—SET 0
當(dāng)前位置 SEEK—CUR 1
文件末尾 SEEK—END 2
例如:
fseek(fp,100L,0);其意義是把位置指針移到離文件首100個字節(jié)處。還要說明的是fseek函數(shù)一般用于二進(jìn)制文件。在文本文件中由于要進(jìn)行轉(zhuǎn)換,故往往計(jì)算的位置會出現(xiàn)錯誤。文件的隨機(jī)讀寫在移動位置指針之后, 即可用前面介紹的任一種讀寫函數(shù)進(jìn)行讀寫。由于一般是讀寫一個數(shù)據(jù)據(jù)塊,因此常用fread和fwrite函數(shù)。下面用例題來說明文件的隨機(jī)讀寫。
[例10.8]在學(xué)生文件stu list中讀出第二個學(xué)生的數(shù)據(jù)。
#include<stdio.h>
struct stu
{
char name[10];
int num;
int age;
char addr[15];
}boy,*qq;
main()
{
FILE *fp;
char ch;
int i=1;
qq=&boy;
if((fp=fopen("stu_list","rb"))==NULL)
{
printf("Cannot open file strike any key exit!");
getch();
exit(1);
}
rewind(fp);
fseek(fp,i*sizeof(struct stu),0);
fread(qq,sizeof(struct stu),1,fp);
printf("nnnametnumber age addrn");
printf("%st%5d %7d %sn",qq->name,qq->num,qq->age,
qq->addr);
}
文件stu_list已由例10.6的程序建立,本程序用隨機(jī)讀出的方法讀出第二個學(xué)生的數(shù)據(jù)。程序中定義boy為stu類型變量,qq為指向boy的指針。以讀二進(jìn)制文件方式打開文件,程序第22行移動文件位置指針。其中的i值為1,表示從文件頭開始,移動一個stu類型的長度, 然后再讀出的數(shù)據(jù)即為第二個學(xué)生的數(shù)據(jù)。
文件檢測函數(shù)
C語言中常用的文件檢測函數(shù)有以下幾個。
一、文件結(jié)束檢測函數(shù)feof函數(shù)調(diào)用格式: feof(文件指針);
功能:判斷文件是否處于文件結(jié)束位置,如文件結(jié)束,則返回值為1,否則為0。
二、讀寫文件出錯檢測函數(shù)ferror函數(shù)調(diào)用格式: ferror(文件指針);
功能:檢查文件在用各種輸入輸出函數(shù)進(jìn)行讀寫時是否出錯。 如ferror返回值為0表示未出錯,否則表示有錯。
三、文件出錯標(biāo)志和文件結(jié)束標(biāo)志置0函數(shù)clearerr函數(shù)調(diào)用格式: clearerr(文件指針);
功能:本函數(shù)用于清除出錯標(biāo)志和文件結(jié)束標(biāo)志,使它們?yōu)?值。
C庫文件
C系統(tǒng)提供了豐富的系統(tǒng)文件,稱為庫文件,C的庫文件分為兩類,一類是擴(kuò)展名為".h"的文件,稱為頭文件, 在前面的包含命令中我們已多次使用過。在".h"文件中包含了常量定義、 類型定義、宏定義、函數(shù)原型以及各種編譯選擇設(shè)置等信息。另一類是函數(shù)庫,包括了各種函數(shù)的目標(biāo)代碼,供用戶在程序中調(diào)用。 通常在程序中調(diào)用一個庫函數(shù)時,要在調(diào)用之前包含該函數(shù)原型所在的".h" 文件。
在附錄中給出了全部庫函數(shù)。
ALLOC.H 說明內(nèi)存管理函數(shù)(分配、釋放等)。
ASSERT.H 定義 assert調(diào)試宏。
BIOS.H 說明調(diào)用IBM—PC ROM BIOS子程序的各個函數(shù)。
CONIO.H 說明調(diào)用DOS控制臺I/O子程序的各個函數(shù)。
CTYPE.H 包含有關(guān)字符分類及轉(zhuǎn)換的名類信息(如 isalpha和toascii等)。
DIR.H 包含有關(guān)目錄和路徑的結(jié)構(gòu)、宏定義和函數(shù)。
DOS.H 定義和說明MSDOS和8086調(diào)用的一些常量和函數(shù)。
ERRON.H 定義錯誤代碼的助記符。
FCNTL.H 定義在與open庫子程序連接時的符號常量。
FLOAT.H 包含有關(guān)浮點(diǎn)運(yùn)算的一些參數(shù)和函數(shù)。
GRAPHICS.H 說明有關(guān)圖形功能的各個函數(shù),圖形錯誤代碼的常量定義,正對不同驅(qū)動程序的各種顏色值,及函數(shù)用到的一些特殊結(jié)構(gòu)。
IO.H 包含低級I/O子程序的結(jié)構(gòu)和說明。
LIMIT.H 包含各環(huán)境參數(shù)、編譯時間限制、數(shù)的范圍等信息。
MATH.H 說明數(shù)學(xué)運(yùn)算函數(shù),還定了 HUGE VAL 宏, 說明了matherr和matherr子程序用到的特殊結(jié)構(gòu)。
MEM.H 說明一些內(nèi)存操作函數(shù)(其中大多數(shù)也在STRING.H 中說明)。
PROCESS.H 說明進(jìn)程管理的各個函數(shù),spawn…和EXEC …函數(shù)的結(jié)構(gòu)說明。
SETJMP.H 定義longjmp和setjmp函數(shù)用到的jmp buf類型, 說明這兩個函數(shù)。
SHARE.H 定義文件共享函數(shù)的參數(shù)。
SIGNAL.H 定義SIG[ZZ(Z] [ZZ)]IGN和SIG[ZZ(Z] [ZZ)]DFL常量,說明rajse和signal兩個函數(shù)。
STDARG.H 定義讀函數(shù)參數(shù)表的宏。(如vprintf,vscarf函數(shù))。
STDDEF.H 定義一些公共數(shù)據(jù)類型和宏。
STDIO.H 定義Kernighan和Ritchie在Unix System V 中定義的標(biāo)準(zhǔn)和擴(kuò)展的類型和宏。還定義標(biāo)準(zhǔn)I/O 預(yù)定義流:stdin,stdout和stderr,說明 I/O流子程序。
STDLIB.H 說明一些常用的子程序:轉(zhuǎn)換子程序、搜索/ 排序子程序等。
STRING.H 說明一些串操作和內(nèi)存操作函數(shù)。
SYSSTAT.H 定義在打開和創(chuàng)建文件時用到的一些符號常量。
SYSTYPES.H 說明ftime函數(shù)和timeb結(jié)構(gòu)。
SYSTIME.H 定義時間的類型time[ZZ(Z] [ZZ)]t。
TIME.H 定義時間轉(zhuǎn)換子程序asctime、localtime和gmtime的結(jié)構(gòu),ctime、 difftime、 gmtime、 localtime和stime用到的類型,并提供這些函數(shù)的原型。
VALUE.H 定義一些重要常量, 包括依賴于機(jī)器硬件的和為與Unix System V相兼容而說明的一些常量,包括浮點(diǎn)和雙精度值的范圍。
補(bǔ)充:在Unix系統(tǒng)的文本文件中,是用換行符(ASCII 10)作為行結(jié)束標(biāo)記。在Macintosh系統(tǒng)中,是用回車符(ASCII 13)作為行結(jié)束標(biāo)記。而Windows系統(tǒng)則是沿用了DOS系統(tǒng)的標(biāo)準(zhǔn),換行符和回車符都用來作為行結(jié)束標(biāo)記。
信威筆試
綜合能力測試:
3、172,84,40,18 ?
4、8 5 2
4 5 0
+C C C +E E E
A-I分別表示0-9的數(shù)字,問A的值
6-9 圖形題
2、男醫(yī)生多于男護(hù)士
4、至少有一個女醫(yī)生
問“我”的性別和職位
11、四個人坐在方桌旁,兩個女士A和B,兩個男士C和D,四個人分別是游泳,滑冰,體操和
網(wǎng)球運(yùn)動員
1、游泳在A左邊
2、體操在C對面
3、B和D相鄰
4、一位女生在滑冰的左邊
問誰是網(wǎng)球運(yùn)動員
12、領(lǐng)導(dǎo)從博物館難走一塊明朝城墻的磚,如何要回來
硬件筆試題
揭露華為、大唐等企業(yè)硬件筆試題 [專題文章]
漢王筆試
下面是一些基本的數(shù)字電路知識問題,請簡要回答之。
a) 什么是Setup 和Holdup時間?
b) 什么是競爭與冒險現(xiàn)象?怎樣判斷?如何消除?
c) 請畫出用D觸發(fā)器實(shí)現(xiàn)2倍分頻的邏輯電路?
d) 什么是"線與"邏輯,要實(shí)現(xiàn)它,在硬件特性上有什么具體要求?
e) 什么是同步邏輯和異步邏輯?
f) 請畫出微機(jī)接口電路中,典型的輸入設(shè)備與微機(jī)接口邏輯示意圖(數(shù)據(jù)接口、控制接口、所存器/緩沖器)。
g) 你知道那些常用邏輯電平?TTL與COMS電平可以直接互連嗎?
2、 可編程邏輯器件在現(xiàn)代電子設(shè)計(jì)中越來越重要,請問:
a) 你所知道的可編程邏輯器件有哪些?
b) 試用VHDL或VERILOG、ABLE描述8位D觸發(fā)器邏輯。
3、 設(shè)想你將設(shè)計(jì)完成一個電子電路方案。請簡述用EDA軟件(如PROTEL)進(jìn)行設(shè)計(jì)(包括原理圖和PCB圖)到調(diào)試出樣機(jī)的整個過程。在各環(huán)節(jié)應(yīng)注意哪些問題?
飛利浦-大唐筆試歸來
1,用邏輯們和cmos電路實(shí)現(xiàn)ab cd
2. 用一個二選一mux和一個inv實(shí)現(xiàn)異或
3. 給了reg的setup,hold時間,求中間組合邏輯的delay范圍。 Setup/hold time 是測試芯片對輸入信號和時鐘信號之間的時間要求。建立時間是指觸發(fā)器的時鐘信號上升沿到來以前,數(shù)據(jù)穩(wěn)定不變的時間。輸入信號應(yīng)提前時鐘上升沿(如上升沿有效)T時間到達(dá)芯片,這個T就是建立時間-Setup time.如不滿足setup time,這個數(shù)據(jù)就不能被這一時鐘打入觸發(fā)器,只有在下一個時鐘上升沿,數(shù)據(jù)才能被打入觸發(fā)器。 保持時間是指觸發(fā)器的時鐘信號上升沿到來以后,數(shù)據(jù)穩(wěn)定不變的時間。時hold time不夠,數(shù)據(jù)同樣不能被打入觸發(fā)器。
4. 如何解決亞穩(wěn)態(tài)
5. 用verilog/vhdl寫一個fifo控制器
6. 用verilog/vddl檢測stream中的特定字符串
信威dsp軟件面試題
1)DSP和通用處理器在結(jié)構(gòu)上有什么不同,請簡要畫出你熟悉的一種DSP結(jié)構(gòu)圖
2)說說定點(diǎn)DSP和浮點(diǎn)DSP的定義(或者說出他們的區(qū)別)
3)說說你對循環(huán)尋址和位反序?qū)ぶ返睦斫?
4)請寫出【-8,7】的二進(jìn)制補(bǔ)碼,和二進(jìn)制偏置碼。 用Q15表示出0.5和-0.5
揚(yáng)智電子筆試
第一題:用mos管搭出一個二輸入與非門。
第二題:集成電路前段設(shè)計(jì)流程,寫出相關(guān)的工具。
第三題:名詞IRQ,BIOS,USB,VHDL,SDR
第四題:unix 命令cp -r, rm,uname
第五題:用波形表示D觸發(fā)器的功能
第六題:寫異步D觸發(fā)器的verilog module
第七題:What is PC Chipset?
第八題:用傳輸門和倒向器搭一個邊沿觸發(fā)器
第九題:畫狀態(tài)機(jī),接受1,2,5分錢的賣報(bào)機(jī),每份報(bào)紙5分錢。
華為面題 (硬件)
全都是幾本模電數(shù)電信號單片機(jī)題目
1.用與非門等設(shè)計(jì)全加法器
2.給出兩個門電路讓你分析異同
3.名詞:sram,ssram,sdram
4.信號與系統(tǒng):在時域與頻域關(guān)系
5.信號與系統(tǒng):和4題差不多
6.晶體振蕩器,好像是給出振蕩頻率讓你求周期(應(yīng)該是單片機(jī)的,12分之一周期....)
7.串行通信與同步通信異同,特點(diǎn),比較
8.RS232c高電平脈沖對應(yīng)的TTL邏輯是?(負(fù)邏輯?)
9.延時問題,判錯
10.史密斯特電路,求回差電壓
11.VCO是什么,什么參數(shù)(壓控振蕩器?)
12. 用D觸發(fā)器做個二分顰的電路.又問什么是狀態(tài)圖
13. 什么耐奎斯特定律,怎么由模擬信號轉(zhuǎn)為數(shù)字信號
14. 用D觸發(fā)器做個4進(jìn)制的計(jì)數(shù)
15.那種排序方法最快?
一、 研發(fā)(軟件)
用C語言寫一個遞歸算法求N!;
給一個C的函數(shù),關(guān)于字符串和數(shù)組,找出錯誤;
防火墻是怎么實(shí)現(xiàn)的?
你對哪方面編程熟悉?
新太硬件面題
(1)d觸發(fā)器和d鎖存器的區(qū)別
(2)有源濾波器和無源濾波器的原理及區(qū)別
(3)sram,falsh memory,及dram的區(qū)別?
(4)iir,fir濾波器的異同
(5)冒泡排序的原理
(6)操作系統(tǒng)的功能
(7)學(xué)過的計(jì)算機(jī)語言及開發(fā)的系統(tǒng)
(8)拉氏變換和傅立葉變換的表達(dá)式及聯(lián)系。
C語言變量和數(shù)據(jù)存儲
C語言的強(qiáng)大功能之一是可以靈活地定義數(shù)據(jù)的存儲方式。C語言從兩個方面控制變量的性質(zhì):作用域(scope)和生存期(lifetime)。作用域是指可以存取變量的代碼范圍,生存期是指可以存取變量的時間范圍。
作用域有三種:
1. extern(外部的) 這是在函數(shù)外部定義的變量的缺省存儲方式。extern變量的作用域是整個程序。
2.static(靜態(tài)的) 在函數(shù)外部說明為static的變量的作用域?yàn)閺亩x點(diǎn)到該文件尾部;在函數(shù)內(nèi)部說明為static的變量的作用域?yàn)閺亩x點(diǎn)到該局部程序塊尾部。
3.a(chǎn)uto(自動的) 這是在函數(shù)內(nèi)部說明的變量的缺省存儲方式。auto變量的作用域?yàn)閺亩x點(diǎn)到該局部程序塊尾部。
變量的生存期也有三種,但它們不象作用域那樣有預(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()或程序退出時為止。
變量可以存儲在內(nèi)存中的不同地方,這依賴于它們的生存期。在函數(shù)外部定義的變量(全局變量或靜態(tài)外部變量)和在函數(shù)內(nèi)部定義的static變量,其生存期就是程序運(yùn)行的全過程,這些變量被存儲在數(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)存(virtual memory)的系統(tǒng)中,這個界限由系統(tǒng)決定,并且通常非常大,因此程序員不必?fù)?dān)心用盡棧空間。關(guān)于虛擬內(nèi)存 的討論,請參見2.3。
第三種(也是最后一種)內(nèi)存空間實(shí)際上并不存儲變量,但是可以用來存儲變量所指向的數(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)存段,這完全取決于編譯選項(xiàng)和操作系統(tǒng)。
與棧相似,堆也有一個增長界限,并且決定這個界限的規(guī)則與棧相同。
請參見:
1.1 什么是局部程序塊(10calblock)?
2.2 變量必須初始化嗎?
2.3 什么是頁抖動(pagethrashing)?
7.20 什么是棧(stack)?
7.21 什么是堆(heap)7 .
不。使用變量之前應(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)?
有些操作系統(tǒng)(如UNIX和增強(qiáng)模式下的Windows)使用虛擬內(nèi)存,這是一種使機(jī)器的作業(yè)地址空間大于實(shí)際內(nèi)存的技術(shù),它是通過用磁盤空間模擬RAM(random—access memory)來實(shí)現(xiàn)的。
在80386和更高級的Intel CPU芯片中,在現(xiàn)有的大多數(shù)其它微處理器(如Motorola 68030,sparc和Power PC)中,都有一個被稱為內(nèi)存管理單元(Memory Management Unit,縮寫為MMU)的器件。MMU把內(nèi)存看作是由一系列“頁(page)”組成的來處理。一頁內(nèi)存是指一個具有一定大小的連續(xù)的內(nèi)存塊,通常為4096或8192字節(jié)。操作系統(tǒng)為每個正在運(yùn)行的程序建立并維護(hù)一張被稱為進(jìn)程內(nèi)存映射(Process Memory Map,縮與為PMM)的表,表中記錄了程序可以存取的所有內(nèi)存頁以及它們的實(shí)際位置。
每當(dāng)程序存取一塊內(nèi)存時,它會把相應(yīng)的地址(虛擬地址,virtualaddress)傳送給MMU,MMU會在PMM中查找這塊內(nèi)存的實(shí)際位置(物理地址,physical address),物理地址可以是由操作系統(tǒng)指定的在內(nèi)存中或磁盤上的任何位置。如果程序要存取的位置在磁盤上,就必須把包含該地址的頁從磁盤上讀到內(nèi)存中,并且必須更新PMM以反映這個變化(這被稱為pagefault,即頁錯)。
希望你繼續(xù)讀下去,因?yàn)橄旅婢鸵榻B其中的難點(diǎn)了。存取磁盤比存取RAM要慢得多,所以操作系統(tǒng)會試圖在RAM中保持盡量多的虛擬內(nèi)存。如果你在運(yùn)行一個非常大的程序(或者同時運(yùn)行幾個小程序),那么可能沒有足夠的RAM來承擔(dān)程序要使用的全部內(nèi)存,因此必須把一些頁從RAM中移到磁盤上(這被為pagingout,即頁出)。
操作系統(tǒng)會試圖去判斷哪些頁可能暫時不會被使用(通?;谶^去使用內(nèi)存的情況),如果它判斷錯了,或者程序正在很多地方存取很多內(nèi)存,那么為了讀入已調(diào)出的頁,就會產(chǎn)生大量頁錯動作。因?yàn)镽AM已被全部使用,所以為了調(diào)入要存取的一頁,必須調(diào)出另一頁,而這將導(dǎo)致更多的頁錯動作,因?yàn)榇藭r不同的一頁已被移到磁盤上。在短時間內(nèi)出現(xiàn)大量頁錯動作的情形被稱為頁抖動,它將大大降低系統(tǒng)的執(zhí)行效率。
頻繁存取內(nèi)存中大量散布的位置的程序更容易在系統(tǒng)中造成頁抖動。如果同時運(yùn)行許多小程序,而實(shí)際上已經(jīng)不再使用這些程序,也很容易造成頁抖動。為了減少頁抖動,你應(yīng)該減少同時運(yùn)行的程序的數(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.31 Windows是怎樣組織內(nèi)存的?
如果希望一個變量在被初始化后其值不會被修改,程序員就會通過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指針的一些合法和非法的用法例子:
const char *str="hello";
char c=*str; /*legal*/
str++; /*legal*/
*str='a'; /* illegal */
str[1]='b'; /*illegal*/
前兩條語句是合法的,因?yàn)樗鼈儧]有修改str所指向的數(shù)據(jù);后兩條語句是非法的,因?yàn)樗鼈円薷膕tr所指向的數(shù)據(jù)。
在說明函數(shù)參數(shù)時,常常要使用const指針。例如,一個計(jì)算字符串長度的函數(shù)不必改變字符串內(nèi)容,它可以寫成這樣:
my_strlen(const char *str)
{
int count=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)該使用類型強(qiáng)制轉(zhuǎn)換(type cast)?
2. 18 用const說明常量有什么好處?
register修飾符暗示編譯程序相應(yīng)的變量將被頻繁使用,如果可能的話,應(yīng)將其保存在CPU的寄存器中,以加快其存取速度。但是,使用register修飾符有幾點(diǎn)限制。
首先,register變量必須是能被CPU寄存器所接受的類型。這通常意味著register變量必須是一個單個的值,并且其長度應(yīng)小于或等于整型的長度。但是,有些機(jī)器的寄存器也能存放浮點(diǎn)數(shù)。
其次,因?yàn)閞egister變量可能不存放在內(nèi)存中,所以不能用取址運(yùn)算符“&”來獲取register變量的地址。如果你試圖這樣做,編譯程序就會報(bào)告這是一個錯誤。
register修飾符的用處有多大還受其它一些規(guī)則的影響。因?yàn)榧拇嫫鞯臄?shù)量是有限的,而且某些寄存器只能接受特定類型的數(shù)據(jù)(如指針和浮點(diǎn)數(shù)),因此,真正能起作用的register修飾符的數(shù)目和類型都依賴于運(yùn)行程序的機(jī)器,而任何多余的register修飾符都將被編譯程序所忽略。
在某些情況下,把變量保存在寄存器中反而會降低運(yùn)行速度,因?yàn)楸徽加玫募拇嫫鞑荒茉儆糜谄渌康?,或—者變量被使用的次?shù)不夠多,不足以抵消裝入和存儲變量所帶來的額外開銷。
那么,什么時候應(yīng)該使用register修飾符呢?回答是,對現(xiàn)有的大多數(shù)編譯程序來說,永遠(yuǎn)不要使用register修飾符。早期的C編譯程序不會把變量保存在寄存器中,除非你命令它這樣做,這時register修飾符是C語言的一種很有價值的補(bǔ)充。然而,隨著編譯程序設(shè)計(jì)技術(shù)的進(jìn)步,在決定哪些變量應(yīng)該被存到寄存器中時,現(xiàn)在的C編譯程序能比程序員作出更好的決定。
實(shí)際上,許多C編譯程序會忽略register修飾符,因?yàn)楸M管它完全合法,但它僅僅是暗示而不是命令。
在極罕見的情況下,程序運(yùn)行速度很慢,而你也知道這是因?yàn)橛幸粋€變量被存儲在內(nèi)存中,也許你最后會試圖在該變量前面加上register修飾符,但是,如果這并沒有加快程序的運(yùn)行速度,你也不要感到奇怪。
請參見:
2.6 什么時候應(yīng)該使用volatile修飾符?
volatile修飾符告訴編譯程序不要對該變量所參與的操作進(jìn)行某些優(yōu)化。在兩種特殊的情況下需要使用volatile修飾符:第一種情況涉及到內(nèi)存映射硬件(memory-mapped hardware,如圖形適配器,這類設(shè)備對計(jì)算機(jī)來說就好象是內(nèi)存的一部分一樣),第二種情況涉及到共享內(nèi)存(shared memory,即被兩個以上同時運(yùn)行的程序所使用的內(nèi)存)。
大多數(shù)計(jì)算機(jī)擁有一系列寄存器,其存取速度比計(jì)算機(jī)主存更快。好的編譯程序能進(jìn)行一種被稱為“冗余裝入和存儲的刪去”(redundant load and store removal)的優(yōu)化,即編譯程序會·在程序中尋找并刪去這樣兩類代碼:一類是可以刪去的從內(nèi)存裝入數(shù)據(jù)的指令,因?yàn)橄鄳?yīng)的數(shù)據(jù)已經(jīng)被存放在寄存器中;另一種是可以刪去的將數(shù)據(jù)存入內(nèi)存的指令,因?yàn)橄鄳?yīng)的數(shù)據(jù)在再次被改變之前可以一直保留在寄存器中。
如果一個指針變量指向普通內(nèi)存以外的位置,如指向一個外圍設(shè)備的內(nèi)存映射端口,那么冗余裝入和存儲的優(yōu)化對它來說可能是有害的。例如,為了調(diào)整某個操作的時間,可能會用到下述函數(shù):
time_t time_addition(volatile const struct timer * t, int a),
{
int n
int x
time_t then
x=O;
then= t->value
for (n=O; n<1O00; n++)
{
x=x+a ;
}
return t->value - then;
}
在上述函數(shù)中,變量t->value實(shí)際上是一個硬件計(jì)數(shù)器,其值隨時間增加。該函數(shù)執(zhí)行1000次把a(bǔ)值加到x上的操作,然后返回t->value在這1000次加法的執(zhí)行期間所增加的值。
如果不使用volatile修飾符,一個聰明的編譯程序可能就會認(rèn)為t->value在該函數(shù)執(zhí)行期間不會改變,因?yàn)樵摵瘮?shù)內(nèi)沒有明確地改變t->value的語句。這樣,編譯程序就會認(rèn)為沒有必要再次從內(nèi)存中讀入t->value并將其減去then,因?yàn)榇鸢赣肋h(yuǎn)是0。因此,編譯程序可能會對該函數(shù)進(jìn)行“優(yōu)化”,結(jié)果使得該函數(shù)的返回值永遠(yuǎn)是0。
如果一個指針變量指向共享內(nèi)存中的數(shù)據(jù),那么冗余裝入和存儲的優(yōu)化對它來說可能也是有害的,共享內(nèi)存通常用來實(shí)現(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)該使用類型強(qiáng)制轉(zhuǎn)換(typecast)?
可以。const修飾符的含義是變量的值不能被使用了const修飾符的那段代碼修改,但這并不意味著它不能被這段代碼以外的其它手段修改。例如,在2.6的例子中,通過一個volatile const指針t來存取timer結(jié)構(gòu)。函數(shù)time_addition()本身并不修改t->value的值,因此t->value被說明為const。不過,計(jì)算機(jī)的硬件會修改這個值,因此t->value又被說明為volatile。如果同時用const和volatile來說明一個變量,那么這兩個修飾符隨便哪個在先都行,
請參見:
2.6什么時候應(yīng)該使用volatile修飾符?
2.8什么時候應(yīng)該使用const修飾符?
2.14什么時候不應(yīng)該使用類型強(qiáng)制轉(zhuǎn)換(typecast)?
使用const修飾符有幾個原因,第一個原因是這樣能使編譯程序找出程序中不小心改變變量值的錯誤。請看下例:
while ( * str=0) / * programmer meant to write * str! =0 * /
{
/ * some code here * /
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í)際編程中,只有在編譯程序通過指針存取這些數(shù)據(jù)的效率比拷貝這些數(shù)據(jù)更高時,才把這些參數(shù)說明為const。
請參見:
2.7 一個變量可以同時被說明為const和volatile嗎?
2.14 什么時候不應(yīng)該使用類型強(qiáng)制轉(zhuǎn)換(typecast)?
2.18用const說明常量有什么好處?
浮點(diǎn)數(shù)是計(jì)算機(jī)編程中的“魔法(black art)”,原因之一是沒有一種理想的方式可以表示一個任意的數(shù)字。電子電氣工程協(xié)會(IEEE)已經(jīng)制定出浮點(diǎn)數(shù)的表示標(biāo)準(zhǔn),但你不能保證所使用的每臺機(jī)器都遵循這一標(biāo)準(zhǔn)。
即使你使用的機(jī)器遵循這一標(biāo)準(zhǔn),還存在更深的問題。從數(shù)學(xué)意義上講,兩個不同的數(shù)字之間有無窮個實(shí)數(shù)。計(jì)算機(jī)只能區(qū)分至少有一位(bit)不同的兩個數(shù)字。如果要表示那些無窮無盡的各不相同的數(shù)字,就要使用無窮數(shù)目的位。計(jì)算機(jī)只能用較少的位(通常是32位或64位)來表示一個很大的范圍內(nèi)的數(shù)字,因此它只能近似地表示大多數(shù)數(shù)字。
由于浮點(diǎn)數(shù)是如此難對付,因此比較一個浮點(diǎn)數(shù)和某個值是否相等或不等通常是不好的編程習(xí)慣。但是,判斷一個浮點(diǎn)數(shù)是否大于或小于某個值就安全多了。例如,如果你想以較小的步長依次使用一個范圍內(nèi)的數(shù)字,你可能會編寫這樣一個程序:
#include <stdio.h>
const float first = O.O;
const float last = 70.0
const float small= O.007
main ( )
{
float f;
for (f=first; f !=last && f<last+1.O; f +=small)
printf("f is now %gn", f);
}
然而,舍入誤差(rounding error)和變量small的表示誤差可能導(dǎo)致f永遠(yuǎn)不等于last(f可能會從稍小于last的一個數(shù)增加到一個稍大于last的數(shù)),這樣,循環(huán)會跳過last。加入不等式"f<last+1.0"就是為了防止在這種情況發(fā)生后程序繼續(xù)運(yùn)行很長時間。如果運(yùn)行該程序并且被打印出來的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)計(jì)數(shù):
float f;
int count=(last-first)/small;
for(f=first;count-->0;f+=small)
;
請參見:
2.11 對不同類型的變量進(jìn)行算術(shù)運(yùn)算會有問題嗎?
要判斷某種特定類型可以容納的最大值或最小值,一種簡便的方法是使用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的補(bǔ)碼運(yùn)算的機(jī)器(你將使用的機(jī)器幾乎都屬此類)上,一個有符號類型可以容納的數(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.6 16位和32位的數(shù)是怎樣存儲的?
C有三類固有的數(shù)據(jù)類型:指針類型、整數(shù)類型和浮點(diǎn)類型;
指針類型的運(yùn)算限制最嚴(yán),只限于以下兩種運(yùn)算:
- 兩個指針相減,僅在兩個指針指向同一數(shù)組中的元素時有效。運(yùn)算結(jié)果與對應(yīng)于兩個指針的數(shù)組下標(biāo)相減的結(jié)果相同。
+ 指針和整數(shù)類型相加。運(yùn)算結(jié)果為一個指針,該指針與原指針之間相距n個元素,n就是與原指針相加的整數(shù)。
浮點(diǎn)類型包括float,double和longdouble這三種固有類型。整數(shù)類型包括char,unsigned char,short,unsigned short,int,unsigned int,long和unsigned long。對這些類型都可進(jìn)行以下4種算術(shù)運(yùn)算:
+ 加
- 減
* 乘
/ 除
對整數(shù)類型不僅可以進(jìn)行上述4種運(yùn)算,還可進(jìn)行以下幾種運(yùn)算:
% 取模或求余
>> 右移
<< 左移
& 按位與
| 按位或
^ 按位異或
! 邏輯非
~ 取反
盡管C允許你使用“混合模式”的表達(dá)式(包含不同類型的算術(shù)表達(dá)式),但是,在進(jìn)行運(yùn)算之前,它會把不同的類型轉(zhuǎn)換成同一類型(前面提到的指針運(yùn)算除外)。這種自動轉(zhuǎn)換類型的過程被稱為“運(yùn)算符升級(operator promotion)”。
請參見:
2.12什么是運(yùn)算符升級(operatorpromotion)?
當(dāng)兩個不同類型的運(yùn)算分量(operand)進(jìn)行運(yùn)算時,它們會被轉(zhuǎn)換為能容納它們的最小的類型,并且運(yùn)算結(jié)果也是這種類型。下表列出了其中的規(guī)則,在應(yīng)用這些規(guī)則時,你應(yīng)該從表的頂端開始往下尋找,直到找到第一條適用的規(guī)則。
-------------------------------------------------------------
運(yùn)算分量1 運(yùn)算分量2 轉(zhuǎn)換結(jié)果
-------------------------------------------------------------
long double 其它任何類型 long double
double 任何更小的類型 double
float 任何更小的類 float
unsigned long 任何整數(shù)類 unsigned long
long unsigned>LONG_MAX unsigned long
long 任何更小的類型 long
unsigned 任何有符號類型 unsigned
-------------------------------------------------------------
下面的程序中就有幾個運(yùn)算符升級的例子。變量n被賦值為3/4,因?yàn)?和4都是整數(shù),所以先進(jìn)行整數(shù)除法運(yùn)算,結(jié)果為整數(shù)0。變量f2被賦值為3/4.0,因?yàn)?.0是一個float類型,所以整數(shù)3也被轉(zhuǎn)換為float類型,結(jié)果為float類型0.75。
#include <stdio.h>
main ()
{
float f1 = 3/4;
float f2 = 3/4.0
printf("3/4== %g or %g depending on the type used. n",f1, f2);
}
請參見:
2.11對不同類型的變量進(jìn)行算術(shù)運(yùn)算會有問題嗎?
2.13什么時候應(yīng)該使用類型強(qiáng)制轉(zhuǎn)換(typecast)?
在兩種情況下需要使用類型強(qiáng)制轉(zhuǎn)換。第一種情況是改變運(yùn)算分量的類型,從而使運(yùn)算能正確地進(jìn)行。下面的程序與2.12中的例子相似,但有不同之處。變量n被賦值為整數(shù)i除以整數(shù)j的結(jié)果,因?yàn)槭钦麛?shù)相除,所以結(jié)果為0。變量f2也被賦值為i除以j的結(jié)果,但本例通過(float)類型強(qiáng)制轉(zhuǎn)換把i轉(zhuǎn)換成一個float類型,因此執(zhí)行的是浮點(diǎn)數(shù)除法運(yùn)算(見2.11),結(jié)果為0.75。
#include <stdio.h>
main ( )
{
int i = 3;
int j = 4
float f1 =i/j;
float f2= (float) i/j;
printf("3/4== %g or %g depending on the type used. n",f1, f2);
}
第二種情況是在指針類型和void * 類型之間進(jìn)行強(qiáng)制轉(zhuǎn)換,從而與期望或返回void指針的函數(shù)進(jìn)行正確的交接。例如,下述語句就把函數(shù)malloc()的返回值強(qiáng)制轉(zhuǎn)換為一個指向foo結(jié)構(gòu)的指針:
struct foo *p=(struct foo *)malloc(sizeof(struct foo));
請參見:
2.6什么時候應(yīng)該使用volatile修飾符?
2.8什么時候應(yīng)該使用const修飾符?
2.11對不同類型的變量進(jìn)行算術(shù)運(yùn)算會有問題嗎?
2.12 什么是運(yùn)算符升級(operator promotion)?
2.14 什么時候不應(yīng)該使用類型強(qiáng)制轉(zhuǎn)換(typecast)?
7.5 什么是void指針?
7.6 什么時候使用void指針?
7.21 什么是堆(heap)?
7.27 可以對void指針進(jìn)行算術(shù)運(yùn)算嗎?
不應(yīng)該對用const或volatile說明了的對象進(jìn)行類型強(qiáng)制轉(zhuǎn)換,否則程序就不能正確運(yùn)行。
不應(yīng)該用類型強(qiáng)制轉(zhuǎn)換把指向一種結(jié)構(gòu)類型或數(shù)據(jù)類型的指針轉(zhuǎn)換成指向另一種結(jié)構(gòu)類型或數(shù)據(jù)類型的指針。在極少數(shù)需要進(jìn)行這種類型強(qiáng)制轉(zhuǎn)換的情況下,用共用體(union)來存放有關(guān)數(shù)據(jù)能更清楚地表達(dá)程序員的意圖。
請參見:
2. 6什么時候應(yīng)該使用volatile修飾符?
2. 8什么時候應(yīng)該使用const修飾符?
被多個文件存取的全局變量可以并且應(yīng)該在一個頭文件中說明,并且必須在一個源文件中定義。變量不應(yīng)該在頭文件中定義,因?yàn)橐粋€頭文件可能被多個源文件包含,而這將導(dǎo)致變量被多次定義。如果變量的初始化只發(fā)生一次,ANSIC標(biāo)準(zhǔn)允許變量有多次外部定義;但是,這樣做沒有任何好處,因此最好避免這樣做,以使程序有更強(qiáng)的可移植性。
注意:變量的說明和定義是兩個不同的概念,在2.16中將講解兩者之間的區(qū)別。
僅供一個文件使用的“全局”變量應(yīng)該被說明為static,而且不應(yīng)該出現(xiàn)在頭文件中。
請參見:
2. 16 說明一個變量和定義一個變量有什么區(qū)別?
2. 17 可以在頭文件中說明static變量嗎?
說明一個變量意味著向編譯程序描述變量的類型,但并不為變量分配存儲空間。定義一個變量意味著在說明變量的同時還要為變量分配存儲空間。在定義一個變量的同時還可以對變量進(jìn)行初始化。下例說明了一個變量和一個結(jié)構(gòu),定義了兩個變量,其中一個定義帶初始化:
extern int decll; / * this is a declaration * /
struct decl2 {
int member;
} ; / * this just declares the type--no variable mentioned * /
int def1 = 8; / * this is a definition * /
int def2; / * this is a definition * /
換句話說,說明一個變量相當(dāng)于告訴編譯程序“在程序的某個位置將用到一個變量,這里給出了它的名稱和類型”,定義一個變量則相當(dāng)于告訴編譯程序“具有這個名稱和這種類型的變量就在這里”。
一個變量可以被說明許多次,但只能被定義一次。因此,不應(yīng)該在頭文件中定義變量,因?yàn)橐粋€頭文件可能會被一個程序的許多源文件所包含。
請參見;
2.17可以在頭文件中說明static變量嗎?
如果說明了一個static變量,就必須在同一個文件中定義該變量(因?yàn)榇鎯︻愋托揎椃鹲tatic和extern是互斥的)。你可以在頭文件中定義一個static變量,但這會使包含該頭文件的源文件都得到該變量的一份私有拷貝,而這通常不是你想得到的結(jié)果。
請參見:
2.16 說明一個變量和定義一個變量有什么區(qū)別?
使用關(guān)鍵字const有兩個好處;第一,如果編譯程序知道一個變量的值不會改變,編譯程.序就能對程序進(jìn)行優(yōu)化;第二,編譯程序會試圖保證該變量的值不會因?yàn)槌绦騿T的疏忽而被改變。
當(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)該使用類型強(qiáng)制轉(zhuǎn)換(typecast)?
9.9 字符串和數(shù)組有什么不同?
聯(lián)系客服