C語言中的const
const是C語言中保留的一個關鍵字,用來定義常量,如果一個變數被const修飾,那麼它的值就不能被改變。使用符號常量寫出的代碼更容易維護;一些指標常常是邊讀邊移動,而不是邊寫邊移動;許多編程規範對於函數參數會強制要求唯讀不寫,在這些情況下,都需要藉助const實現。
那麼有些人會問,C語言中不是已經存在#define了嗎,為什麼還要使用const呢。相比於#define,const修飾符有如下優勢:
1. const能夠使編譯器進行類型檢查,而先行編譯指令#define只是簡單的對值進行替換。
2. const可以保護被修飾的變數等,防止變數因為意外被修改,從而增強程式的健壯性。
3. 編譯器通常不為普通const常量分配儲存空間,而是將他們儲存在符號表中,這使得它成為了一個編譯期間的常量,沒有了儲存於記憶體操作,使得它的效率很高。 const用法
const最常見的用法就是作為數組的邊界和switch分情況標號,分類如下:
常變數: const + 類型說明符 + 變數名
常引用: const + 類型說明符 + &引用名
常對象: 類名 + const 對象名
常成員函數: 類名::fun(形參) + const
常數組: 類型說明符 + const 數組名[大小]
常量指標: const + 類型說明符* 指標名 或者 類型說明符 + const *指標名
首先提示的是:在常變數(const + 類型說明符 + 變數名)、常引用(const + 類型說明符 + &引用名)、常對象(類名 + const 對象名)、 常數組(類型說明符 + const 數組名[大小]), const”與“類型說明符”或“類名”(其實類名是一種自訂的類型說明符) 的位置可以互換。如:
const int a=5; int const a=5; //等同
其實這種可以很好理解,const只能修飾int(類型名或類名),所以二者是等同的。 修飾局部變數
const int n=5;int const n=5;
這兩種寫法是一樣的,都是表示變數n的值不能被改變了,需要注意的是,用const修飾變數時,一定要給變數初始化(用來修飾函數的形參除外),否則之後就不能再進行賦值了。
接下來看看const用於修飾常量靜態字串,例如:
const char* str = "fdsafdsa";
如果沒有const的修飾,我們可能會在後面有意無意的寫str[4]=’x’這樣的語句,這樣會導致對唯讀記憶體地區的賦值,然後程式會立刻異常終止。有了const,這個錯誤就能在程式被編譯的時候就立即檢查出來,這就是const的好處。讓邏輯錯誤在編譯期被發現。 常量指標與指標常量 常量指標
常量指標表示一個指向常量的指標,即該指標指向的內容是個常量(至少該常量指標是這樣認為的),可以有如下的定義方式:
const int * n;int const * n;
需要注意以下兩點:
1.常量指標指向的值是不能改變的,但是這不意味指標本身不能改變,常量指標是可以指向其他的地址的。
int a=5;int b=6;const int* n = &a;int const *n1 = &a; n=&b;
這個例子中,n和n1是等價的,都是指向a的一個常量指標,這時候有人會說,常量指標指向的應該是一個常量吧,但是a並不是一個常量啊,為什麼常量指標依舊可以指向a呢,這時候又要注意一下了:
編譯器允許把非 const 對象的地址賦給指向 const 對象的指標,不允許把一個 const 對象的地址賦給一個普通的、非 const 對象的指標。
其實這個也很好理解,對於一個變數,我可以對你增加束縛,用指向const對象的指標指向你,這隻是意味著我現在認為你是常量,不能通過這個常量指標對你進行修改,但是對於第二種情況就不一樣了,你本來是一個被束縛的常量,如果用一個普通的指標指向你的話,就意味著我認為你這個常量是一個變數,這樣或許我會在下面的代碼中,對你的值進行修改,對一個const常量進行修改,會發生什麼,你應該明白。
然後回到上面的代碼,我們將a的地址給了n,然後在下一行又改變了n的值,將b的地址賦給了n,這是沒有任何問題的,因為n只是一個指標,指向了一個常量,所以被稱為常量指標,它本身依舊是一個普通的指標,比較特殊的是它所指向的內容,而不是它本身。所以它本身的值可以修改,指向不同的內容。
2.當常量指標指向了一個變數時,不能通過這個常量指標改變變數的值,但是還是可以通過其他的引用來改變變數的值的。
int a=5;const int* n=&a;a=6; // 正確*n = 7; //錯誤,對於常量指標,不能通過該指標來改變所指向的內容(即使它所指向的內容並不是一個常量)。int *ni = (int *)n;*n = 100; //正確
在n這個指標眼中,n所指向的內容是一個常量,所以不允許別人通過n本身對a進行修改,但是a本質上是一個變數,所以我們可以直接通過變數名或者新的普通指標對a進行修改。實際上,在將程式載入記憶體的時候,會有專門的一塊記憶體地區來存放常量。但是,上面的a本身不是常量,是存放在棧或者堆中的。我們仍然可以修改它的值。而n不能修改指向的值應該說是編譯器的一個限制。 指標常量
指標常量是指指標本身是一個常量,它只能指向一個固定的地址,不能指向別的地址。寫法如下:
int *const n;
和上面的常量指標不一樣,指標常量是指指標本身比較特殊,而它所指的內容並不特殊,因此,指標常量指向的內容是可以修改的,可以通過別的指向該地址的指標進行修改。如下所示:
int a=5;int *p=&a;int* const n=&a;*p=8;
在函數中,指標常量時表示不允許將該指標指向其他內容:
void func_02(int* const p){ int *pi = new int(100); /* 錯誤。P是指標常量。不能對它賦值。 */ p = pi;}int main(){ int* p = new int(10); func_02(p); delete p; return 0;}
然後看下面的代碼:
const int *m1 = new int(10);int* const m2 = new int(20);
在上面的兩個運算式中,最容易讓人迷惑的是const到底是修飾指標還是指標指向的記憶體地區。其實,只要知道:const只對它左邊的東西起作用,唯一的例外就是const本身就是最左邊的修飾符,那麼它才會對右邊的東西起作用。根據這個規則來判斷,m1應該是常量指標(即不能通過m1來修改它所指向的內容。);而m2應該是指標常量(即不能讓m2指向其他的記憶體位址)。或者我們可以把星號看做指標,把const看做常量,那麼m1就是常量指標,m2就是指標常量。 指向常量的常指標
是以上兩種的結合,指標指向的位置不能改變並且也不能通過這個指標改變變數的值,但是當它指向一個變數時,依然可以通過其他的普通指標改變變數的值。
const int* const p; 修飾函數的參數
根據常量指標與指標常量,const修飾函數的參數也是分為三種情況
1.防止修改指標指向的內容
void StringCopy(char *strDestination, const char *strSource);
其中 strSource 是輸入參數,strDestination 是輸出參數。給 strSource 加上 const 修飾後,如果函數體內的語句試圖改動 strSource 的內容,編譯器將指出錯誤。這種形式通常用於在數組形式的參數中類比傳值調用。也就是相當於函數調用者聲稱:”我給你一個指向它的指標,但你不能去修改它。”如果函數編寫者遵循了這個約定,那麼就相當於類比了值傳遞。這也是const最有用之處了:用來限定函數的形參,這樣該函數將不會修改實參指標所指的資料。這裡注意了,是函數不應該去修改而不是不能修改,也就是說const不能阻止參數的修改(原因見上)。
2.防止修改指標指向的地址
void swap (int* const p1 , int* const p2);
指標p1和指標p2都是指標常量,指向的地址都不能修改。 修飾函數的傳回值
很多時候,我們的函數中會返回一個地址或者引用。調用這得到這個返回的地址或者引用後就可以修改所指向或者代表的對象。這個時候如果我們不希望這個函數的調用這修改這個返回的內容,就應該返回一個常量。
如果給以“指標傳遞”方式的函數傳回值加 const 修飾,那麼函數傳回值(即指標)的內容不能被修改,該傳回值只能被賦給加const 修飾的同類型指標。
例如函數
const char * GetString(void);
如下語句將出現編譯錯誤:
char *str = GetString();
正確的用法是
const char *str = GetString();
修飾全域變數
全域變數的範圍是整個檔案,我們應該盡量避免使用全域變數,以為一旦有一個函數改變了全域變數的值,它也會影響到其他引用這個變數的函數,導致除了bug後很難發現,如果一定要用全域變數,我們應該盡量的使用const修飾符進行修飾,這樣方式不必要的以為修改,使用的方法與局部變數是相同的。 const 在c和c++中的區別 C++中的const正常情況下是看成編譯期的常量,編譯器並不為const分配空間,只是在編譯的時候將期值儲存在名字表中,並在適當的時候摺合在代碼中.所以,以下代碼:
using namespace std;
int main()
{
const int a = 1;
const int b = 2;
int array[ a + b ] = {0};
for (int i = 0; i < sizeof array / sizeof *array; i++)
{
cout << array << endl;
}
}
在可以通過編譯,並且正常運行.但稍加修改後,放在C編譯器中,便會出現錯誤:
int main()
{
int i;
const int a = 1;
const int b = 2;
int array[ a + b ] = {0};
for (i = 0; i < sizeof array / sizeof *array; i++)
{
printf(“%d”,array);
}
}
錯誤訊息:
c:\test1\te.c(8): error C2057: 應輸入常數運算式
c:\test1\te.c(8): error C2466: 不能分配常數大小為 0 的數組
出現這種情況的原因是:在C中,const是一個不能被改變的普通變數,既然是變數,就要佔用儲存空間,所以編譯器不知道編譯時間的值.而且,數組定義時的下標必須為常量. 在C語言中: const int size; 這個語句是正確的,因為它被C編譯器看作一個聲明,指明在別的地方分配儲存空間.但在C++中這樣寫是不正確的.C++中const預設是內部串連,如果想在C++中達到以上的效果,必須要用extern關鍵字.即C++中,const預設使用內部串連.而C中使用外部串連.
(1) 內串連:編譯器只對正被編譯的檔案建立儲存空間,別的檔案可以使用相同的表示符或全域變數.C/C++中內串連使用static關鍵字指定.
(2) 外串連:所有被編譯過的檔案建立一片單獨儲存空間.一旦空間被建立,連接器必須解決對這片儲存空間的引用.全域變數和函數使用外部串連.通過extern關鍵字聲明,可以從其他檔案訪問相應的變數和函數. 參考連結:
C/C++中const關鍵字詳解
詳解C語言中const關鍵字的用法
話說C語言const用法