C語言do、while、for關鍵字—循環
C 語言中循環語句有三種:while 循環、do-while 循環、for 循環。while 循環:先判斷while 后面括號里的值,如果為真則執行其后面的代碼;否則不執行。while(1)表示死循環。死循環有沒有用呢?
看下面例子:比如你開發一個系統要日夜不停的運行,但是只有操作員輸入某個特定的字符‘#’才可以停下來。
while(1)
{
? ?if(‘#’== GetInputChar())
? ?{
? ? ? break;
? ?}
}
一、break 與continue 的區別
break 關鍵字很重要,表示終止本層循環。現在這個例子只有一層循環,當代碼執行到break 時,循環便終止。如果把break 換成continue 會是什么樣子呢? continue 表示終止本次(本輪)循環。當代碼執行到continue 時,本輪循環終止,進入下一輪循環。
while(1)也有寫成while(true) 或者while(1==1) 或者while((bool) 1)等形式的,效果一樣。
do-while 循環:先執行do 后面的代碼,然后再判斷while 后面括號里的值,如果為真,循環開始;否則,循環不開始。其用法與while 循環沒有區別,但相對較少用。
for 循環:for 循環可以很容易的控制循環次數,多用于事先知道循環次數的情況下。
留一個問題:在switch case 語句中能否使用continue 關鍵字?為什么?
二、循環語句的注意點
1、在多重循環中,如果有可能,應當將最長的循環放在最內層,最短的循環放在最外層,以減少CPU 跨切循環層的次數。例如:
半開半閉區間寫法和閉區間寫法雖然功能是相同,但相比之下,半開半閉區間寫法寫法更加直觀。


如果你寫的一個循環的代碼超過一顯示屏,那會讓讀代碼的人發狂的。 解決的辦法由兩個:第一,重新設計這個循環,確認是否這些操作都必須放在這個循環里;第二,將這些代碼改寫成一個子函數,循環中只調用這個子函數即可。一般來說循環內的代碼不要超過20行。
5、把循環嵌套控制在3 層以內。
國外有研究數據表明,當循環嵌套超過3 層,程序員對循環的理解能力會極大的降低。如果你的循環嵌套超過3 層,建議你重新設計循環或是將循環內的代碼改寫成一個字函數。
C語言switch、case完美組合
既然有了if、else 組合為什么還需要switch、case 組合呢?一、不要拿青龍偃月刀去削蘋果
那你既然有了菜刀為什么還需要水果刀呢?你總不能扛著云長的青龍偃月刀(又名冷艷鋸)去削蘋果吧。如果你真能做到,關二爺也會佩服你的。^_^。if、else 一般表示兩個分支或是嵌套表示少量的分支,但如果分支很多的話……還是用switch、case 組合吧。其基本格式為:
switch(variable)
{
? ?case Value1:
? ? ? //program code
? ? ? break;
? ?case Value2:
? ? ? //program code
? ? ? break;
? ?case Value3:
? ? ? //program code
? ? ? break;
? ?…
? ?default:
? ? ? break;
}
很簡單,但有兩個規則:
- 每個case 語句的結尾絕對不要忘了加break,否則將導致多個分支重疊(除非有意使多個分支重疊)。
- 最后必須使用default 分支。即使程序真的不需要default 處理,也應該保留語句:
? ?default :
? ?break;
這樣做并非畫蛇添足,可以避免讓人誤以為你忘了default 處理。
二、case 關鍵字后面的值有什么要求嗎?
好,再問問:真的就這么簡單嗎?看看下面的問題:Value1 的值為0.1 行嗎?-0.1 呢?-1 呢?0.1+0.9 呢? 1+2 呢?3/2 呢?‘A’呢?“A”呢?變量i(假設i 已經被初始化)呢?NULL 呢?等等。這些情形希望你親自上機調試一下,看看到底哪些行,哪些不行。
記住:case 后面只能是整型或字符型的常量或常量表達式(想想字符型數據在內存里是怎么存的)。
三、case 語句的排列順序
似乎從來沒有人考慮過這個問題,也有很多人認為case 語句的順序無所謂。但事實卻不是如此。如果case 語句很少,你也許可以忽略這點,但是如果case 語句非常多,那就不得不好好考慮這個問題了。比如你寫的是某個驅動程序,也許會經常遇到幾十個case 語句的情況。一般來說,我們可以遵循下面的規則:1、按字母或數字順序排列各條case 語句。
如果所有的case 語句沒有明顯的重要性差別,那就按A-B-C 或1-2-3 等順序排列case語句。這樣做的話,你可以很容易的找到某條case 語句。比如:
switch(variable)
{
? ?case A:
? ? ? //program code
? ? ? break;
? ?case B:
? ? ? //program code
? ? ? break;
? ?case C:
? ? ? //program code
? ? ? break;
? ?…
? ?default:
? ?break;
}
2、把正常情況放在前面,而把異常情況放在后面。
如果有多個正常情況和異常情況,把正常情況放在前面,并做好注釋;把異常情況放在后面,同樣要做注釋。比如:
switch(variable)
{
? ?///
? ?//正常情況開始
? ?case A:
? ? ? //program code
? ? ? break;
? ?case B:
? ? ? //program code
? ? ? break;
? ?//正常情況結束
? ?/
? ?//異常情況開始
? ?case -1:
? ? ? //program code
? ? ? break;
? ?//異常情況結束
? ?//
? ?…
? ?default:
? ? ? break;
}
3、按執行頻率排列case 語句:把最常執行的情況放在前面,而把最不常執行的情況放在后面。
最常執行的代碼可能也是調試的時候要單步執行的最多的代碼。如果放在后面的話,找起來可能會比較困難,而放在前面的話,可以很快的找到。
三、使用case 語句的其他注意事項
1、簡化每種情況對應的操作。使得與每種情況相關的代碼盡可能的精煉。case 語句后面的代碼越精煉,case 語句的結果就會越清晰。你想想,如果case 語句后面的代碼整個屏幕都放不下,這樣的代碼誰也難看得很清晰吧。如果某個case 語句確實需要這么多的代碼來執行某個操作,那可以把這些操作寫成一個或幾個子程序,然后在case 語句后面調用這些子程序就ok 了。 一般來說case語句后面的代碼盡量不要超過20 行。
2、不要為了使用case 語句而刻意制造一個變量。
case 語句應該用于處理簡單的,容易分類的數據。如果你的數據并不簡單,那可能使用ifelseif 的組合更好一些。 為了使用case 而刻意構造出來的變量很容易把人搞糊涂,應該避免這種變量。比如:
char action = a[0];
switch (action)
{
? ?case ‘c’:
? ? ? fun1();
? ? ? break;
? ?case ‘d’:
? ? ? …
? ? ? break;
? ?default:
? ? ? break;
}
這里控制case 語句的變量是action。而action 的值是取字符數組a 的一個字符。但是這種方式可能帶來一些隱含的錯誤。一般而言,當你為了使用case 語句而刻意去造出一個變量時,真正的數據可能不會按照你所希望的方式映射到case 語句里。在這個例子中,如果用戶輸入字符數組a 里面存的是“const”這個字符串,那么case 語句會匹配到第一個case上,并調用fun1()函數。然而如果這個數組里存的是別的以字符c 開頭的任何字符串(比如:“col”,“can”),case 分支同樣會匹配到第一個case 上。但是這也許并不是你想要的結果,這個隱含的錯誤往往使人抓狂。如果這樣的話還不如使用if-else if 組合。比如:
if(0 == strcmp(“const”,a))
{
? ?fun1();
}
else if
{
? ?…
}
三、把default 子句只用于檢查真正的默認情況。
有時候,你只剩下了最后一種情況需要處理,于是就決定把這種情況用default 子句來處理。這樣也許會讓你偷懶少敲幾個字符,但是這卻很不明智。這樣將失去case 語句的標號所提供的自說明功能,而且也喪失了使用default 子句處理錯誤情況的能力。所以,奉勸你不要偷懶,老老實實的把每一種情況都用case 語句來完成,而把真正的默認情況的處理交給default 子句。
C語言if、else超級組合
if 語句很簡單吧。嗯,的確很簡單。那我們就簡單的看下面幾個簡單的問題:一、bool 變量與“零值”進行比較
bool 變量與“零值”進行比較的if 語句怎么寫?bool bTestFlag = FALSE;//想想為什么一般初始化為FALSE 比較好?A), if(bTestFlag == 0); if(bTestFlag == 1);
B), if(bTestFlag == TRUE); if(bTestFlag == FLASE);
C), if(bTestFlag); if(!bTestFlag);
哪一組或是那些組正確呢?我們來分析分析:
A)寫法:bTestFlag 是什么?整型變量?如果要不是這個名字遵照了前面的命名規范,肯怕很容易讓人誤會成整型變量。所以這種寫法不好。
B)寫法:FLASE 的值大家都知道,在編譯器里被定義為0;但TRUE 的值呢?都是1嗎?很不幸,不都是1。Visual C++定義為1,而它的同胞兄弟Visual Basic 就把TRUE 定義為-1.那很顯然,這種寫法也不好。
大家都知道if 語句是靠其后面的括號里的表達式的值來進行分支跳轉的。表達式如果為真,則執行if 語句后面緊跟的代碼;否則不執行。那顯然,本組的寫法很好,既不會引起誤會,也不會由于TRUE 或FLASE 的不同定義值而出錯。記住:以后寫代碼就得這樣寫。
二、float 變量與“零值”進行比較
float 變量與“零值”進行比較的if 語句怎么寫?? ?float fTestVal = 0.0;
A), if(fTestVal == 0.0); if(fTestVal != 0.0);
B), if((fTestVal >= -EPSINON) && (fTestVal <= EPSINON)); //EPSINON 為定義好的精度。
哪一組或是那些組正確呢?我們來分析分析:
float 和double 類型的數據都是有精度限制的,這樣直接拿來與0.0 比,能正確嗎?明顯不能,看例子: ? 的值四舍五入精確到小數點后10位為:3.1415926536,你拿它減去0.00000000001 然后再四舍五入得到的結果是多少?你能說前后兩個值一樣嗎?
EPSINON 為定義好的精度,如果一個數落在[0.0-EPSINON,0.0+EPSINON] 這個閉區間內,我們認為在某個精度內它的值與零值相等;否則不相等。擴展一下,把0.0 替換為你想比較的任何一個浮點數,那我們就可以比較任意兩個浮點數的大小了,當然是在某個精度內。
同樣的也不要在很大的浮點數和很小的浮點數之間進行運算,比如:
? ?10000000000.00 + 0.00000000001
這樣計算后的結果可能會讓你大吃一驚。
三、指針變量與“零值”進行比較
指針變量與“零值”進行比較的if 語句怎么寫?? ?int* p = NULL;//定義指針一定要同時初始化,指針與數組那章會詳細講解。
A), if(p == 0); if(p != 0);
B), if(p); if(!p);
C) , if(NULL == p); if(NULL != p);
哪一組或是那些組正確呢?我們來分析分析:
A)寫法:p 是整型變量?容易引起誤會,不好。盡管NULL 的值和0 一樣,但意義不同。
B)寫法:p 是bool 型變量?容易引起誤會,不好。
C)寫法:這個寫法才是正確的,但樣子比較古怪。為什么要這么寫呢?是怕漏寫一個“=”號:if(p = NULL),這個表達式編譯器當然會認為是正確的,但卻不是你要表達的意思。 所以,非常推薦這種寫法。
四、else 到底與哪個if 配對呢?
else 常常與if 語句配對,但要注意書寫規范,看下面例子:if(0 == x)
if(0 == y) error();
else
{
? ?//program code
}
這個else 到底與誰匹配呢?讓人迷糊,尤其是初學者。還好,C 語言有這樣的規定:else始終與同一括號內最近的未匹配的if 語句結合。雖然老手可以區分出來,但這樣的代碼誰都會頭疼的,任何時候都別偷這種懶。關于程序中的分界符‘{’和‘}’,建議如下:
提示:程序中的分界符‘{’和‘}’對齊風格如下:
注意下表中代碼的縮進一般為4 個字符,但不要使用Tab 鍵,因為不同的編輯器Tab 鍵定義的空格數量不一樣,別的編輯器打開Tab 鍵縮進的代碼可能會一片混亂。



