const淺析

前言

c++中使用到const的地方有不少, 並且const 自己也針對不一樣的類型可能有不一樣的含義, 好比對指針就有頂層和底層. 本節就是探討關於C++中const的在不一樣的地方不一樣表現或含義.c++

const

關於const :

  • const修飾的對象一旦建立通常就不能改變, 因此對於const對象必須進行初始化.函數

    int i = 0;
    const int j;    // error. 必須進行初始化
    const int j = 0;
  • 初始化時並不關心初始化對象的是const仍是非constthis

    int i = 0;
    const int j = i;    // i 是非const也能夠
  • const不能改變.net

    const int i = 0;
    i = 1;  // error const的對象通常不能進行修改
  • 引用對象的類型必須與其所引用對象的類型一致指針

    int i = 0;
    int &j = i;
    double &size = j; // error. size與j的類型不一致
    • 由於以上引用的規則, 因此const類型的引用只能被const的對象引用
    int i = 0;
    const int &size = i;
    int &j = size; // error. size的類型爲const int, j的類型爲 int. 二者並不匹配
    • 引用類型對應的例外code

      int size = 0;
      const double &i = size; // size與i的類型雖然不一致, 可是由於const的緣由使得等式成立

      緣由 : 雖然i與size二者的類型並不一致, 可是初始化i時, 編譯器會爲size生成一個臨時量(double j = size;), 而後i最終綁定在這個臨時量上(const double &i = j ). i 之因此能綁定在一個臨時量上, 仍是由於const的對象不能被修改, 則i 沒法被修改, 保障了臨時量不會被改變.對象

      注意 i實際綁定在臨時量上, 並無綁定在size上blog

      int size = 0;
      const double &i = size;
      size = 1;   // i 實際值並無改變, 它綁定的是臨時量不是size
  • 修改const對象的值get

    int i = 0;
    const int size = i;
    const int &j = i;
    const_cast<int&>(size) = 1; // 將size的值修改成1
    i = 2;  // 由於j綁定i, i被修改則j也被修改

    由於const只是對修飾的對象限制其不能修改, 不能保證對象必定是常量, 因此能保證是常量的對象最好都定義成constexpr . 對constexpr不清楚的能夠看一下constexpr淺析編譯器

頂層與底層const概念

頂層const : 指針自己是一個常量(即地址不容許改變).

其實咱們一直都有在用頂層const, 好比int i = 0;, 這就是一個頂層const, 由於 i 的地址不會改變, 只有值會被改變.

int size = 0, i = 0;    // 實際上是頂層const
int *const p = &size; // const直接修飾指針自己, 頂層const
p = &i;     // error. p是頂層const
*p = 1;     // 頂層const能夠直接修改值

底層const : 指針所指的對象是一個常量(指針自己是能夠修改的, 只是指向的值不能進行修改).

int size = 0, i = 0;
const int * p = &size; // const直接修飾指針指向的對象, 底層const
ptr = &i;   // ptr能夠從新指向其餘地址, 由於是底層const
*ptr = 1;   // error. 底層const不能直接修改指向的值

固然咱們能夠將一個對象修飾爲既是頂層又是底層

int size = 0;
const int * const p = &size;    // 既是頂層又是底層
const int i = 0;    // 既是頂層又是底層

有一點必定要注意 : 頂層const被拷貝時會忽略掉頂層const

const int i = 0;
int size = i;   // 這裏的頂層const被忽略掉了
auto j = i;     // 此時 auto 推斷出 j的類型爲 int , i 的頂層const被忽略了

const與重載函數

在重載函數時, const類型的參數可能會在處理頂層const與底層const的時候出現問題. 具體什麼問題分析以後再來總結.

void Ccount(int ccount) {}          // ccount爲頂層const
void Ccount(const int ccount) {}    // error. ccount也是爲頂層const, 賦值時會忽略掉頂層const, 就與上面的函數同樣了

void Ccount_pointer(int *ccount) {} // ccount爲頂層const
void Ccount_pointer(int *const ccount) {}   // error. ccount也是爲頂層const, 賦值時會忽略掉頂層const, 就與上面的函數同樣了

上面能夠看出來, 由於頂層const會被忽略, 因此頂層const與另外頂層const不能被區分出來.

