天天看點

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

1、nullptr

  • (1) nullptr是一個關鍵字,而nullptr_t是一個類型
    • typedef decltype(nullptr) nullptr_t
    • 使用nullptr_t類型必須包含#include <cstddef>,而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虛函數式運作時動态綁定,與編譯時常量相悖。

繼續閱讀