C++的那些事:const用法面面觀

1、const是什麼

在 C/C++ 語言中,const關鍵字是一種修飾符。所謂「修飾符」,就是在編譯器進行編譯的過程當中,給編譯器一些「要求」或「提示」,但修飾符自己,並不產生任何實際代碼。就 const 修飾符而言,它用來告訴編譯器,被修飾的這些東西,具備「只讀」的特色。在編譯的過程當中,一旦咱們的代碼試圖去改變這些東西,編譯器就應該給出錯誤提示。html

因此,const修飾符的做用主要是利用編譯器幫助咱們檢查本身代碼的正確性。咱們使用const在源碼中標示出「不該該改變」的地方,而後利用編譯器,幫助咱們檢查這些地方是否真的沒有被改變過。若是咱們不當心去修改了這些地方,編譯器就會報錯,從而幫助咱們糾正錯誤。使用const和不使用const,對於最終編譯產生的代碼並無影響。數組

雖然const對於最終代碼沒有影響,可是儘量使用const,將幫助咱們避免不少錯誤,提升程序正確率。函數

2、const能夠修飾哪些對象

在上面已經提到過了,const是一種修飾符,那它能夠做爲哪些對象的修飾符呢?下面列舉了一些C/C++中用到const的地方。this

1,const變量spa

2,const指針設計

3,const引用指針

4,const類code

5,類的const成員變量htm

6,類的const成員函數對象

7,const修飾函數的形參與返回值

下面咱們分別討論上面幾種狀況下,const的用法。

3、const與變量

當一個變量被const修飾後,具備如下幾個特色:

1)該變量只能讀取不能修改。(編譯器進行檢查)

2)定義時必須初始化。

3)C++中喜歡用const來定義常量,取代原來C風格的預編譯指令define。

1 const int var; // Error:常量 變量"var"須要初始化設定項
2 const int var1 = 42;
3 var1 = 43; // Error:表達式必須是能夠修改的左值

上面代碼中第一行和第三行都有錯誤,註釋即是編譯器給出的錯誤提示。

另外注意,在使用const變量做爲數組的下標時,變量的值必定要是一個常量表達式(在編譯階段就能計算獲得結果)。

1 const int sz = 42;
2 int iAr[sz];
3 const int sz1 = size(); // size()必須是一個返回常量的函數
4 int iAr1[sz1];
5 
6 int var = 42;
7 const int sz2 = var;
8 int iAr2[sz2]; // error:sz2只有運行時才知道值

4、const與引用

咱們知道,引用必須在定義的時候賦值,這樣就會所引用的變量綁定在一塊兒並做爲它的一個別名,在程序中的其餘地方,是不能讓引用再與其餘對象綁定。這個特性,讓引用看起來就像是const對象同樣,一旦定義後將不能更改。因此並不存在const的引用。

可是咱們卻能夠引用一個const的對象(變量),咱們稱之爲對常量的引用,與普通的引用不一樣的時,對常量的引用不能被用做修改它所綁定的對象。

1 const int ci = 1024;
2 const int &r1 = ci;
3 r1 = 42; // Error:r1是對常量的引用
4 int & r2 = ci; //Error:不能將一個很是量引用指向一個常量的對象

咱們知道,引用的類型必須與其所引用對象的類型一致,以下面的代碼:

double dval = 3.14;
int& ri = dval; // Error:沒法用double類型的值初始化int&類型的引用(很是量限定)

上述代碼爲什麼不行?

此處ri引用了一個int型的整數。對於ri的操做數應該是整數運算,可是dval倒是一個雙精度的浮點數而非整數。所以爲了確保讓ri綁定一個整數,編譯器把上述代碼變成了以下形式:

double dval = 3.14;
int temp = dval;
int& ri = temp;

其中temp是一個臨時變量,而ri綁定了一個臨時量,因此當ri改變時,並無改變davl的值,因此這種引用是無效的。

也許你注意到了,當咱們把double變量綁定在一個int&類型上時,編譯器提示後有個括號:很是量限定。這說明若是是一個常量的引用,則有多是經過的,顯然下面的代碼就沒有任何問題:

double dval = 3.14;
const int& ri = dval;

由於在這裏,ri是一個常量引用,咱們並不想經過ri改變dval的值,只要能讀到dval對應的int型的值就行。

