C++:const

const

const是C++提供的一個強大的關鍵字,const的用法很是多,但總的來講,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對象的引用,也就是說,它只是確保不去修改引用的內容,與引用的對象是不是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也能夠修飾指針。因爲多級指針的存在,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獲得的是一個普通指針

constexpr表達式

前面提到,能夠用常量表達式或者很是量表達式初始化const對象。所謂的常量表達式是指在編譯期就能夠獲得結果的表達式,由常量表達式初始化的const對象也能夠參與組成常量表達式。在某些時候,咱們但願一個表達式能在編譯期就獲得肯定,但在複雜的項目中確認一個對象是否是常量表達式很是困難,由此C++引入了constexpr關鍵字,用於顯式說明某個對象是常量表達式。

constexpr int a = 0; // 正確,用字面量0初始化常量表達式
constexpr int b = getB(); // 正確與否取決於getB()是不是常量表達式

因爲須要在編譯期就肯定constexpr對象的值,這也就對指針和引用的constexpr初始化提出了更嚴格的要求:通常狀況下,定義在函數內的對象地址沒法在編譯期肯定,所以沒法做爲初始化常量表達式的值,相反,全局對象能夠。

const與函數

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能夠修飾類的成員,因爲調用構造函數時就已經確認了對象的內容,也就是說,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
相關文章
相關標籤/搜索