五、if 語句后面的分號
關于if-else 語句還有一個容易出錯的地方就是與空語句的連用。看下面的例子:? ?if(NULL != p) ;
? ?fun();
這里的fun()函數并不是在NULL != p 的時候被調用,而是任何時候都會被調用。問題就出在if 語句后面的分號上。在C 語言中,分號預示著一條語句的結尾,但是并不是每條C 語言語句都需要分號作為結束標志。if 語句的后面并不需要分號,但如果你不小心寫了個分號,編譯器并不會提示出錯。因為編譯器會把這個分號解析成一條空語句。也就是上面的代碼實際等效于:
if(NULL != p)
{
? ?;
}
fun();
這是初學者很容易犯的錯誤,往往不小心多寫了個分號,導致結果與預想的相差很遠。所以建議在真正需要用空語句時寫成這樣:
? ?NULL;
而不是單用一個分號。這就好比匯編語言里面的空指令,比如ARM 指令中的NOP 指令。這樣做可以明顯的區分真正必須的空語句和不小心多寫的分號。
六、使用if 語句的其他注意事項
1、先處理正常情況,再處理異常情況。在編寫代碼是,要使得正常情況的執行代碼清晰,確認那些不常發生的異常情況處理代碼不會遮掩正常的執行路徑。這樣對于代碼的可讀性和性能都很重要。因為,if 語句總是需要做判斷,而正常情況一般比異常情況發生的概率更大(否則就應該把異常正常調過來了),如果把執行概率更大的代碼放到后面,也就意味著if 語句將進行多次無謂的比較。 另外,非常重要的一點是,把正常情況的處理放在if 后面,而不要放在else 后面。當然這也符合把正常情況的處理放在前面的要求。
2、確保if 和else 子句沒有弄反。
這一點初學者也容易弄錯,往往把本應該放在if 語句后面的代碼和本應該放在else 語句后面的代碼弄反了。
C語言ned、unsigned關鍵字
我們知道計算機底層只認識0、1.任何數據到了底層都會變計算轉換成0、1.那負數怎么存儲呢?肯定這個“-”號是無法存入內存的,怎么辦?很好辦,做個標記。把基本數據類型的最高位騰出來,用來存符號,同時約定如下:最高位如果是1,表明這個數是負數,其值為除最高位以外的剩余位的值添上這個“-”號;如果最高位是0,表明這個數是正數,其值為除最高位以外的剩余位的值。
這樣的話,一個32位的signed int類型整數其值表示法范圍為:- 231~231 -1;8 位的char類型數其值表示的范圍為- 27~27 -1。一個32位的unsigned int類型整數其值表示法范圍為:0~ 232 -1;8位的char類型數其值表示的范圍為0~28 -1。同樣我們的signed 關鍵字也很寬恒大量,你也可以完全當它不存在,編譯器缺省默認情況下數據為signed 類型的。
上面的解釋很容易理解,下面就考慮一下這個問題:
int main()
{
? ?char a[1000];
? ?int i;
? ?for(i=0; i<1000; i++)
? ?{
? ? ? a[i] = -1-i;
? ?}
? ?printf("%d",strlen(a));
? ?return 0;
}
此題看上去真的很簡單,但是卻鮮有人答對。答案是255。別驚訝,我們先分析分析。
for 循環內,當i 的值為0 時,a[0]的值為-1。關鍵就是-1 在內存里面如何存儲。
我們知道在計算機系統中,數值一律用補碼來表示(存儲)。主要原因是使用補碼,可以將符號位和其它位統一處理;同時,減法也可按加法來處理。另外,兩個用補碼表示的數相加時,如果最高位(符號位)有進位,則進位被舍棄。正數的補碼與其原碼一致;負數的補碼:符號位為1,其余位為該數絕對值的原碼按位取反,然后整個數加1。
按照負數補碼的規則,可以知道-1 的補碼為0xff,-2 的補碼為0xfe……當i 的值為127時,a[127]的值為-128,而-128 是char 類型數據能表示的最小的負數。當i 繼續增加,a[128]的值肯定不能是-129。因為這時候發生了溢出,-129 需要9 位才能存儲下來,而char 類型數據只有8 位,所以最高位被丟棄。剩下的8 位是原來9 位補碼的低8 位的值,即0x7f。
當i 繼續增加到255 的時候,-256 的補碼的低8 位為0。然后當i 增加到256 時,-257 的補碼的低8 位全為1,即低八位的補碼為0xff,如此又開始一輪新的循環……
按照上面的分析,a[0]到a[254]里面的值都不為0,而a[255]的值為0。strlen 函數是計算字符串長度的,并不包含字符串最后的‘\0’。而判斷一個字符串是否結束的標志就是看是否遇到‘\0’。如果遇到‘\0’,則認為本字符串結束。
分析到這里,strlen(a)的值為255 應該完全能理解了。 這個問題的關鍵就是要明白char類型默認情況下是有符號的,其表示的值的范圍為[-128,127],超出這個范圍的值會產生溢出。另外還要清楚的就是負數的補碼怎么表示。弄明白了這兩點,這個問題其實就很簡單了。
留三個問題:
1)按照我們上面的解釋,那-0 和+0 在內存里面分別怎么存儲?
2)
int i = -20;
unsigned j = 10;
i+j
的值為多少?為什么?
3)?
下面的代碼有什么問題?
unsigned i ;
for (i=9;i>=0;i--)
{
? ?printf("%u\n",i);
}
C語言sizeof關鍵字—被冤枉的關鍵字
一、常年被人誤認為函數。
sizeof 是關鍵字不是函數,其實就算不知道它是否為32 個關鍵字之一時,我們也可以借助編譯器確定它的身份。看下面的例子:? ?int i=0;
A),sizeof(int); B),sizeof(i); C),sizeof int; D),sizeof i;
毫無疑問,32 位系統下A),B)的值為4。那C)的呢?D)的呢?在32 位系統下,通過Visual C++6.0 或任意一編譯器調試,我們發現D)的結果也為4。
咦?sizeof 后面的括號呢?沒有括號居然也行,那想想,函數名后面沒有括號行嗎?由此輕易得出sizeof 絕非函數。
好,再看C)。編譯器怎么怎么提示出錯呢?不是說sizeof 是個關鍵字,其后面的括號可以沒有么?那你想想sizeof int 表示什么啊?int 前面加一個關鍵字?類型擴展?明顯不正確,我們可以在int 前加unsigned,const 等關鍵字但不能加sizeof。好,記住:sizeof 在計算變量所占空間大小時,括號可以省略,而計算類型(模子)大小時不能省略。一般情況下,咱也別偷這個懶,乖乖的寫上括號,繼續裝作一個“函數”,做一個“披著函數皮的關鍵字”。
做我的關鍵字,讓人家認為是函數去吧。
二、sizeof(int)*p 表示什么意思?
留幾個問題(講解指針與數組時會詳細講解),32 位系統下:? ?int *p = NULL;
sizeof(p)的值是多少?
sizeof(*p)呢?
? ?int a[100];
sizeof (a) 的值是多少?
sizeof(a[100])呢?//請尤其注意本例。
sizeof(&a)呢?
sizeof(&a[0])呢?
int b[100];
void fun(int b[100])
{
? ?sizeof(b);// sizeof (b) 的值是多少?
}
c語言基本數據類型short、int、long、char、float、double
C 語言包含的數據類型如下圖所示:
short、int、long、char、float、double 這六個關鍵字代表C 語言里的六種基本數據類型。
怎么去理解它們呢? 舉個例子:見過藕煤球的那個東西吧?(沒見過?煤球總見過吧)。那個東西叫藕煤器,拿著它在和好的煤堆里這么一咔,一個煤球出來了。半徑12cm,12 個孔。不同型號的藕煤器咔出來的煤球大小不一樣,孔數也不一樣。這個藕煤器其實就是個模子。
現在我們聯想一下,short、int、long、char、float、double 這六個東東是不是很像不同類型的藕煤器啊?拿著它們在內存上咔咔咔,不同大小的內存就分配好了,當然別忘了給它們取個好聽的名字。
在32 位的系統上short 咔出來的內存大小是2 個byte;
int 咔出來的內存大小是4 個byte;
long 咔出來的內存大小是4 個byte;
float 咔出來的內存大小是4 個byte;
double 咔出來的內存大小是8 個byte;
char 咔出來的內存大小是1 個byte。
(注意這里指一般情況,可能不同的平臺還會有所不同,具體平臺可以用sizeof 關鍵字測試一下)
很簡單吧?咔咔咔很爽吧?是很簡單,也確實很爽,但問題就是你咔出來這么多內存塊,你總不能給他取名字叫做x1,x2,x3,x4,x5…或者長江1 號,長江2 號…吧。它們長得這么像(不是你家的老大,老二,老三…),過一陣子你就會忘了到底哪個名字和哪個內存塊匹配了(到底誰嫁給誰了啊?^_^)。所以呢,給他們取一個好的名字絕對重要。下面我們就來研究研究取什么樣的名字好。
二、變量的命名規則
1、命名應當直觀且可以拼讀,可望文知意,便于記憶和閱讀。
標識符最好采用英文單詞或其組合,不允許使用拼音。程序中的英文單詞一般不要太復雜,用詞應當準確。
2、命名的長度應當符合“min-length && max-information”原則。
C 是一種簡潔的語言, 命名也應該是簡潔的。例如變量名MaxVal 就比MaxValueUntilOverflow 好用。標識符的長度一般不要過長,較長的單詞可通過去掉“元音”形成縮寫。
另外,英文詞盡量不縮寫,特別是非常用專業名詞,如果有縮寫,在同一系統中對同一單詞必須使用相同的表示法,并且注明其意思。
3、當標識符由多個詞組成時,每個詞的第一個字母大寫,其余全部小寫。比如:
? ?int CurrentVal;
這樣的名字看起來比較清晰,遠比一長串字符好得多。
4、盡量避免名字中出現數字編號,如Value1,Value2 等,除非邏輯上的確需要編號。比如驅動開發時為管腳命名,非編號名字反而不好。
初學者總是喜歡用帶編號的變量名或函數名,這樣子看上去很簡單方便,但其實是一顆顆定時炸彈。這個習慣初學者一定要改過來。
5、對在多個文件之間共同使用的全局變量或函數要加范圍限定符(建議使用模塊名(縮寫)作為范圍限定符)。
(GUI_ ,etc)標識符的命名規則:
6、 標識符名分為兩部分:規范標識符前綴(后綴) + 含義標識。非全局變量可以不用使用范圍限定符前綴。







