先說結論
相同點:const和consexpr都是用來定義常量的。
不同點:const聲明的常量,初始值引用的對象不一定是一個常量;constexpr聲明的常量,初始值一定是常量表達式。
constexpr是c++11標准添加的關鍵字。
之所以說const聲明的常量,初始值不一定是一個常量,主要是從引用和指針的角度出發的。如果初始化const時候,指定一個固定的字面值常量,那么它引用的肯定是常量。
const int i = 10; constexpr int j = 20;
這種情況下,i和j都是常量,而且它們引用的也是一個常量(因為是固定的字面值)。那么如果它們引用的不是固定的字面值,而是指針和引用呢?接下來筆者將從引用和指針的角度出發,解釋const和constexpr的區別:
const與引用
通過如下語法可以聲明一個常量int類型引用:
const int &v;
注意這里的v是一個常量int引用,也就是說,v是肯定是一個int類型的常量引用(值不能改變),應該給它賦值常量int類型,如果我們給它賦值變量int類型會怎么樣呢?看下面的案例。
例如:
#include <iostream> using namespace std; int main(){ int a = 20; const int &b = a;//引用a,常量int引用b引用了非常量inta cout << "a = " << a << ", b = " << b << endl; a = 10;//可以通過a改變變量的值 cout << "a = " << a << ", b = " << b << endl; //b = 20;//出錯,不可以通過b改變變量的值,因為b一個常量引用,所以不能通過b去改變。 return 0; }
結果:
a = 20, b = 20
a = 10, b = 10
上面的案例中,a是一個變量,b是一個常量引用。a變量的值不能通過b來改變,但是可以通過a來改變,因為a不是常量,b是常量引用(b認為自己引用的是一個常量,實際卻不是)。
const與指針
可以通過如下的方式,來聲明一個常量指針。
int *const p;
p首先是一個常量,然后再是一個指針,並且這個指針指向一個int類型。
下面的案例
#include <iostream> using namespace std; int main(){ int i = 10; int *const p = &i;//指向非常量的常量指針p,指向了非常量i cout << "i = " << i << ",*p = " << *p << endl; i = 20; cout << "i = " << i << ",*p = " << *p << endl; *p = 30; cout << "i = " << i << ",*p = " << *p << endl; int j = 0; //p = &j;//出錯 return 0; }
輸出
i = 10,*p = 10
i = 20,*p = 20
i = 30,*p = 30
上面的案例中p是一個常量類型的指針,並且指向一個非常量int對象。由於p是指針,所以*p解地址后實際上是變量i,所以可以通過*p改變變量的值。但是p = &j語句,會改變p變量中存儲的地址(初始化時存儲的是變量i的地址),由於p是常量,所以p中的值是不能改變,因此p = &j會報錯。
當然可以這樣定義
const int *const p;
這樣的話,p是一個常量指針,並且指向常量int類型。
例如:
#include <iostream> using namespace std; int main(){ int i = 10; const int *const p = &i;//指向常量int的常量指針p,指向了非常量i cout << "i = " << i << ",*p = " << *p << endl; i = 20; cout << "i = " << i << ",*p = " << *p << endl; //*p = 30;//出錯 int j = 0; //p = &j;//出錯 return 0; }
輸出結果:
i = 10,*p = 10
i = 20,*p = 20
雖然常量指針p應該指向一個常量int類型,但是筆者給它一個非常量int類型的地址,這樣一來,p會認為它指向的是一個常量int類型,所以當通過*p = 30改變它的值時,不能通過。但是通過i依然可以修改。
小結:
常量引用可以引用非常量,非常用引用不能引用常用。
指向常量的指針可以指向一個非常量,指向非常量的指針不能指向常量。
從邏輯上可以這樣理解:因為非常量具有可讀可寫的功能,常量只有可讀的功能;當常量引用非常量時,常量只期望可以讀數據,非常量不僅提供了讀數據,而且非常量還可以寫數據(修改),因此常量引用非常量可以滿足常量的需求,可以通過;返過來,常量不能夠滿足非常量的需求,所以不能通過。
int a = 10; const int &b = a;//正確,b只需要讀數據,a可以讀數據,所以可以通過。 const int c = 10; int &d = c;//錯誤,d需要讀數據和寫數據,c只能提供讀數據,所以不通過。 int e = 10; const int *f = &e;//正確,*f只需要能夠讀數據,e可以讀數據,所以可以通過。 const int g = 10; int *h = &g;//錯誤,*h需要讀數據和寫數據,g只能提供讀數據,所以不通過。
上面筆者總結的規律還有一些需要補充,在不改變const對象的操作中還有一種是初始化,如果一個利用對象去初始化另外一個對象(引用和指針除外,這里主要是指拷貝),則他們是不是const都無關緊要:
int i = 42; const int ci = i; // 正確:i的值被拷貝給了ci int j = ci; //正確 : ci的值被拷貝給了j
盡管ci是const類型,j是int類型。ci的常量特征僅僅在執行改變ci的操作時才會發揮作用,當用ci初始化j時,更本無需在意ci是不是一個常量。拷貝一個對象的值不會改變它,一旦拷貝完成,新的對象和原來的對象就沒什么關系了。
constexpr
在上面的說過了const的特點,可以得出,當const變量引用或指向某個變量時,被引用或指向的變量是不能確定是否是一個常量的。
C++11標准提供了constexpr關鍵字,被constexpr修飾的變量的初始值必須是常量表達式,也就是說,被constexpr修飾的變量,肯定是常量,而且引用常量表達式。
constexpr int m = 10;//20是常量表達式
constexpr int n = m + 1;//m+1是一個常量表達式
//* & 都不屬於常量表達式
constexpr const int *p = &m;//錯誤
constexpr const int &r = m;//錯誤