const在C語言中算是一個比較新的描述符,咱們稱之爲常量修飾符,意即其所修飾 的對象爲常量(immutable)。 咱們來分狀況看語法上它該如何被使用。數組
一、函數體內修飾局部變量。 例: void func(){安全
const int a=0;函數
}指針
首先,咱們先把const這個單詞忽略不看,那麼a是一個int類型的局部自動變量, 咱們給它賦予初始值0。調試
而後再看const。 const做爲一個類型限定詞,和int有相同的地位。 const int a; int const a; 是等價的。對象
因而此處咱們必定要清晰的明白,const修飾的對象是誰,是a,和int沒有關係。內存
const 要求他所修飾的對象爲常量,不可被改變,不可被賦值,不可做爲左值(l-value)。字符串
這樣的寫法也是錯誤的: const int a;編譯器
a=0; // 非法,a不可變io
這也是一個很常見的使用方式: const double pi=3.14;在程序的後面若是企圖對pi再次賦值或者修改就會出錯。
而後看一個稍微複雜的例子。 const int* p; 仍是先去掉const 修飾符號,獲得:int *p。
注意,下面兩個是等價的。 int* p; int *p;
其實咱們想要說的是,對於int *p:*p是int類型變量。那麼顯然,p就是指向int類型變量的指針,即p所指向的內存塊內所存儲的數據爲*p。
同理 const int* p; 其實等價於 const int (*p); int const (*p); 即,*p是常量。也就是說,p指向的數據是常量,也就是說:*p不可變,而p可變。
因而 p+=8; //合法
*p=3; //非法,p指向的數據是常量。
那麼如何聲明一個自身是常量的指針呢?
方法是讓const儘量的靠近p;即:int* const p; const右面只有p,顯然,它修飾的是p,說明p不可被更改,也就是說:*p可變,而p不可變。
首先把const去掉,能夠看出int *p:p是一個指向 int類型變量的指針。 因而 p+=8; //非法,p不可變
*p=3; //合法
再看一個更復雜的例子。
它是上面兩者的綜合 const int* const p; 說明p本身是常量,且p指向的int類型變量也是常量。也就是說:*p不可變,而p也不可變。
因而 p+=8; //非法,p不可變
*p=3; //非法,*p不可變
const 還有一個做用就是用於修飾常量靜態字符串。
例如: const char* name=David;
若是沒有const,咱們可能會在後面有意無心的寫name[4]='x'這樣的語句,這樣會致使對只讀內存區域的賦值,而後程序會馬上異常終止。
有了 const,這個錯誤就 能在程序被編譯的時候就當即檢查出來。
這就是const的好處:讓邏輯錯誤在編譯期被發現。
const 還能夠用來修飾數組 const char s[]=David; 與上面有相似的做用。
二、在函數聲明時修飾參數 來看實際中的一個例子。
void * memmove(void *dst, const void *src, size_t len); // 這是標準庫中的一個函數,用於按字節方式複製字符串(內存)。
它的第一個參數:是將字符串複製到哪裏去(destination),即目的地,這段內存區域必須是可寫的,所以這個參數不能用const修飾。
它的第二個參數:是要將什麼樣的字符串複製出去,咱們對這段內存區域只作讀取,不寫,所以這個參數用const修飾。
因而,咱們站在這個函數本身的角度來看:
src 這個指針,它所指向的內存內所存儲的數據(*src)在整個函數執行的過程當中是不變的。因而src所指向的內容(*src)是常量。因而*src就須要用const修飾。
例如,咱們這裏這樣使用它:
const char* s=hello;
char buf[100];
memmove(buf,s,6); //這裏其實應該用strcpy或memcpy更好
若是咱們反過來寫: memmove(s,buf,6); 那麼編譯器必定會報錯。
這個報錯的事實是:咱們常常會把各類函數的參數的順序寫反。
其實是編譯器在此時幫了咱們大忙。若是編譯器靜悄悄的不報錯,(在函數聲明處去掉 const便可),那麼這個程序在運行的時候必定會崩潰。
這裏還要說明的一點是:在函數的參數聲明中,const通常用來聲明指針而不是基本類型變量。
例如,上面的memmove()函數中的size_t len這個參數,在函數實現的時候能夠徹底不用更改len的值,那麼是否應該把len也聲明爲常量呢?
能夠這麼作。咱們來分析這麼作有什麼優劣。
若是len加了const,那麼對於這個函數的調用者,能夠防止他在實現這個函數的時候修改不須要修改的值(len),這樣很好。
可是: 1 這個修飾符號毫無心義,咱們仍然能夠傳遞一個常量整數或者一個很是量整數過去,反正對方得到的只是咱們傳遞的一個copy。
2 暴露了實現,我不須要知道你在實現這個函數的時候是否修改過len的值。
因此綜合1和2,const通常只用來修飾指針。
再看一個複雜的例子。
int execv(const char *path, char *const argv[]);
着重看第二個參數:char *const argv[]。
若是去掉const,咱們能夠看出 char * argv[]; argv是一個數組,它的每一個元素都是char *類型的指針。
若是加上const.那麼const修飾的是誰呢?
它修飾的是一個數組argv[]:意思就是說這個數組中的元素是隻讀的。
那麼這個數組中的元素的是什麼類型呢?是char *類型的指針,也就是說指針是常量,而它指向的數據不是。 因而 argv[1]=NULL; //非法
argv[0][0]='a'; //合法
三、全局變量。
咱們的原則依然是:儘量少使用全局變量。
咱們的第二條規則是:儘量多使用const。
若是一個全局變量只在本文件中使用,那麼用法和前面所說的函數局部靜態變量沒有什麼區別。
/* MyFile.c */
static const double pi=3.14;
而若是它要在多個文件間共享,那麼就牽扯到一個存儲類型的問題。有兩種方式:
1.使用extern修飾
/* file1.h */
extern const double pi;
/* file1.c */
const double pi=3.14;
而後在其餘須要使用pi這個變量的文件中:#include file1.h 或者,本身把那句外部變量聲明覆制一遍就好。
這樣作的結果是:整個程序連接完後,全部須要使用pi這個變量的文件共享同一個pi存儲區域。
2.使用static靜態外部存儲類
/* constant.h */
static const pi=3.14;
在須要使用這個變量的*.c文件中,必須包含這個頭文件。 前面的static必定不能少。不然連接的時候會報告說該變量被屢次定義。
這樣作的結果是:每一個包含了constant.h的*.c文件,都有一份該變量本身的copy, 該變量實際上仍是被定義了屢次,佔用了多個存儲空間,不過在加了static關鍵字 後,解決了文件間重定義的衝突。
壞處是:浪費了存儲空間,致使連接完後的可執行文件變大。
可是一般,這個小小几個字節的變化,不是問題。
好處是:你不用關心這個變量是在哪一個文件中被初始化的。
最後,說說const的做用。
const 的好處:引入了常量的概念,讓咱們不要去修改不應修改的內存。
直接的做用就是:讓更多的邏輯錯誤在編譯期被發現。因此咱們要儘量的多使用const。
可是不少人並不習慣使用它,更有甚者,是在整個程序 編寫/調試 完後才補 const。若是是給函數的聲明補const,尚好。
若是是給 全局/局部變量補const,那 麼……那麼,爲時已晚,無非是讓代碼看起來更漂亮了。
關於const的使用,曾有一 個笑話說,const 就像安全套,事前要記牢。若是作完後纔想起來該用而忘了用, 呵呵……呵呵……