const關鍵字是ANSI標準新增加的關鍵字。const是個型別限定符,可以和任何型別說明符一起使用,以指定被宣告物件的特殊屬性。C語言的型別說明符包括:
- void
- char
- short
- int
- long
- float
- double
- signed
- unsigned
- 結構體說明符
- 聯合體說明符
- 列舉說明符
- 型別定義名
const用於宣告可以存放在只讀儲存器中的物件,並可能提高優化的可能性。
概念是抽象的,下面我們從幾個方面來說一下const的用法:
1. 修飾區域性變數
const int n=5;
int const n=5;
複製程式碼
這兩種寫法是一樣的,都是表示變數n的值不能被改變了,需要注意的是,用const修飾變數時,一定要給變數初始化,否則之後就不能再進行賦值了。
關鍵字const並不能把變數變成常量!在一個符號前加上const限定符只是表示這個符號不能被賦值,但是不能阻止通過其它的方法來修改這個值。下面的例子就demo了這點。
int main(void)
{
const int limit = 10;
int *p = &limit;
/* error: assignment of read-only variable ‘limit’ */
/* limit = 1; */
*p = 11;
printf("%d\n", limit); /* OK, limit=11 now */
return 0;
}
複製程式碼
2. const與指標
2.1 常量指標
const int * n;
int const * n;
複製程式碼
這段程式碼是等價的,但是一般建議用第一個。表示n是一個指向整形常量的指標,簡稱常量指標。
需要注意的是一下兩點:
- 常量指標說的是不能通過這個指標改變它所指向的變數的值,但是還是可以通過其他的引用來改變變數的值。
int main(void)
{
int a = 1;
const int *limitp = &a;
int i = 27;
/* error: assignment of read-only location ‘*limitp’ */
/* *limitp = 1; */
a = 2; /* OK */
}
複製程式碼
- 常量指標指向的值不能改變,但是常量指標可以指向其他的地址。
printf("limitp %p, %d\n", limitp, *limitp);
limitp = &i;
printf("limitp %p, %d\n", limitp, *limitp);
複製程式碼
2.2 指標常量
指標常量是指指標本身是個常量,不能在指向其他的地址,寫法如下:
int *const n;
複製程式碼
需要注意的是,指標常量指向的地址不能改變,但是地址中儲存的數值是可以改變的,可以通過其他指向改地址的指標來修改。
int b = 100;
int *const p2 = &b;
/* error: assignment of read-only variable ‘p2’ */
/* p2 = &i; */
*p2 = 101; /* OK */
複製程式碼
區分常量指標和指標常量的關鍵就在於星號的位置,我們以星號為分界線,如果const在星號的左邊,則為常量指標,如果const在星號的右邊則為指標常量。如果我們將星號讀作‘指標’,將const讀作‘常量’的話,內容正好符合。int const * n;是常量指標,int *const n;是指標常量。
2.3 指向常量的常指標
const int* const p;
複製程式碼
是以上兩種的結合,指標指向的位置不能改變並且也不能通過這個指標改變變數的值,但是依然可以通過其他的普通指標改變變數的值。
int c = 1000;
const int *const p3 = &c;
int i = 1;
p3 = &i; /* error: assignment of read-only variable ‘p3’ */
*p3 = 1001; /* error: assignment of read-only location ‘*p3’ */
複製程式碼
3. 修飾函式的引數
const最有用之處就是用它來限定函式的形參,這樣該函式將不會修改實參指標所指向的資料,但是其他的函式卻可能會修改它。 根據常量指標與指標常量,const修飾函式的引數也是分為三種情況
1、防止修改指標指向的內容
void StringCopy(char *strDestination, const char *strSource);
複製程式碼
其中 strSource 是輸入引數,strDestination 是輸出引數。給 strSource 加上 const 修飾後,如果函式體內的語句試圖改動 strSource 的內容,編譯器將指出錯誤。
2、防止修改指標指向的地址
void swap ( int * const p1 , int * const p2 )
複製程式碼
指標p1和指標p2指向的地址都不能修改。
3、以上兩種的結合。
來看一個具體的例子,編譯器對foo函式發出了一條警告,foo2正常
/* note: expected ‘const char **’ but argument is of type ‘char **’ */
void foo(const char **p)
{
p = p;
}
void foo2(const char *p2)
{
p = p;
}
int main(int argc, char **argv)
{
char *str = "test";
foo(argv);
foo2(str);
return 0;
}
複製程式碼
也就是說實參char* str
與形參const char *p2
是相容的,標準庫中所有的字串處理函式都是這樣的。那麼,為什麼實參char **argv
和形參const char **p
實際上不能相容呢?
答案是肯定的,它們並不相容。原因是...
在ANSI C標準第6.3.2.2節中講述約束條件的小節中有這麼一句話:
每個實參都應該具有自己的型別,這樣它的值就可以賦值給與它所對應的形參型別的物件(該物件的型別不能含有限定符)
也就是說引數傳遞過程類似於賦值。所以除非一個型別為char **
的值可以賦值給一個const char **
型別的物件,否則肯定會產生一條診斷資訊。關於賦值的部分,位於第6.3.16.1節,描述下列約束條件:
兩個運算元都是指向有限定符或者無限定符的相容型別的指標,左邊指標所指向的型別必須要具有右邊指標所指向型別的全部限定符。
之所以實參char* str
與形參const char *p2
是相容的,是因為下面的程式碼中:
char *cp;
const char *ccp;
cpp = cp;
複製程式碼
-
左運算元是一個指向有const限定符的char的指標。
-
右運算元是一個指向沒有限定符的char的指標。
-
char型別與char型別是相容的,左運算元所指向的型別具有右運算元所指向型別的限定符(無),再上自身的限定符(const).
注意,反過來就不能進行賦值。下面的程式碼將產生警告:
cp = cpp;
複製程式碼
標準第6.3.16.1節有沒有說實參char **
和形參const char **
是相容的呢?沒有。
標準第6.1.2.5節中講述例項的部分聲稱:
const float*
型別並不是一個有限定符的型別---它的型別是“指向一個具有const限定符的float型別的指標”,也就是說const限定符是修飾指標所指向的型別,而不是指標本身。
類似地,const char **
也是一個沒有限定符的指標型別。它的型別是“指向一個有const限定符的char型別的指標的指標”。由於char **
和const char **
都是沒有限定符的指標型別,但它們所指向的型別不一樣,前者指向的是char *
,後者指向的是const char *
,因此它們是不相容的。因此,型別為char **
的實參與型別為const char **
的形參是不相容的,因為違法了標準第6.3.2.2節中講述約束條件,編譯器必然會產生一條診斷資訊。
4. 修飾函式的返回值
如果給以“指標傳遞”方式的函式返回值加 const 修飾,那麼函式返回值(即指標)的內容不能被修改,該返回值只能被賦給加const 修飾的同型別指標。 例如函式
const char * GetString(void);
複製程式碼
如下語句將出現編譯警告,但是沒有編譯錯誤:
/* warning: assignment discards ‘const’ qualifier from pointer target type [-Wdiscarded-qualifiers] */
char *str = GetString();
複製程式碼
正確的用法是
const char *str = GetString();
複製程式碼
5. 修飾全域性變數
全域性變數的作用域是整個檔案,我們應該儘量避免使用全域性變數,因為一旦有一個函式改變了全域性變數的值,它也會影響到其他引用這個變數的函式,導致出了bug後很難發現,如果一定要用全域性變數,我們應該儘量的使用const修飾符進行修飾。
參考:
-
《The C Programming Language中文版(第2版.新版)》
-
《C專家程式設計》