const
是C++提供的一個強大的關鍵字,const
的用法很是多,但總的來講,const
的做用只有一個:保證被修飾的內容不會被程序所修改。安全
對一個類型的對象使用const
修飾即限定這個對象是隻讀的,不能進行修改操做,因爲沒法進行修改操做,這也就要求咱們在聲明const
對象時必須同時賦值或初始化。const對象的初始化通常是以下形式:函數
const TypeName Var = Expression;
示例:this
const int a = 0; int const a = 0; // 等價寫法 a = 1; // 編譯報錯
能夠注意到的是,const對象的初始化是用表達式初始化的,只是在咱們的示例中使用的是常量表達式。事實上,const初始化能夠是以下形式:指針
int getA() { return 0; } const int a = 0; // 字面量0是常量表達式,在編譯期就能肯定,a在編譯期完成初始化 const int b = getA(); // getA()在編譯期編譯,b在運行時初始化
一個特殊狀況是,指望這個const
對象存在於全局做用域,那麼能夠在聲明時加上extern
修飾,那麼就能夠在聲明時不賦初值,但必須確保程序至少有一處聲明賦初值。code
// A.cpp extern const int a; // B.cpp extern const int a = 0;
const引用是對const對象的引用,也就是說,它只是確保不去修改引用的內容,與引用的對象是不是const
對象無關。對象
int a = 0; const int b = 0; const int &c = a; // 正確,能夠直接修改a的值,但不能經過c修改a const int &d = b; // 正確,b、d均不可修改
須要注意的是,把const引用綁定到臨時對象上是非法的。作用域
int a = 0; const int &b = a + 1; // 這裏a+1生成了一個臨時對象 //等價於 const int temp1 = a + 1; const int &b = temp1; const double &c = a; // 這裏經過隱式類型轉換生成了臨時對象 // 等價於 const double temp = a; const double &c = temp;
雖然能夠經過編譯,但這是無心義的引用綁定。get
const也能夠修飾指針。因爲多級指針的存在,const的結合也就變得複雜起來。io
int a = 0; const int b = 0; const int *c = &a; // 合法,c是一個指向常量的指針,儘管a自己不是常量 // 等價於 int const *c = &a; const int *d = &b; // 合法,b是常量,c是指向常量的指針 int *e = &b; // 非法,b是常量,普通指針沒法指向常量地址 const int *f = &b; // 合法,b是常量,f是指向常量的指針
最多見的是常量指針和指向常量的指針,前者表示指針是一個常量,即指向的地址不能修改,後者表示指向的地址所存儲的內容是常量。常量指針和指向常量的指針是用const
修飾的一級指針的兩種狀況,在《C++ Primer》一書中,二者分別稱爲頂層const和底層const。編譯
const int a = 0; const int *b = &a; // b是指向常量的指針,底層const int c = 0; int * const d = &c; // d是一個常量指針,頂層const const int * const e = &a; // e是一個指向常量的常量指針
在涉及到多級指針時,能夠從右往左閱讀聲明表達式,確認const
修飾的是哪一級。
int a = 0; int *b = &a; // b是一個一級指針 int **c = &b; // c是一個二級指針 int **const *d = &c; // d是一個三級指針 /* 從右往左閱讀表達式: 1.首先聲明瞭一個變量d 2.下一個是*,說明d是一個指針,它指向了一個對象 3.接着是const,說明它指向的這個對象不能修改 4.接着又是一個*,說明指向的對象也是一個指針 5.而後是最後的*,說明指向的指針指向的對象還是一個指針 6.最後是int,說明最後一級指針指向的是一個int類型的地址 在理解這個聲明以後很容易就能夠對下面的賦值作判斷 */ d = &c; // 正確,d是一個普通指針 *d = &b; // 錯誤,解引用d獲得的是一個常量對象 **d = &a; // 正確,二次解引用d獲得的是一個普通指針
前面提到,能夠用常量表達式或者很是量表達式初始化const對象。所謂的常量表達式是指在編譯期就能夠獲得結果的表達式,由常量表達式初始化的const對象也能夠參與組成常量表達式。在某些時候,咱們但願一個表達式能在編譯期就獲得肯定,但在複雜的項目中確認一個對象是否是常量表達式很是困難,由此C++引入了constexpr
關鍵字,用於顯式說明某個對象是常量表達式。
constexpr int a = 0; // 正確,用字面量0初始化常量表達式 constexpr int b = getB(); // 正確與否取決於getB()是不是常量表達式
因爲須要在編譯期就肯定constexpr對象的值,這也就對指針和引用的constexpr初始化提出了更嚴格的要求:通常狀況下,定義在函數內的對象地址沒法在編譯期肯定,所以沒法做爲初始化常量表達式的值,相反,全局對象能夠。
const
能夠修飾函數的形參。
int LiF(const int lif); // 正確,在函數內部不能修改lif // 固然,形參自己只是一個拷貝,在函數調用過程當中發生的修改並不會反饋到實參 int LiF(const int *lif); // 正確,保護原數據不被修改 int LiF(const int &lif); // 正確,這是最經常使用的寫法,兼具效率與安全性
const
還能夠修飾函數的返回值。const
確保函數的返回值不會被修改,即沒法用做左值。
const int& LiF(int &lif) { return lif; }
const
能夠修飾類的成員,因爲調用構造函數時就已經確認了對象的內容,也就是說,const成員須要在構造函數以前初始化,那麼,被修飾的類成員只能經過初始化列表初始化。
class LiF { public: LiF(int _lif): lif(_lif) {} private: const int lif; };
const能夠修飾類的成員函數,被修飾的成員函數稱爲常成員函數,常成員函數能夠被全部對象調用,但常對象只能調用常成員函數。這是由於,成員函數的參數列表裏隱式傳遞了一個this
指針,用const
修飾成員函數,其實是修飾this
,而const *
是沒辦法轉換成普通指針類型的,故不能調用普通成員函數。又因爲函數重載不會忽略掉底層const,故根據成員函數的const
也能夠構成重載。很是對象會經過精確匹配找到普通成員函數,而常對象則會匹配到對應的常成員函數。
class LiF { public: int get() { return lif; } int get() const { return lif; } // 常成員函數重載 private: int lif; }; LiF l1; const LiF l2; l1.get(); // 調用的是int get(); l2.get(); // 調用的是int get() const;
有時咱們但願類的成員能記錄某些信息,即使是在const對象內。這時就須要一個永遠可變的成員,對應地,C++提供了mutable
關鍵字。
class LiF { public: void count() const { lif++; } private: mutable int lif; }; LiF l1; const LiF l2 = l1; l2.count();
const
還能夠修飾成員函數的返回值,與普通函數的const返回值相似,以禁止鏈式調用,或者說禁止返回值成爲左值。
class LiF { public: const LiF& operator= (const LiF &l) { lif = l.lif; return *this; } const LiF& set(int _lif) { lif = _lif; return *this; } private: int lif; }; LiF l1, l2, l3; l1 = l2 = l3; // 合法 (l1 = l2) = l3; // 非法,重載後的賦值運算符返回值是常量,不能再次賦值 LiF l4; l4.set(1); // 合法 l4.set(1).set(2); // 非法,set(1)以後返回的是常量this