C++11 nullptr與常量表達式constexpr記錄

1、nullptr

  • (1) nullptr是一個關鍵字,而nullptr_t是一個類型
    • typedef decltype(nullptr) nullptr_t
    • 使用nullptr_t類型必須包含#include ,而nullptr不需要包含任何頭文件。
    • nullptr_t類型可以隱式轉換爲任意一種指針類型;如:std::nullptr_t test;char* s = test;
    • Nullptr_t類型不適用於算數表達式,但是使用與關係表達式。
  • (2) nullptr是有類型的,僅可以被隱式轉換爲指針類型
    • 和NULL的不同,NULL的定義有二義性,有時其定義爲0,有時定義爲void*(0)
  • (3) 模板只能將nullptr_t作爲一個普通類型來推導,而不會將其視爲T*指針,因此要讓編譯器成功的推導出nullptr_t的類型,必須做顯式的類型轉換。
  • (4) nullptr類型所佔用的內存空間大小和void*是相同的。即:(sizeof(nullptr) == sizeof(void*))
  • (5) nullptr與void*的區別
    • nullptr是編譯時期的常量,是編譯時期的一個關鍵字,可以被編譯器識別。void*(0)是一個強制轉換表達式,返回值也是void*類型
    • nullptr到任何指針的轉換都是隱式的,而void*要轉換爲其他類型必須強制轉換。【注意C語言void*可以隱式轉換爲任意指針,而C++不行】

2、constexpr

2.1 constexpr常量表達式函數

2.1.1 常量表達式要解決的問題

​ 在編碼的過程中,我們會遇到下面的情況:

const int test1() {
    return 1;
}

int main() {
    int a[test1()] = {0}; // 編譯錯誤,test1()返回值是運行時常量,所以在編譯期間不能確定a的大小,這個時候需要使用編譯時常量
}

​ 上面的代碼是編譯不過的,原因很簡單,就是定義數組a的時候,a的大小由test1()返回值決定,由於test1()返回的值是一個運行時常量,而不是編譯時常量,所以在編譯時期,編譯器不知道要給數組a分配多大的空間,所以就報錯了。要解決這個問題,C++11引入了常量表達式函數,即在函數的返回值類型之前添加constexpr關鍵字,這個時候改函數的返回值即爲一個編譯時常量,可以放心使用了。

constexpr int test1() {
  return 1;
}

int mani() {
  int a[test1()] = {0};
}

2.1.2常量表達式限制

常量表達式在使用的時候需要注意下面四個問題:

  • (1) 函數體必須只有一個單一的return返回語句
    • 如:constexpr int test() {int i = 1; return i;}這樣常量表達式定義是不允許的,會導致編譯錯誤。
  • (2) 函數必須返回值
    • 顯而易見,常量表達式函數必須返回值,即不允許有constexpr void fun() {}這種常量表達式函數
  • (3) 常量表達式函數使用前必須定義
  • (4) return返回語句表達式中不能使用非常量表達式函數、全局數據並且必須是一個常量表達式。

下面對這幾個進行逐個講解。

(1)函數體必須只有一個單一的return返回語句

// 常量表達式函數中只允許有一個return語句,不能有其他語句,否則會產生編譯錯誤,(不產生代碼的語句除外)
constexpr int func() {
  int i = 1;
  return i;
}

(2)函數必須返回值

// 禁止出現這種沒有返回值的常量表達式,也沒有什麼意義,編譯錯誤
constexpr viod fun() {
}

(3)常量表達式函數使用前必須定義

constexpr int fun();

int main() {
  constexpr int a = fun();	// 編譯錯誤,在定義前使用,使用前必須先定義
  int b = fun();						// 編譯通過,因爲此時轉換爲普通的函數調用
}

constexpr int fun() {
  return 1;
}

(4) return返回語句表達式中不能使用非常量表達式函數、全局數據並且必須是一個常量表達式。

return語句中不能使用非常量表達式,因爲如果使用非常量表達式,可能會導致常量表達式的結果在編譯時期不能確定,同樣的,也不能使用全局變量,因爲全局變量在運行的過程中可能會被修改而導致編譯時期不確定。

int a = 0;
// 編譯錯誤,不允許使用全局變量
constexpr int func() {
  return a;
}

2.2 常量表達式值

2.2.1 const與constexpr區別

​ 常量表達式值很容易理解,即爲在變量前添加constexpr修飾符,與const類似。如:

const int a = 0;
constexpr b = 1;

那上面a和b是否有區別呢?

  • 對於a來說,編譯器總是爲a生成數據,即編譯器總是爲變量a分配一塊內存
  • 對於b來說,只有顯式的使用b的地址的情況下,編譯器會爲b分配內存;否則編譯器不爲變量b分配內存。

2.2.2 浮點型表達式常量

​ 由於編譯時環境和運行時環境可能有差別,在使用浮點型常量表達式的時候,可能導致編譯時期的常量值和運行時的常量值不一致而導致程序出現爲定義行爲,所以C++11標準要求,編譯時的浮點常量的精度要高於或等於運行時的精度,只有這樣才能保證爲定義行爲的出現。

2.2.3 自定義類型的常量表達式

​ 默認情況下constexpr修飾符不能修飾自定義類型,若在使用的時候需要修飾自定義類型,需要爲爲自定義類型定義常量表達式構造函數。常量表達式構造函數有下面兩個限制:

  • 構造函數的函數體必須爲空
  • 初始化列表中只能由常量表達式來賦值
class Test {
public:
    constexpr Test(int a):_a(a) {}
    constexpr int getA() {return _a;}
private:
    int _a;
};

int main() {
    constexpr Test t(1);
    std::cout << t.getA() << std::endl;

}

注意:程序函數的虛函數成員不允許聲明爲constexpr,因爲virtual虛函數式運行時動態綁定,與編譯時常量相悖。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章