1. 程式人生 > >C++ const用法總結

C++ const用法總結

文章轉載自 https://www.cnblogs.com/lanjianhappy/p/7298427.html

  • 常變數: const 型別說明符 變數名
  • 常引用: const 型別說明符 &引用名
  • 常物件: 類名 const 物件名
  • 常成員函式: 類名::fun(形參) const
  • 常陣列: 型別說明符 const 陣列名[大小]
  • 常指標: const 型別說明符* 指標名 ,型別說明符* const 指標名
    const與 “型別說明符”或“類名”(其實類名是一種自定義的型別說明符) 的位置可以互換。

常變數

取代了C中的巨集定義,宣告時必須進行初始化(!c++類中則不然)。const限制了常量的使用方式,並沒有描述常量應該如何分配。如果編譯器知道了某const的所有使用,它甚至可以不為該const分配空間。最簡單的常見情況就是常量的值在編譯時已知,而且不需要分配儲存。

指標和常量

常量與指標放在一起很容易讓人迷糊。

const int *m1 = new int(10);
int* const m2 = new int(20);

在上面的兩個表示式中,最容易讓人迷惑的是const到底是修飾指標還是指標指向的記憶體區域?其實,只要知道:const只對它左邊的東西起作用,唯一的例外就是const本身就是最左邊的修飾符,那麼它才會對右邊的東西起作用。根據這個規則來判斷,m1應該是常量指標(即,不能通過m1來修改它所指向的內容。);而m2應該是指標常量(即,不能讓m2指向其他的記憶體模組)。

常量與引用

常量與引用的關係稍微簡單一點。因為引用就是另一個變數的別名,它本身就是一個常量。也就是說不能再讓一個引用成為另外一個變數的別名, 那麼他們只剩下代表的記憶體區域是否可變。即:

 int i = 10;

    // 正確:表示不能通過該引用去修改對應的記憶體的內容。

    const int& ri = i;

    // 錯誤!不能這樣寫。

    int& const rci = i;

    由此可見,如果我們不希望函式的呼叫者改變引數的值。最可靠的方法應該是使用引用。下面的操作會存在編譯錯誤:

    void func(const int& i)

    {

    // 錯誤!不能通過i去改變它所代表的記憶體區域。

    i = 100;

    }

    int main()

    {

    int i = 10;

    func(i);

    return 0;

    }

這裡已經明白了常量與指標以及常量與引用的關係。但是,有必要深入的說明以下。在系統載入程式的時候,系統會將記憶體分為4個區域:堆區、棧區、全域性區(靜態)和程式碼區。從這裡可以看出,對於常量來說,系統沒有劃定專門的區域來保護其中的資料不能被更改。也就是說,使用常量的方式對資料進行保護是通過編譯器作語法限制來實現的。我們仍然可以繞過編譯器的限制去修改被定義為“常量”的記憶體區域。看下面的程式碼:

 const int i = 10;

    // 這裡i已經被定義為常量,但是我們仍然可以通過另外的方式去修改它的值。

    // 這說明把i定義為常量,實際上是防止通過i去修改所代表的記憶體。

    int *pi = (int*) &i;

常量與函式

常量函式是C++對常量的一個擴充套件,它很好的確保了C++中類的封裝性。在C++中,為了防止類的資料成員被非法訪問,將類的成員函式分成了兩類,一類是常量成員函式(也被稱為觀察者);另一類是非常量成員函式(也被成為變異者)。在一個函式的簽名後面加上關鍵字const後該函式就成了常量函式。對於常量函式,最關鍵的不同是編譯器不允許其修改類的資料成員。例如:

 class Test

    {

    public:

    void func() const;

    private:

    int intValue;

    };

    void Test::func() const

    {

    intValue = 100;

    }

上面的程式碼中,常量函式func函式內試圖去改變資料成員intValue的值,因此將在編譯的時候引發異常。

當然,對於非常量的成員函式,我們可以根據需要讀取或修改資料成員的值。但是,這要依賴呼叫函式的物件是否是常量。通常,如果我們把一個類定義為常量,我們的本意是希望他的狀態(資料成員)不會被改變。那麼,如果一個常量的物件呼叫它的非常量函式會產生什麼後果呢?看下面的程式碼:

class Fred{

    public:

    void inspect() const;

    void mutate();

    };

    void UserCode(Fred& changeable, const Fred& unChangeable)

    {

    changeable.inspect(); // 正確,非常量物件可以呼叫常量函式。

    changeable.mutate(); // 正確,非常量物件也允許修改呼叫非常量成員函式修改資料成員。

    unChangeable.inspect(); // 正確,常量物件只能呼叫常理函式。因為不希望修改物件狀態。

    unChangeable.mutate(); // 錯誤!常量物件的狀態不能被修改,而非常量函式存在修改物件狀態的可能

    }

從上面的程式碼可以看出,由於常量物件的狀態不允許被修改,因此,通過常量物件呼叫非常量函式時將會產生語法錯誤。實際上,我們知道每個成員函式都有一個隱含的指向物件本身的this指標。而常量函式則包含一個this的常量指標。如下:

void inspect(const Fred* this) const;

void mutate(Fred* this);