5、const與指針

 咱們知道,指針與引用不一樣,指針自己是一個對象,因此存在常量指針,這種指針在定義並初始化後,便不能再指向其餘變量。用來修飾這種常量指針的const,咱們稱之爲"頂層const"。

與頂層指針對應的是底層指針,這種指針指向一個const修改的對象,這一點上就有點像是常量的引用。

對於指向常量的指針或引用,都有如下規則:

1)能夠將一個非const對象的地址賦給一個指向const對象的指針

2)能夠將一個非const對象的地址賦給一個指向非const對象的指針

3)能夠將一個const對象的地址賦給一個指向const對象的指針

4)不能夠將一個const對象的地址賦給一個指向非const對象的指針。

1 int var;
2 const int ci = 42;
3 
4 int *p1 =& var;
5 int *p2 = &ci; // Error,const int* 不能用於初始化int*
6 const int *p3 = &var; //ok
7 const int *p4 = &ci; // ok

 還有一種指向const對象的const指針,這種指針首先代表,自己是一個const指針,一旦初始化後不能指向其餘對象;其次,它自己所指向的對象也是一個常量,即不能經過指針修改對象的值。

const int var = 42;
const int* const p = &var;

這裏再強調一點,const只是給編譯器看的,咱們能夠很輕鬆的騙過編譯器,並看看編譯器都作了什麼:

1 const int var = 42;
2 int* p = (int*)&var;
3 *p = 20;
4 cout << var << endl;     //42
5 cout << *p << endl;     //20

咱們在代碼的第2行,用一個類型轉換強制的,把一個非const指針指向了一個const對象。

可是後面咱們經過這個指針來修改這個值,卻沒有生效,緣由呢?

那是由於編譯器在編譯階段發現var是一個常量,因此在編譯目標代碼時已經將var的地方都用42進行了替換。

6、const與類

其實類定義的對象,與普通的變量是同樣的,用const修飾時,說明這個類是一個常量類對象,這個對象有下面2個特色:

1)不能改變其成員變量(非mutalbe成員)

2)不能調用其非const成員函數

 1 class AClass{
 2     public:
 3         int m_var;
 4         mutable int m_mutable_var;
 5         void setVar(int var){ m_var = var; }
 6         void printVar(){ cout << m_var; }
 7         void printVar_const()const { cout << m_var; }
 8     };
 9 
10     const AClass ac;
11     ac.m_var = 20; // Error:ac是一個const類,不能修改爲員變量
12     ac.m_mutable_var = 42; // ok 能夠修改mutable修飾的變量
13     ac.setVar(20); // Error: ac不能調用非const成員函數,並且這個成員函數還修改了成員變量的值
14     ac.printVar();// Error:ac不能調用非const成員函數
15     ac.printVar_const(); // ok

7、const與類的成員

1,const成員變量

const 成員變量指的是類中的成員變量爲只讀,不可以被修改(包括在類外部和類內部)。

1)const 成員變量必須在類的構造函數初始化表達式中被初始化,即便在構造函數體內也不能夠。

2)靜態 const 成員變量須要在類外部單獨定義並初始化(可定義在頭文件)

1 class constTestClass
2 {
3 public:
4     const int var;
5     static const int sci;
6 public:
7     constTestClass() :var(42){} // const成員變量必須在類的構造函數初始化列表中初始化
8 };
9 const int constTestClass::sci = 42; // static const成員變量須要在類外單獨進行定義和初始化

類對象的實例化過程能夠理解爲包含如下步驟:首先,開闢整個類對象的內存空間。以後,根據類成員狀況,分配各個成員變量的內存空間,並經過構造函數的初始化列表進行初始化。最後,執行構造函數中的代碼。因爲 const 成員變量必須在定義(分配內存空間)時,就進行初始化。因此須要在夠在函數的初始化列表中初始化。const成員在初始化以後,其值就不容許改變了,即使在構造內部也是不容許的。

靜態成員變量並不屬於某個類對象,而是整個類共有的。靜態成員變量能夠不依附於某個實例化後的類對象進行訪問。那麼,靜態成員變量的值,應該在任何實例化操做以前,就可以進行改變(不然,只有實例化至少一個對象,才能訪問靜態成員)。因此,靜態成員變量不可以由構造函數進行內存分配,而應該在類外部單獨定義,在實例化任何對象以前,就開闢好空間。又因爲 const 成員變量 必須初始化,因此靜態成員變量必須在定義的時候就初始化。

