c++中使用到const
的地方有不少, 並且const
自己也針對不一樣的類型可能有不一樣的含義, 好比對指針就有頂層和底層. 本節就是探討關於C++中const
的在不一樣的地方不一樣表現或含義.c++
const
修飾的對象一旦建立通常就不能改變, 因此對於const
對象必須進行初始化.函數
int i = 0; const int j; // error. 必須進行初始化 const int j = 0;
初始化時並不關心初始化對象的是const
仍是非const
this
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, 好比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的時候出現問題. 具體什麼問題分析以後再來總結.
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 &i
與const int &i
有區別, 前者類型爲int
, 後者類型爲const int
, 因此後者是底層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
, 即
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
, 因此與之對應的函數應該是常量成員函數, 因此最好在定義類函數實現時, 重載一個常量成員函數.
一樣上面的類爲例子
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
要求必須在建立的時候就須要對其初始化, 因此上式的例子才成立.
當咱們不肯意每次都定義指針的時候, 就想到用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
, 怎樣重載, 基本不少的問題都是衍生.