也就是說對於常量函式,我們不能通過this指標去修改物件對應的記憶體塊。但是,在上面我們已經知道,這僅僅是編譯器的限制,我們仍然可以繞過編譯器的限制,去改變物件的狀態。看下面的程式碼:

class Fred{

    public:

    void inspect() const;
    private:

    int intValue;

    };

    void Fred::inspect() const

    {

    cout << "At the beginning. intValue = "<< intValue << endl;

    // 這裡,我們根據this指標重新定義了一個指向同一塊記憶體地址的指標。

    // 通過這個新定義的指標,我們仍然可以修改物件的狀態。

    Fred* pFred = (Fred*)this;

    pFred->intValue = 50;

    cout << "Fred::inspect() called. intValue = "<< intValue << endl;

    }

    int main()

    {

    Fred fred;

    fred.inspect();

    return 0;

    }

上面的程式碼說明,只要我們願意,我們還是可以通過常量函式修改物件的狀態。同理,對於常量物件,我們也可以構造另外一個指向同一塊記憶體的指標去修改它的狀態。這裡就不作過多描述了。
關於常量函式,還有一個問題是過載。

    #include <iostream>

    #include <string>

    using namespace std;

    class Fred{

    public:

    void func() const;

    void func();

    };

    void Fred::func() const

    {

    cout << "const function is called."<< endl;

    }

    void Fred::func()

    {

    cout << "non-const function is called."<< endl;

    }

    void UserCode(Fred& fred, const Fred& cFred)

    {

    cout << "fred is non-const object, and the result of fred.func() is:" << endl;

    fred.func();

    cout << "cFred is const object, and the result of cFred.func() is:" << endl;

    cFred.func();

    }

    int main()

    {

    Fred fred;

    UserCode(fred, fred);

    return 0;

    }

    輸出結果為:

    fred is non-const object, and the result of fred.func() is:

    non-const function is called.

    cFred is const object, and the result of cFred.func() is:

    const function is called.

從上面的輸出結果,我們可以看出。當存在同名同參數和返回值的常量函式和非常量函式時,具體呼叫哪個函式是根據呼叫物件是常量對像還是非常量物件來決定的。常量物件呼叫常量成員;非常量物件呼叫非常量的成員。

總之,我們需要明白常量函式是為了最大程度的保證物件的安全。通過使用常量函式,我們可以只允許必要的操作去改變物件的狀態,從而防止誤操作對物件狀態的破壞。但是,就像上面看見的一樣,這樣的保護其實是有限的。關鍵還是在於我們開發人員要嚴格的遵守使用規則。另外需要注意的是常量物件不允許呼叫非常量的函式。這樣的規定雖然很武斷,但如果我們都根據原則去編寫或使用類的話這樣的規定也就完全可以理解了。

常量返回值

很多時候,我們的函式中會返回一個地址或者引用。呼叫這得到這個返回的地址或者引用後就可以修改所指向或者代表的物件。這個時候如果我們不希望這個函式的呼叫這修改這個返回的內容,就應該返回一個常量。

Tips

+++++++++++++++++++++++++++++++++++++++

c++ 中const

+++++++++++++++++++++++++++++++++++++++

const常量

const int max = 100;  

優點:const常量有資料型別,而巨集常量沒有資料型別。編譯器可以對前者進行型別安全檢查,而對後者只進行字元替換,沒有型別安全檢查,並且在字元替換時可能會產生意料不到的錯誤(邊際效應)

const 修飾類的資料成員

class A
{

    const int size;

    …

}

const資料成員只在某個物件生存期內是常量,而對於整個類而言卻是可變的。因為類可以建立多個物件,不同的物件其const資料成員的值可以不同。所以不能在類宣告中初始化const資料成員,因為類的物件未被建立時,編譯器不知道const 資料成員的值是什麼。如

class A

{

 const int size = 100;    //錯誤

 int array[size];         //錯誤,未知的size

}

const資料成員的初始化只能在類的建構函式的初始化表中進行。要想建立在整個類中都恆定的常量,應該用類中的列舉常量來實現。如

class A

{…

 enum {size1=100, size2 = 200 };

int array1[size1];

int array2[size2];

}

列舉常量不會佔用物件的儲存空間,他們在編譯時被全部求值。但是列舉常量的隱含資料型別是整數,其最大值有限,且不能表示浮點數。

const修飾指標的情況

nt b = 500; 
const int* a = &                  [1] 
int const *a = &                  [2] 
int* const a = &                  [3] 
const int* const a = &       [4]

如果你能區分出上述四種情況,那麼,恭喜你,你已經邁出了可喜的一步。不知道,也沒關係,我們可以參考《Effectivec++》Item21上的做法,如果const位於星號的左側,則const就是用來修飾指標所指向的變數,即指標指向為常量;如果const位於星號的右側,const就是修飾指標本身,即指標本身是常量。因此,[1]和[2]的情況相同,都是指標所指向的內容為常量(const放在變數宣告符的位置無關),這種情況下不允許對內容進行更改操作,如不能*a = 3;[3]為指標本身是常量,而指標所指向的內容不是常量,這種情況下不能對指標本身進行更改操作,如a++是錯誤的;[4]為指標本身和指向的內容均為常量。