2,const成員函數

const成員函數指的是,此函數不該該修改任何成員變量。

1)傳給const成員函數的this指針,是指向 const 對象 的 const 指針。

2)const成員函數,不可以修改任何成員變量,除非成員變量被 mutable 修飾符修飾。

 1 class constTestClass
 2 {
 3 public:
 4     int var;
 5     const int ci;
 6     mutable  int mci;
 7 public:
 8     void setVar(int i);
 9     void setMci(int i)const;
10 };
11 void constTestClass::setVar(int i)
12 {
13     var = i; // ok
14     mci = i; // ok
15     ci = i;  // Error:ci是一個const對象不能修改
16 }
17 void constTestClass::setMci(int i)const
18 {
19     var = i;    // ok
20     mci = i;    // ok  mutable成員變量能夠被const成員函數修改
21     ci = i;    // Error
22 }

在成員函數調用的過程當中,都有一個 this 指針被當作參數隱性地傳遞給成員函數(可能經過棧,也可能經過CPU寄存器)。這個this指針,指向調用這個函數的對象(這樣,成員函數才能找到成員變量的地址,從而對其進行操做)。這個this指針,是個 const指針,不能修改其指向(你不但願這個對象的函數,修改了那個對象的成員變量,對吧?)。

傳遞給const成員函數的this指針,指向一個const對象。也就是說,在const成員函數內部,這個this指針是一個指向const對象的const指針。

mutable 修飾符使得const函數的行爲有了一些靈活性。至關於提醒編譯器,這個成員變量比較特殊,就不要進行任何只讀檢查了。

爲何 const 對象只可以調用const成員函數呢?,實際上是這樣的。因爲對象自己經過 const 修飾,那麼指向這個對象的指針也就是指向const對象的const指針了。換句話說,指向這個對象的this指針就是指向const對象的const指針。通常成員函數要求的this指針爲:指向對象的const指針。因此此處發生了參數不匹配,沒法進行調用。而 const 成員函數要求的this指針,偏偏是 指向const對象的const指針。因此依然可以調用。

8、const與函數

將函數的形參用const修飾是但願實參在函數內部不被修改,而通常函數接口可能會遇到如下三種狀況:

1,const對象

2,指向const對象的指針

3,綁定const對象的引用

4,返回值是一個const對象

首先,咱們看const對象的形參,這種接口用const修飾實際上沒有任何意義,由於實參在傳遞給實參時是傳遞了一份副本,原實參是不會變化的。

 1 int main(void)
 2 {
 3     int var = 42;
 4     fun(var);
 5     cout << var << endl; // print 42
 6     return 0;
 7 }
 8 void fun( int i)
 9 {
10     i = 10;
11 }

經過上面代碼能夠看出,實參若是隻能過值進行傳遞,函數接口不用const修改,也不會令實參的值改變。

而經過指針或引用傳遞給函數時,函數就能夠經過形參來改變實參的值,這裏若是須要對實參進行保護,則須要在函數接口聲明形參爲指向const類型的指針或引用。

 1 void fun( const int* p)
 2 {
 3     *p = 42; // error
 4     int var = 10;
 5     p = &var;  // 能夠改變p自己的值
 6 }
 7 void fun(const int& p)
 8 {
 9     p = 42; // error,p是一個指向const對象的引用
10 }

有的時候,咱們須要函數的返回值是一個const對象,好比咱們考慮一個有理數據類,咱們給類定義了一個*的重載。

1 class Rational{
2 // ....
3 };
4 const Rational operator* (const Rational& lhs, const Rational& rhs);
5 Rational a, b, c;
6 a*b = c; // Error,由於左端爲一個const對象

若是上面代碼中重載操做符返回對象不是const類型,則a*b=c這個式子就成立,實際上這與咱們的內置類型的算術運算原則違背了,而咱們但願咱們設計的類的操做意義要像內置內類同樣。

 

參考博文:

[1]:C/C++中const修飾符用法總結

[2]:C++const變量使用技巧總結

相關文章
相關標籤/搜索