C++中const的詳解

  • const的含義
    使用const用來表示程序中不可修改的常量或對象的值。

  • const作用
    (1)const可以定義常量。例如,const int a = 100;其中定義了a爲常量,不可以修改。
    (2)類型檢查。const常量具有類型,編譯器可以進行安全檢查,#define也存在類型,不過與其表達量有關(整數,浮點數,用戶定義以及運算符的結果類型)。const定義常量從彙編的角度來看,只是給出了對應的內存地址,而不是像#define一樣給出的是立即數,所以,const定義的常量在程序運行過程中只有一份拷貝,而#define定義的常量在內存中有若干個拷貝。但是如果用const表達常量時必須用常量表達式賦值,使用它時不需要依賴它爲變量的身份,一定場合下不需要定義,比如類中的static成員對象。編譯器在作爲常量處理它時,不會依賴“一份定義”,而是像是立即數一樣使用它,它本身可能在機器碼中被“拷貝”到多個地方,和 #define 定義的宏常量的結果相同。另一方面, const 定義的常量由於是整數或枚舉,所以直接變成機器碼上的立即數往往性能更好。最後,const 定義的變量只有類型爲整數或枚舉,且以常量表達式初始化時才能作爲常量表達式。其他情況下它只是一個 const 限定的變量,不要將與常量混淆。

    綜上,const在程序週期中是被程序以一段內存地址來存儲的,使用時調用其內存地址,但是#define使用的是它的拷貝,使用一次拷貝一次,這樣const對內存的使用很靈活。

  • 防止修改,保證程序的健壯性。
    比如下面的代碼:

void Demo(const int num)
{
	num++;
}

這樣編譯器就會報錯,表達式的值必須爲可修改的左值。這樣可以保證num的值不被惡意修改。

  • 可以節省內存
    因爲const給出的是常量的內存地址,使用時直接調用內存地址,直接使用,不用進行拷貝。

  • const對象默認爲文件局部變量
    注意:非const變量默認爲extern。要使const變量能夠在其他文件中訪問,必須在文件中顯式地指定它爲extern。
    未被const修飾的變量不需要extern顯式聲明!而const常量需要顯式聲明extern,並且需要做初始化!因爲常量在定義後就不能被修改,所以定義時必須初始化。

  • 指針與const
    與const有關的指針有以下四種,

    const int *ptr;	//指向常量的指針
    int const *ptr;	//同上
    int * const ptr;	//內存地址爲常量的指針,又叫常指針,const指針
    const int * const ptr;	//指向const對象的const指針
    

    這幾種比較相似,但是區別也很大。最明顯的區別就是*的位置。前兩種表示修飾的是指針所指向的值,第三種修飾的是地址,最後一種修飾的是地址和指向的對象都是不可變的。const表示修飾的是常量,對於指針的理解我在前面文章中有過介紹。而且後兩種在編譯時會報錯,具體原因後文會進行介紹。接下來我就這幾種情況進行介紹。
    (1)指向常量的指針。

const int *ptr;
int const *ptr;

這兩種情況表示指向常量的指針,表示它指向的對象不可變。const修飾的是int,也就是ptr指向的類型,而不是ptr本身,所以ptr不必賦初始值。但是不能通過修改ptr來改變對象的值。因爲其對象是常量,不可變。

const int *ptr;
*ptr = 10//error

除此之外,也不能用void*來儲存const對象的地址,必須使用const void *類型的指針來儲存對象的地址。

const int *p = 10void *q = &p; //error
const void *q = &p; 

另外還要一個需要注意的地方,即允許把非const對象的地址賦給指向const對象的指針。例如

const int *p;
int q = 10;
p = &q;	//ok

但是不能通過修改p的值來修改q的值,即使它指向的是非const對象,因爲p指向的對象爲常量。
但是修改指針可以使用另一種方式。例如:

int *p = 10*p = 4;
cout<<*p<<endl;		//the output is 4

(2)常指針
const指針必須初始化,且const定義的內存不可修改。而且其內存必須初始化,例如下面的例子:

int t = 0int n = 100int* const ptr = &t;
* ptr = &n;

這樣是可以的,即可以修改它指向的對象的值。
最後,當把一個const常量的地址賦值給一個用const修飾的地址時,需要把指向的對象也設置爲const。例如下面的例子:

const int num = 10;
	int* const p = &num;

這樣就會報錯,“const int *” 類型的值不能用於初始化 “int *const” 類型的實體,原因是p指向的對象爲一個const常量,但是int前沒有加const,所以不可以通過編譯。只要這樣修改:

const int* const p = &num;

這樣它指向的就是一個常量,就可以通過編譯。
(3)
指向常量的常量指針地址
有了前面的介紹,就可以理解什麼是指向常量的常量指針地址。

  • 函數中的const

const可以修飾返回值
(1)const int fun1();
但是這樣做明顯沒有意義,因爲函數的返回值本來就是賦值給變量的。
(2)const int * fun1()
這樣表示指針指向的內容不變
(3)int * const fun1()
這樣表示指針本身不可變。

const可以在參數傳遞中發揮作用。比如修飾參數,使其在函數體中不可變,但是其意義不是很大,一個函數基本上就是參數進行修改操作的。例如下面的幾個函數:

void fun1(onst int num);
void fun2(int *const num);

這兩個函數中不可以修改num的值或者指針。
const還可以修飾,使其指向的對象不可變。例如:

void fun3(int const *num);

這樣表示num指向的對象在函數中不可以改變。
參數爲引用,防止其在函數中被錯誤或者惡意修改,這樣明顯增加了程序的健壯性。
假如函數爲:void fun3(const A &a);這樣在函數內部就保證了a的不變。
爲什麼要這樣做呢?我們從以下角度來考慮,假如函數爲void fun3(A a);這樣在函數使用時必定會產生一個拷貝,使用時複製一個對象,而且在析構時也會再次析構,這無疑增加了程序的複雜性,因此使用引用可以節省開銷,但是我們如果要保證它在函數中不可以被改變的時候,就需要增加const來保證它是不可以改變的。
但是以此類推,如果是void fun1(int num);也需要變爲void fun1(const int &num);嗎?答案是否定的。因爲對於內置數據類型,函數在拷貝,析構等操作時,即使會增加開銷,但是也很快,這個速度與引用的速度相當,所以只能說沒必要,傳入參數就可以。

  • 類中使用const
    這是最後一個const的使用方式了,在類中使用const常量,則其不可改變,而且不可以被普通函數調用,只有常成員函數可以進行調用,這無疑增加了程序的健壯性。意思是const定義的函數纔可以調用const定義的常量。還有常成員對象只可以訪問常成員函數,但是普通對象卻可以調用類中所以函數,常成員函數也可以調用。

綜上所述,const其實就是修飾了一個常量,使其不可以改變,而且有一定的就近原則(具體還要在實際環境中考慮),它在程序的安全性中確實有很重要的作用,所以我認爲應該大膽的使用const進行編程。以上便是我對const的一些個人的認識,希望對大家有幫助。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章