天天看點

C/C++中const關鍵字

const經常被用到,是以今天總結一下const關鍵字的用法,常用的場景為修飾變量(C/C++不同),修飾指針和引用,修飾函數參數,修飾函數傳回值,修飾函數定義體(類 成員函數)

(1)修飾變量

cosnt在修飾變量時,在C和C++中是不同的。

const int a = ;
int array[a];//在C語言中是錯誤的,因為在C語言中是定義了一個隻讀變量
int array[a];//在c++中是正确的,因為在C++中定義了一個常量
           

C中,const是一個不能被改變的普通變量,既然是變量,就要占用存儲空間,是以編譯器不知道編譯時的值.而且,數組定義時的下标必須為常量.

而C++中的const正常情況下是看成編譯期的常量,編譯器并不為const配置設定空間,隻是在編譯的時候将期值儲存在名字表中,并在适當的時候折合在代碼中. 是以在C++中const修飾的量可以用在數組的定義中。

在C語言中: const int size; 這個語句是正确的,因為它被C編譯器看作一個聲明,指明在别的地方配置設定存儲空間.

但在C++中這樣寫是不正确的,會報錯

C/C++中const關鍵字

C++中const預設是内部連接配接,如果想在C++中達到以上的效果,必須要用extern關鍵字. C++中,const預設使用内部連接配接.而C中使用外部連接配接.

内連接配接:編譯器隻對正被編譯的檔案建立存儲空間,别的檔案可以使用相同的表示符或全局變量.C/C++中内連接配接使用static關鍵字指定.

外連接配接:所有被編譯過的檔案建立一片單獨存儲空間.一旦空間被建立,連接配接器必須解決對這片存儲空間的引用.全局變量和函數使用外部連接配接.通過extern關鍵字聲明,可以從其他檔案通路相應的變量和函數. C++中,是否為const配置設定空間要看具體情況. 如果加上關鍵字extern或者取const變量位址,則編譯器就要為const配置設定存儲空間.

C++中定義常量的時候不再采用define,因為define隻做簡單的宏替換,并不提供類型檢查.

(2)修飾指針和引用

2.1修飾指針

使用const修飾指針時,由于const的位置不同,而含意不同。如果const位于的左側,則const就是用來修飾指針所指向的變量,即指針指向為常量;如果const位于的右側,const就是修飾指針本身,即指針本身是常量。

可能有點繞,舉幾個例子來說明一下。

int a = ;
    int b = ;
    //const在*左邊,常量指針
    const int * p1 = &a;
    int const *p2 = &a;
    *p1 = ;//錯誤,因為const修飾的是p1指向的值,不能通過p1修改,但是可以通過a來修改。
    a = ; //正确

    //const 在*右邊,指針常量
    int * const p3 = &a; 
    p3 = &b; //錯誤,因為const修飾的是p3指針,是以p3的指向不能修改
    *p3 = ; //正确
           

2.2修飾引用

 使用const修飾符也可以說明引用,被說明的引用為常引用,該引用所引用的對象不能被更新。其定義格式如下:

  const <類型說明符> & <引用名>

  例如:

  const double & v;

  在實際應用中,常指針和常引用往往用來作函數的形參,這樣的參數稱為常參數。這個看下面的修飾函數參數時再詳細講解

(3)修飾函數參數

首先如果該參數用于輸出,那麼無論是采用指針傳遞還是引用傳遞,都不能加const修飾。是以const隻能用于修飾輸入參數。這裡又分三種情況:輸入參數采用值傳遞還是指針傳遞還是引用傳遞。

3.1采用值傳遞

如果采用值傳遞,由于函數将自動産生臨時變量用于複制該參數,該輸入參數本來就無需保護,是以不需要加const 修飾。

例如,對于函數void Func1(int x),寫成void Func1(const int x)一點意義也沒有。同理,對于void Func2(A a)也不需要寫成void Func2(const A a),其中A為使用者自定義的對象類型。

3.2采用指針傳遞

如果采用指針傳遞,那麼加const可以防止函數體内部對該參數進行改變,起到保護作用。

例如某個 StringCopy 函數:

void StringCopy(char *strDestination, const char *strSource);

