C語言易錯點總結
最近又把C語言看了一遍,發現了很多之前學C語言時沒有注意到但又很容易出錯的地方,現在總結出來和大家一起分享。可能有疏忽紕漏,歡迎大家指正。
一下分為幾個部分分別加以說明。
一、關鍵字
1.什麼是定義?什麼是宣告?兩者有何區別?
答:定義是建立一個物件,並未該物件分配一塊記憶體和取一個名字,這個名字就是變數名或者物件名;宣告是告訴編譯器這個變數或者物件的記憶體已經存在,這裡只是引用。兩者最重要的區別在於,定義建立了物件併為物件分配了記憶體,而宣告沒有分配記憶體。
2.static關鍵字的作用
答:static既可以修飾變數,也可以修飾函式,但兩者的作用不一樣,下面分別加以說明:
(1)static修飾變數
變數又分為全域性變數和區域性變數,被static修飾後的變數的記憶體在靜態儲存區,準確的說應該是 .data 段(《程式設計師自我修養》上有詳細介紹)
a)靜態全域性變數
對於一般的全域性變數,其生存週期和程式一樣,作用域為整個檔案,而且如果其他檔案想引用該全域性變數的話,可以使用extern關鍵字宣告即可。但是當全域性變數被static修飾以後,在原來基礎上唯一改變的是,其他檔案無法再通過extern進行引用。所以一般程式設計時都會加上static,這樣就不用擔心自己的變數和其他人定義的變數在命名上發生衝突了。
b)靜態區域性變數
靜態區域性變數的作用域和區域性變數一樣,只侷限於某個函式內,其他函式無法引用。但也有其特殊的地方,一般的區域性變數在函式呼叫完成以後會被系統自動釋放,但是對於靜態區域性變數其生存週期和全域性變數的週期一樣;靜態區域性變數只初始化一次,下次呼叫時保持上一次的值而不會重複初始化。
(2)static修飾函式
static修飾函式時,函式的作用域僅侷限於本檔案(所以又稱為內部函式)。使用內部函式的好處是不用擔心自己寫的函式和其他檔案的函式重名。
3.變數命名規則
從我寫程式開始,需要定義變數時一直都是看哪個字母閒著就用哪個,常用的有i,j,k......等。程式程式碼比較少時這樣寫也沒什麼能看懂,但是一旦程式程式碼很多時,再這樣給變數命名就會帶來很多不必要的麻煩。大家可能和我剛開始一樣覺得這是一個很小的問題,但是我發現,養成一個很好的命名習慣不是一下子就能做到的,需要自己在平時養成這樣的習慣,下面簡單說一下命名規則:
(1)命名時要保證最短的變數名,但要包含最大的資訊量;
(2)駝峰式命名,當識別符號由多個片語成時,每個詞的第一個字母大寫,其餘全部小寫;
(3)識別符號分為兩部分:字首_含義。下面列出常用的字首縮寫:
bool--bchar--cint--i short--s long--l unsigned--u double--u
pointer--p enum/struct/union--st function_pointer--fp array--*_a typedef enum/struct/union-- *_st
(4)所用的巨集定義、列舉常量和const修飾的常量全部大寫
4.關鍵字sizeof
首先要說明的一點是sizeof不是函式,而是一個關鍵字,是一個運算子。
sizeof計算變數所佔空間大小時,括號可以省略;但是就算某種資料型別的記憶體大小時不能省略。舉例如下:
int i= 0;
sizeof(int) sizeof(i) sizeof i //均正確
sizeof int //錯誤
5.if的條件表示式怎麼寫
if語句在程式中使用的很是頻繁,但大家一般都不會去思考怎麼寫一個比較合理或者規範的條件表示式,這裡說一種常用的規範:
(1)bool變數和零值比較
if(bTestFlag) 或 if(!bTestFlag)
(2)float/double變數和零值比較
float fTestVal = 0.0;
const double EPSINON = 0.000001;
if(fTestVal >= -EPSINON && fTestVal <= EPSINON)
(3)z指標變數和零值比較
if(NULL == p) 或者if(NULL != p)
6.空語句的寫法
對於寫空語句,如果直接寫一個“;”使程式碼不易閱讀。建議寫成 NULL; 這種形式。
7.關鍵字const
(1)const修飾變數
const修飾變數時,變數變為只讀變數。但竟然是變數,那就不能作為定義陣列時的大小出現,因為陣列的大小必須在編譯時確定,必須為一個常量。
(2)const修飾指標
const int *p;
int const *p;
int *const p;
const int *const p;
這裡提供一種簡單的方法來判斷const到底修飾的是p還是*p,總結起來就是一句話“忽略型別,淨水樓臺先得月”。我們判斷const的修飾物件時可以忽略型別名,如上面的我們可以忽略int,則變成:
const *p;
const *p;
const p;
const *const p;
8.關鍵字 volatile
遇到這個關鍵字宣告的變數,編譯器對該變數訪問時不再進行優化,從而可以提供對特殊地址的穩定訪問。
先看看下面的例子:
int i=10;
int j = i;//(1)語句
int k = i;//(2)語句
這時候編譯器對程式碼進行優化,因為在(1)、(2)兩條語句中,i 沒有被用作左值。這時候編譯器認為i 的值沒有發生改變,所以在(1)語句時從記憶體中取出i 的值賦給j 之後,這個值並沒有被丟掉,而是在(2)語句時繼續用這個值給k 賦值。編譯器不會生成出彙編程式碼重新從記憶體裡取i 的值,這樣提高了效率。但要注意:(1)、(2)語句之間i 沒有被用作左值才行。
再看另一個例子:
volatile int i=10;
int j = i;//(3)語句
int k = i;//(4)語句
volatile關鍵字告訴編譯器i是隨時可能發生變化的,每次使用它的時候必須從記憶體中取出i的值,因而編譯器生成的彙編程式碼會重新從i 的地址處讀取資料放在k 中。
二、符號
1.註釋符號
(1)編譯器剔除註釋時不是簡單的去掉,而是用空格來替換
(2)/* */不能巢狀
(3)註釋的位置可以放在當前語句行或者上一行,但最好不要放在下一行
2.++/--運算子
++/--在遇到逗號、分號時認為本計算單元已經結束,會進行自加或自減。
三、預處理
1.預處理有三種--檔案包含、巨集定義和條件編譯,這是三種預處理指令,不是C語言的一部分。
2.#運算子
宣告一個符號為巨集引數,如下:
#define SQR(x) printf("The square of "#x" is %d.\n",((x)*(x))); //被#修飾後,這裡的x成為了巨集引數
再使用:
SQR(8);
則輸出的是:
The square of 8 is 64.
四、指標和陣列
1.NULL不要寫成Null或者null,影響程式碼的移植性。
2.如何將陣列寫入指定地址。
如往記憶體0x12ff7c地址存入一個整數0x100:
int *p = (int*)0x12ff7c;
*p = 10;
或者:
*(int*)0x12ff7c = 0x100;
3.當一維陣列作為函式引數的時候,編譯器總是把它解析成一個指向其首元素地址的指標。
4.函式指標、函式指標陣列和指向函式指標陣列的指標
函式指標:char* (*pfunc)(char *p);
函式指標陣列:char* (*pfunc[3])(char *p);
指向函式指標陣列的指標:char* (*(*pfunc[3])(char *p);
五、記憶體管理
1.堆、棧和靜態區
靜態區:儲存自動管全域性變數和static變數。靜態區的內容在整個程式的宣告週期內都存在,由編譯器在編譯的時候分配。
棧:儲存區域性變數。棧上的內容只在函式的範圍內存在,當函式執行結束,這些內容也會自動銷燬。其特點是效率高,但空間大小有限。
堆:由malloc系列函式或new操作符分配的記憶體。其生命週期由free或delete決定。在沒有釋放之前一直存在,知道程式結束。其特點是使用靈活,空間比較大,但容易出錯。
2. 使用malloc分配記憶體時,如果分配失敗則返回NULL。所以在每次使用malloc函式以後都要使用if(NULL != p)來判斷一下是否分配成功。
3. 使用free釋放記憶體後要將指標變數的值置為NULL。