- const修飾常量,但是const并未區分編譯時常量和運作時常量,而constexpr則隻能是編譯時常量,在C++11中提出。
- 這篇文章,将詳細講解constexpr。
目錄
- 一、常量表達式
- 二、constexpr變量
-
- 三、constexpr函數
- 四、字面值類型
- 五、指針和constexpr
- 六、字面值常量類
- 參考
一、常量表達式
常量表達式(const expression):指值不會改變并且在編譯階段過程就能得到計算結果的表達式。
以下兩種是常量表達式:
const int maxSize = 10;
const int limit = maxSize + 1;
以下兩種不是常量表達式:
int staff_size = 27;
const int sz = get_size();
- staff_size的初始值雖然是個字面值常量,但它的資料類型隻是普通的int而非const int,還是可以被重新指派的,是以不是常量表達式。
- sz本身是一個常量,但它的具體值直到運作時才能擷取到,是以也不是常量表達式。
二、constexpr變量
在一個複雜系統中,很難分辨一個初始值到底是不是常量表達式。從前面的例子可以發現,即使變量加上const,但是指派是在運作時确定的也不是常量表達式。
C++11新标準規定,允許将變量聲明為constexpr類型以便由編譯器來驗證變量的值是否是一個常量表達式。
- 聲明為constexpr的變量一定是一個常量。
- 必須用常量表達式初始化。
constexpr int mf = 20; //20是常量表達式
constexpr int limit = mf + 1; //mf + 1是常量表達式
constexpr int sz = size(); //隻有當size是一個constexpr函數時才是一條正确的聲明語句
size()函數也需要constexpr修飾,成為constexpr函數。
三、constexpr函數
constexpr函數指能用于常量表達式的函數。定義constexpr函數有幾項約定:
- 函數的傳回值類型及所有的類型都得是字面值類型。
- 函數體中必須有且隻有一條return語句。
constexpr int new_sz() { return 40; }
constexpr int foo = new_sz(); //正确:foo是一個常量表達式
因為編譯器能在程式編譯時驗證new_sz函數傳回的是常量表達式,是以可以用new_sz函數初始化constexpr類型的變量foo。
(1)執行初始化任務時,編譯器把對constexpr函數的調用替換成其結果值。為了能在編譯過程中随時展開,constexpr函數被隐式地指定為内聯函數。
(2)constexpr函數體内也可以包含其他語句,隻要這些語句在運作時不執行任何操作就行。例如,constexpr函數中可以有空語句、類型别名、using聲明。
(3)constexpr函數的傳回值可以不是一個常量:
//cnt如果是常量表達式,傳回值就是常量表達式
constexpr size_t scale(size_t cnt) { return new_sz() * cnt; }
比如,下面兩個例子:
int arr[scale(2)]; //正确:scale(2)是常量表達式
int i = 3;
int a[scale(i)]; //錯誤:scale(i)不是常量表達式
- 給scale傳入字面值為2的常量表達式時,它的傳回類型也是常量表達式。此時編譯器用對應的結果值(80)替換為對scale函數的調用。
- 當我們用一個非常量表達式調用scale函數時,比如
的對象i,傳回值則不是一個常量表達式。當把scale函數用在需要常量表達式的上下文中時,編譯器發現不是常量表達式,發出錯誤資訊。int i = 3
(4)constexpr函數通常定義在頭檔案中。因為編譯器要想展開函數不僅需要函數聲明還需要函數定義,而constexpr函數可以在程式中多次定義,但多個定義必須完全一緻。
四、字面值類型
常量表達式的值需要在編譯時就得到計算,是以對聲明constexpr時用到的類型必須有所限制。因為這些類型一般比較簡單,值也顯而易見、容易得到,稱之為"字面值類型"(literal type)。
字面值類型包括:算數類型、引用、指針,自定義類、string等類型不是字面值類型,也就不能定義成constexpr。
盡管指針和引用都能定義成constexpr,但它們的初始值卻受到嚴格限制。一個constexpr指針的初始值必須是
nullptr
或者
,或者是存儲在某個固定位址中的對象。
函數體内定義的變量一般來說并非存放在固定位址中,是以constexpr指針不能指向這樣的變量。相反的,定義在函數體之外的對象位址固定不變,能用來初始化constexpr指針。
五、指針和constexpr
(1)如果在constexpr聲明中定義了一個指針,限定符constexpr僅對指針有效,與指針所指的對象無關。
const int *p = nullptr; //p是一個指向整數常量的指針
constexpr int *q = nullptr; //q是一個指向整數的常量指針
q是一個常量指針,因為constexpr把它所定義的對象置為了頂層const。類似于:
int *const q = nullptr;
(2)與其他常量指針類似,constexpr指針即可以指向常量也可以指向一個非常量:
constexpr int *np = nullptr; //np是一個指向整數的常量指針,其值為空
int j = 0;
constexpr int i = 40; //i的類型是整數常量
//假設i和j都定義在函數體之外
constexpr const int *p = &i; //p是常量指針,指向整型常量i
constexpr int *p1 = &j; //p1是常量指針,指向整數j
六、字面值常量類
constexpr函數的參數和傳回值必須是字面值類型。注意,函數的傳回值必須是字面值類型,但可以不是一個常量。
和其他類不同,字面值類型的類可能含有constexpr函數成員。這樣的成員必須符合constexpr函數的所有要求,它們是隐式const。
字面值常量類:資料成員都是字面值類型的聚合類。如果一個類不是聚合類,但它符合下述要求,則它也是一個字面值常量類:
- 資料成員都必須是字面值類型。
- 類必須至少含有一個constexpr構造函數。
- 如果一個資料成員含有類内初始值,這内置類型成員的初始值必須是一條常量表達式;如果成員屬于某種類類型,這初始值必須使用成員自己的constexpr構造函數。
- 類必須使用析構函數的預設定義,該成員負責銷毀類的對象。
盡管構造函數不能是const的,但是字面值常量類的構造函數可以是constexpr函數。一個字面值常量類必須至少提供一個constexpr構造函數。
參考
- C++ Primer
碼字不易,覺得不錯的小夥伴可以一鍵三連支援一下~