// error. 在函數調用的時候有二義性, 並不能區分調用哪個函數, 在編譯期間報錯. 
void const_reference(int i) {}  // i 是頂層const. 參數類型爲 int
void const_reference(int &i) {} // i 是頂層const. 參數類型爲 int

// 下面都沒有問題
void const_reference(int &i) {} // i 是頂層const. 參數類型爲 int
void const_reference(const int &i) {}   // i 是底層const. 參數類型爲const int

void const_pointer(int *i) {}       // 頂層const
void const_pointer(const int *i) {} // 底層const

由於引用對象的類型必須相同, 因此int &iconst int &i有區別, 前者類型爲int , 後者類型爲const int, 因此後者是底層const.

上面能夠看出來, 由於底層const不會被忽略, 底層與底層有區分, 因此能夠底層const能夠用來重載.

const與類的常量成員函數

若是const放在函數名的前面其意義只是告訴編譯器返回類型是const類型的常量而已, 可是若是把const放在函數名後那就又是另外一種狀況了, 咱們這裏主要分析的就種狀況.

const int const_func(int i) {return i;} // 這裏函數返回的是const類型的, 即常量
int const_func(int i) const {return i;} // error. const不能直接放在普通函數名的後面, 只能放在成員函數(類函數)名的後面,緣由以後分析.

定義一個簡單的類

class A {
    private: int nun;
    public: int const_func() const {return 0;}  // success
};
// 若是將函數改成
int const_func() const { ++num; return 0;}  // error

int const_func() const函數中const是告訴編譯器, 類中定義的非靜態變量都不能進行修改. 緣由在於類的全部成員函數都會隱式的傳入this 指針, 即上面的成員函數被修改成

int const_func(const A * const this)  { ++num; return 0;}

this指針自己就是頂層const, 而放在函數名後面的const是爲了修飾this指針的, 可是由於this指針不能顯示的被傳入, 因此const只能放在函數名後.

知道了這裏const修飾的是this 指針, 因此this->i就不能被修改了, 而靜態成員不是屬於實例化類自己, 也就沒有this指向靜態變量, 因此能夠在以上類型的函數中修改靜態變量.

const放在成員函數名後面的函數咱們稱爲常量成員函數

可是有的時候非要在以上函數中改變某個變量的值怎麼辦? c++中有mutable關鍵字, 就是容許這樣的特例發生. mutable就是告訴編譯器, num能夠在任何函數中進行修改.

class A {
    private: mutable int nun;
    public: void const_func() const {++num;}    // success
};

const與類

咱們在定義類的實例化時, 可能會將類實例化定義爲const, 即

class A {
    private: int nun;
    public: int const_func()  {return 0;}   
};
A a;
const A ca;
a.const_func(); // success
ca.const_func(); // error

上面出錯的緣由在於ca的類型爲const, 因此與之對應的函數應該是常量成員函數, 因此最好在定義類函數實現時, 重載一個常量成員函數.

const與類靜態成員

一樣上面的類爲例子

class A {
    private: static int nun = 0; // error
    public: int const_func() const {++num; return 0;}   // success. 緣由上面分析了
};

在類中定義的靜態變量不能在類中初始化, 必須在類外進行初始化, 否則報錯. 因此上面應該在類外改成int A::num = 0; .

可是有一個例外 :

class A {
    private: const static int nun = 0; // success
};

由於const要求必須在建立的時候就須要對其初始化, 因此上式的例子才成立.

const與typedef

當咱們不肯意每次都定義指針的時候, 就想到用typedef來定義指針類型. 即:

typedef char * Str;
char *str1 = "hello";
const char *str2 = "hello";
const Str str3 = "hello";

對其進行相同的操做

str1[0] = 'a';
str2[0] = 'a';  // error
str3[0] = 'a';  // success

str1++;
str2++; // success
str3++; // error

以上面的執行的操做能夠看出來typedef不只僅只是一個替換, 它將const Str str3轉換爲了char *const str3而不是跟str2同樣.

緣由是 : char *重寫聲明以後, 真實的數據類型變成了char 而不是char *, 反而*成了聲明符的一部分了, 致使const Str的數據類型爲const char, 而*修飾const char, 也就成了常量指針.

總結

本節彙總了部分關於const用法的注意點, 可能看起來會很暈, 也不是一次性就容易記住, 但願在看的時候最好也進行驗證是最好的. 最主要記住底層const和頂層const, 怎樣重載, 基本不少的問題都是衍生.

相關文章
相關標籤/搜索