10、程序中不得出現僅靠大小寫區分的相似的標識符。例如:
? ?int x, X; 變量x 與X 容易混淆
? ?void foo(int x); 函數foo 與FOO 容易混淆
? ?void FOO(float x);
這里還有一個要特別注意的就是1(數字1)和l(小寫字母l)之間,0(數字0)和o(小寫字母o)之間的區別。這兩對真是很難區分的,我曾經的一個同事就被這個問題折騰了一次。
11、一個函數名禁止被用于其它之處。例如:
#include "c_standards.h"
void foo(int p_1)
{
? ?int x = p_1;
}
void static_p(void)
{
? ?int foo = 1u;
}
12、所有宏定義、枚舉常數、只讀變量全用大寫字母命名,用下劃線分割單詞。例如:
const int MAX_LENGTH = 100; //這不是常量,而是一個只讀變量,具體請往后看
? ?#define FILE_PATH “/usr/tmp”
13、考慮到習慣性問題,局部變量中可采用通用的命名方式,僅限于n、i、j 等作為循環變量使用。
一定不要寫出如下這樣的代碼:
? ?int p;
? ?char i;
? ?int c;
? ?char * a;
一般來說習慣上用n,m,i,j,k 等表示int 類型的變量;c,ch 等表示字符類型變量;a 等表示數組;p 等表示指針。當然這僅僅是一般習慣,除了i,j,k 等可以用來表示循環變量外,別的字符變量名盡量不要使用。
14、定義變量的同時千萬千萬別忘了初始化。定義變量時編譯器并不一定清空了這塊內存,它的值可能是無效的數據。這個問題在內存管理那章有非常詳細的討論,請參看。
15、不同類型數據之間的運算要注意精度擴展問題,一般低精度數據將向高精度數據擴展。
C語static關鍵字—言最名不符實的關鍵字
不要誤以為關鍵字static 很安靜,其實它一點也不安靜。這個關鍵字在C 語言里主要有兩個作用,C++對它進行了擴展。一、修飾變量
第一個作用:修飾變量。變量又分為局部和全局變量,但它們都存在內存的靜態區。靜態全局變量,作用域僅限于變量被定義的文件中,其他文件即使用extern 聲明也沒法使用他。準確地說作用域是從定義之處開始,到文件結尾處結束,在定義之處前面的那些代碼行也不能使用它。想要使用就得在前面再加extern ***。惡心吧?要想不惡心,很簡單,直接在文件頂端定義不就得了。
靜態局部變量,在函數體里面定義的,就只能在這個函數里用了,同一個文檔中的其他函數也用不了。由于被static 修飾的變量總是存在內存的靜態區,所以即使這個函數運行結束,這個靜態變量的值還是不會被銷毀,函數下次使用時仍然能用到這個值。
static int j;
void fun1(void)
{
? ?static int i = 0;
? ?i ++;
}
void fun2(void)
{
? ?j = 0;
? ?j++;
}
intmain()
{
? ?for(k=0; k<10; k++)
? ?{
? ? ? fun1();
? ? ? fun2();
? ?}
? ?return 0;
}
i 和j 的值分別是什么,為什么?
二、修飾函數
第二個作用:修飾函數。函數前加static 使得函數成為靜態函數。但此處“static”的含義不是指存儲方式,而是指對函數的作用域僅局限于本文件(所以又稱內部函數)。使用內部函數的好處是:不同的人編寫不同的函數時,不用擔心自己定義的函數,是否會與其它文件中的函數同名。
關鍵字static 有著不尋常的歷史。起初,在C 中引入關鍵字static 是為了表示退出一個塊后仍然存在的局部變量。隨后,static 在C 中有了第二種含義:用來表示不能被其它文件訪問的全局變量和函數。為了避免引入新的關鍵字,所以仍使用static 關鍵字來表示這第二種含義。
當然,C++里對static 賦予了第三個作用,這里先不討論,有興趣的可以找相關資料研究。
C語言register關鍵字—最快的關鍵字
register:這個關鍵字請求編譯器盡可能的將變量存在CPU 內部寄存器中而不是通過內存尋址訪問以提高效率。注意是盡可能,不是絕對。你想想,一個CPU 的寄存器也就那么幾個或幾十個,你要是定義了很多很多register 變量,它累死也可能不能全部把這些變量放入寄存器吧,輪也可能輪不到你。一、皇帝身邊的小太監----寄存器
不知道什么是寄存器?那見過太監沒有?沒有?其實我也沒有。沒見過不要緊,見過就麻煩大了。^_^,大家都看過古裝戲,那些皇帝們要閱讀奏章的時候,大臣總是先將奏章交給皇帝旁邊的小太監,小太監呢再交給皇帝同志處理。這個小太監只是個中轉站,并無別的功能。好,那我們再聯想到我們的CPU。CPU 不就是我們的皇帝同志么?大臣就相當于我們的內存,數據從他這拿出來。那小太監就是我們的寄存器了(這里先不考慮CPU 的高速緩存區)。數據從內存里拿出來先放到寄存器,然后CPU 再從寄存器里讀取數據來處理,處理完后同樣把數據通過寄存器存放到內存里,CPU 不直接和內存打交道。這里要說明的一點是:小太監是主動的從大臣手里接過奏章,然后主動的交給皇帝同志,但寄存器沒這么自覺,它從不主動干什么事。一個皇帝可能有好些小太監,那么一個CPU 也可以有很多寄存器,不同型號的CPU 擁有寄存器的數量不一樣。
為啥要這么麻煩啊?速度!就是因為速度。寄存器其實就是一塊一塊小的存儲空間,只不過其存取速度要比內存快得多。進水樓臺先得月嘛,它離CPU 很近,CPU 一伸手就拿到數據了,比在那么大的一塊內存里去尋找某個地址上的數據是不是快多了?那有人問既然它速度那么快,那我們的內存硬盤都改成寄存器得了唄。我要說的是:你真有錢!
二、使用register 修飾符的注意點
雖然寄存器的速度非常快,但是使用register 修飾符也有些限制的:register 變量必須是能被CPU 寄存器所接受的類型。意味著register 變量必須是一個單個的值,并且其長度應小于或等于整型的長度。而且register 變量可能不存放在內存中,所以不能用取址運算符“&”來獲取register 變量的地址。
C語言auto—最寬恒大量的關鍵字
auto:它很寬恒大量的,你就當它不存在吧。編譯器在默認的缺省情況下,所有變量都是auto 的。
C語言關鍵字有哪些
每次講關鍵字之前,我總是問學生:C 語言有多少個關鍵字?sizeof 怎么用?它是函數嗎?有些學生不知道C 語言有多少個關鍵字,大多數學生往往告訴我sizeof 是函數,因為它后面跟著一對括號。當投影儀把這32 個關鍵字投到幕布上時,很多學生表情驚訝。有些關鍵字從來沒見過,有的驚訝C 語言關鍵字竟有32 個之多。更有甚者,說大學老師告訴他們sizeof 是函數,沒想到它居然是關鍵字!由此可想而知,大學的計算機教育是多么失敗!C 語言標準定義的32 個關鍵字:
auto 聲明自動變量,缺省時編譯器一般默認為auto
int 聲明整型變量
double 聲明雙精度變量
long 聲明長整型變量
char 聲明字符型變量
float 聲明浮點型變量
short 聲明短整型變量
signed 聲明有符號類型變量
unsigned 聲明無符號類型變量
struct 聲明結構體變量
union 聲明聯合數據類型
enum 聲明枚舉類型
static 聲明靜態變量
switch 用于開關語句
case 開關語句分支
default 開關語句中的“其他”分支
break 跳出當前循環
register 聲明寄存器變量
const 聲明只讀變量
volatile 說明變量在程序執行中可被隱含地改變
typedef 用以給數據類型取別名(當然還有其他作用)
extern 聲明變量是在其他文件正聲明(也可以看做是引用變量)
return 子程序返回語句(可以帶參數,也可不帶參數)
void 聲明函數無返回值或無參數,聲明空類型指針
continue 結束當前循環,開始下一輪循環
do 循環語句的循環體
while 循環語句的循環條件
if 條件語句
else 條件語句否定分支(與if 連用)
for 一種循環語句(可意會不可言傳)
goto 無條件跳轉語句
sizeof 計算對象所占內存空間大小
下面的篇幅就一一講解這些關鍵字。但在講解之前先明確兩個概念:
什么是定義?什么是聲明?它們有何區別?舉個例子:
A)int i;
B)extern int i;(關于extern,后面解釋)
哪個是定義?哪個是聲明?或者都是定義或者都是聲明?我所教過的學生幾乎沒有一人能回答上這個問題。這個十分重要的概念在大學里從來沒有被提起過!
什么是定義:所謂的定義就是(編譯器)創建一個對象,為這個對象分配一塊內存并給它取上一個名字,這個名字就是我們經常所說的變量名或對象名。但注意,這個名字一旦和這塊內存匹配起來(可以想象是這個名字嫁給了這塊空間,沒有要彩禮啊。^_^),它們就同生共死,終生不離不棄。并且這塊內存的位置也不能被改變。一個變量或對象在一定的區域內(比如函數內,全局等)只能被定義一次,如果定義多次,編譯器會提示你重復定義同一個變量或對象。
什么是聲明:有兩重含義。如下:
第一重含義:告訴編譯器,這個名字已經匹配到一塊內存上了(伊人已嫁,吾將何去何從?何以解憂,唯有稀粥),下面的代碼用到變量或對象是在別的地方定義的。聲明可以出現多次。
第二重含義:告訴編譯器,我這個名字我先預定了,別的地方再也不能用它來作為變量名或對象名。比如你在圖書館自習室的某個座位上放了一本書,表明這個座位已經有人預訂,別人再也不允許使用這個座位。其實這個時候你本人并沒有坐在這個座位上。這種聲明最典型的例子就是函數參數的聲明,例如:
? ?void fun(int i, char c);
好,這樣一解釋,我們可以很清楚的判斷:A)是定義;B)是聲明。那他們的區別也很清晰了。記住,定義聲明最重要的區別:定義創建了對象并為這個對象分配了內存,聲明沒有分配內存(一個抱伊人,一個喝稀粥。^_^)。