C++: const and constexpr
const修飾變數
如上節所述,用const修飾變數的語義是要求編譯器去阻止所有對該變數的賦值行為。因此,必須在const變數初始化時就提供給它初值:
const
int
i;
i = 5; // !error
const
int
j = 10; // ok
這個初值可以是編譯時即確定的值,也可以是運行期才確定的值。如果給整數類型的const變數一個編譯時初值,那麼可以用這個變數作為聲明數組時的長度:
const
int
COMPILE_CONST = 10;
const
int
RunTimeConst = cin.get();
int
a1[COMPLIE_CONST]; // ok in C++ and error in C
int
a2[RunTimeConst]; // !error in C++
因為C++編譯器可以將數組長度中出現的編譯時常量直接替換為其字面值,相當於自動的宏替換。(gcc驗證發現,只有數組長度那裡直接做了替換,而其它用COMPILE_CONST賦值的地方並沒有進行替換。)
文件域的const變數默認是文件內可見的,如果需要在b.cpp中使用a.cpp中的const變數M,需要在M的初始化處增加extern:
//a.cppextern const int M = 20; //b.cppextern const int M;
一般認為將變數的定義放在.h文件中會導致所有include該.h文件的.cpp文件都有此變數的定義,在鏈接時會造成衝突。但將const變數的定義放在.h文件中是可以的,編譯器會將這個變數放入每個.cpp文件的匿名namespace中,因而屬於是不同變數,不會造成鏈接衝突。(注意:但如果頭文件中的const量的初始值依賴於某個函數,而每次調用此函數的返回值不固定的話,會導致不同的編譯單元中看到的該const量的值不相等。猜測:此時將該const量作為某個類的static成員可能會解決此問題。)
const修飾指針與引用
const修飾引用時,其意義與修飾變數相同。但const在修飾指針時,規則就有些複雜了。
簡單的說,可以將指針變數的類型按變數名左邊最近的『*』分成兩部分,右邊的部分表示指針變數自己的性質,而左邊的部分則表示它指向元素的性質:
const
int
*p1; // p1 is a non-const pointer and points to a const int
int
* const
p2; // p2 is a const pointer and points to a non-const int
const
int
* const
p3; // p3 is a const pointer and points to a const it
const
int
*pa1[10]; // pa1 is an array and contains 10 non-const pointer point to a const int
int
* const
pa2[10]; // pa2 is an array and contains 10 const pointer point to a non-const int
const
int
(* p4)[10]; // p4 is a non-const pointer and points to an array contains 10 const int
const
int
(*pf)(); // pf is a non-const pointer and points to a function which has no arguments and returns a const int
...
const指針的解讀規則差不多就是這些了……
指針自身為const表示不可對該指針進行賦值,而指向物為const則表示不可對其指向進行賦值。因此可以將引用看成是一個自身為const的指針,而const引用則是const Type * const指針。
指向為const的指針是不可以賦值給指向為非const的指針,const引用也不可以賦值給非const引用,但反過來就沒有問題了,這也是為了保證const語義不被破壞。
可以用const_cast來去掉某個指針或引用的const性質,或者用static_cast來為某個非const指針或引用加上const性質:
int
i;
const
int
*cp = &i;
int
*p = const_cast<int
*>(cp);
const
int
*cp2 = static_cast<const
int
*>(p); // here the static_cast is optional
C++類中的this指針就是一個自身為const的指針,而類的const方法中的this指針則是自身和指向都為const的指針。
--------
constexpr是C++11中新增的關鍵字,其語義是「常量表達式」,也就是在編譯期可求值的表達式。最基礎的常量表達式就是字面值或全局變數/函數的地址或sizeof等關鍵字返回的結果,而其它常量表達式都是由基礎表達式通過各種確定的運算得到的。constexpr值可用於enum、switch、數組長度等場合。
constexpr所修飾的變數一定是編譯期可求值的,所修飾的函數在其所有參數都是constexpr時,一定會返回constexpr。
constexpr int Inc(int i) { return i + 1;} constexpr int a = Inc(1); // okconstexpr int b = Inc(cin.get()); // !errorconstexpr int c = a * 2 + 1; // ok
constexpr還能用於修飾類的構造函數,即保證如果提供給該構造函數的參數都是constexpr,那麼產生的對象中的所有成員都會是constexpr,該對象也就是constexpr對象了,可用於各種只能使用constexpr的場合。注意,constexpr構造函數必須有一個空的函數體,即所有成員變數的初始化都放到初始化列表中。
struct A { constexpr A(int xx, int yy): x(xx), y(yy) {} int x, y;}; constexpr A a(1, 2);enum {SIZE_X = a.x, SIZE_Y = a.y};
constexpr的好處:
- 是一種很強的約束,更好地保證程序的正確語義不被破壞。
- 編譯器可以在編譯期對constexpr的代碼進行非常大的優化,比如將用到的constexpr表達式都直接替換成最終結果等。
- 相比宏來說,沒有額外的開銷,但更安全可靠。
--------------
談談 C++ 的編譯期計算
const 變數
const
修飾變數,表示這個變數是不可修改,const
變數必須初始化,一經初始化就不可修改:
- 編譯時初始化。
const int SIZE = 100;
- 運行時初始化。
vector<int> v;const int i = v.size();
constexpr 變數
const
變數的值可以在編譯時或運行時確定,與const
相比,constexpr
的限制更多,因為constexpr
變數的值必須在編譯時就能確定。
constexpr
變數正好滿足要求:
- 數組的大小必須是編譯期常量。
std::array
的大小必須是編譯期常量。std::bitset
的大小必須是編譯期常量。
constexpr auto SIZE = 100;std::array<int, SIZE> arr;
constexpr 函數
constexpr
函數則與編譯期計算有關,要是constexpr
函數所使用的變數其值能夠在編譯時就確定,那麼constexpr
函數就能在編譯時執行計算。另一方面,要是constexpr
函數所使用的變數其值只能在運行時確定,那麼constexpr
就和一般的函數沒區
constexpr function: 在程序編譯的時候就已經執行好了。代價是增加編譯時間,但程序能執行得更高效。
struct S { constexpr S(int);};const S s0(0);constexpr S s1(1);
s0
is a constant, but it does not promise to be initialized at compile-time. s1
is marked constexpr
, so it is a constant and, because S
s constructor is also marked constexpr
, it will be initialized at compile-time.
Mostly this matters when initialization at runtime would be time-consuming and you want to push that work off onto the compiler, where its also time-consuming, but doesnt slow down execution time of the compiled program
推薦閱讀:
※C 語言比 C++ 更強大嗎?
※C++中lambda與function的推導問題?
※C++序列化json字元串對Unicode有哪些特殊處理?