其中 strSource 是輸入參數,strDestination 是輸出參數。給 strSource 加上 const 修飾後,如果函數體内的語句試圖改動 strSource 的内容,編譯器将指出錯誤。

3.3采用引用傳遞

首先我們來說一下,為什麼要引入引用傳遞這種方法。原因是:對于非内部資料類型的參數而言,象void Func(A a) 這樣聲明的函數注定效率比較底。因為函數體内将産生A 類型的臨時對象用于複制參數a,而臨時對象的構造、複制、析構過程都将消耗時間。為了提高效率,可以将函數聲明改為void Func(A& a)。這樣一來,根據引用傳遞的定義,隻是借用了參數的别名,不需要産生臨時對象。

但是,這樣一來,當函數體中改變了參數a的值後,相應的傳遞的原始值也會相應改變。是以如果不希望改變原始參數,隻需要在前面加上const修飾,這樣一來,函數最終定義為void Func(const A& a)。同理,是否應将void Func(int x) 改寫為void Func(const int &x),以便提高效率?完全沒有必要,因為内部資料類型的參數不存在構造、析構的過程,而複制也非常快,“值傳遞”和“引用傳遞”的效率幾乎相當。

總結一下const作為函數輸入參數的用法:

  1. 對于非内部資料類型的輸入參數,應該将“值傳遞”的方式改為“const 引用傳遞”,目的是提高效率。例如将void Func(A a) 改為void Func(const A &a)。
  2. 對于内部資料類型的輸入參數,不要将“值傳遞”的方式改為“const 引用傳遞”。否則既達不到提高效率的目的,又降低了函數的可了解性。例如void Func(int x) 不應該改為void Func(const int &x)。

(4)修飾函數傳回值

根據上面的思路,這裡也分三種情況,即值傳遞、指針傳遞、引用傳遞。

  1. 如果函數傳回值采用“值傳遞”方式,由于函數會把傳回值複制到外部臨時的存儲單元中,加const 修飾沒有任何價值。

    例如,不要把函數int GetInt(void) 寫成const int GetInt(void)。同理不要把函數A GetA(void) 寫成const A GetA(void),其中A 為使用者自定義的資料類型。

  2. 如果函數傳回值采用“指針傳遞”方式,那麼函數傳回值(即指針)的内容不能被修改,該傳回值隻能被賦給加const 修飾的同類型指針。

    例如,定義函數為:const char *GetString(void),那麼char *str = GetString()将會出現編譯錯誤。應該寫成const char *str = GetString()。

  3. 如果函數傳回值是采用“引用傳遞”方式,它的意義在于能提高效率,而這種方式使用場合并不多。這個時候,一定要搞清楚函數究竟是想傳回一個對象的“拷貝”還是僅傳回“别名”就可以了,否則程式會出錯。

    例如,對于**類的重載指派函數**A & operate = (const A &other),如果不加cons修飾,則定義A a, b, c;(a = b) = c,程式合法,但是如果加上const修飾,即const A & operate = (const A &other),則程式會報錯。

(5)修飾成員函數

const 成員函數的聲明看起來怪怪的:const 關鍵字隻能放在函數聲明的尾部,大概是因為其它地方都已經被占用了。

但事實上,任何不會修改資料成員(即函數中的變量)的函數都應該聲明為const 類型。如果在編寫const 成員函數時,不慎修改了資料成員,或者調用了其它非const 成員函數,編譯器将指出錯誤,這無疑會提高程式的健壯性。

例如下面的類MyClass的成員函數getCount():

class MyClass {
    int member ;
    public:
    int getCount ( ) const;
};
           

這裡,在函數定義頭後面加上的 const 表示這個函數是一個“隻讀函數”,函數不能改變類對象的狀态,不能改變對象的成員變量的值。如在函數體中不能這麼寫:

classname :: getCount( )
{   member = ;
    return member;
}
           

編譯器将指出getCount 函數中的錯誤,因為試圖修改資料成員member。同樣,const成員函數也不能在函數中調用其他非const 的函數。

總結一下const成員函數:

1. const對象隻能調用const成員函數

2. 非const對象可以調用const和非const成員函數,不過優先調用非 const成員函數

3. 對于加上mutable修飾符的資料成員,const成員函數可以修改