nullptr
??nullptr出現的目的是為了替代NULL。在某種意義上來說,傳統會把NULL,0視為同一種東 西,這取決于編譯器如何定義NULL,有些編譯器會將定義為((void*)0),有些則會直接將其定義 為0。?
??????? C++不允許直接將void*隱式轉換到其他類型。但如果編譯器嘗試把NULL定義為((void*)0), 那么在下面這句代碼中:?
char *ch = NULL;
??????? 沒有了void*隱式轉換的C++只好將定義為0。而這依然會產生新的問題,將NULL定義成0,將導致C++中重載特性發生混亂。考慮下面這兩個函數:?
void foo(char*);
void foo(int);
????????那么foo(NULL)這個語句將會去調用foo(int),從而導致代碼違反直覺。?
??????? 為了解決這個問題,C++引入了關鍵字nullptr,專門用來區分空指針、0。而nullptr的類型 為nullptr_t,能夠隱式的轉換為任何指針或成員指針的類型,也能和他們進行相等或者不等的比較。 你可以嘗試使用clang++編譯下面的代碼:
#include <iostream>#include <type_traits>//*******1. nullptr******/
// C++17 引入了 std::nullptr_t 類型,它是一個空指針類型。
// 在 C++ 中,NULL 是一個宏,通常定義為 0 或 (void*)0。
/*int main(){if(std::is_same<decltype(NULL), decltype(0)>::value){std::cout << "NULL is the same type as 0" << std::endl;}else{std::cout << "NULL is NOT the same type as 0" << std::endl;//NULL 和 0 在 C++ 中是不同的類型。}if(std::is_same<decltype(NULL), decltype((void*)0)>::value){std::cout << "NULL is the same type as (void*)0" << std::endl;}else{std::cout << "NULL is NOT the same type as (void*)0" << std::endl;//NULL 和 (void*)0 在 C++ 中是不同的類型。}if(std::is_same<decltype(NULL), std::nullptr_t>::value){std::cout << "NULL is the same type as std::nullptr_t" << std::endl;}else{std::cout << "NULL is NOT the same type as std::nullptr_t" << std::endl;//NULL 和 std::nullptr_t 在 C++ 中是不同的類型。}return 0;
}
*/
??????? 從輸出中我們可以看出,NULL不同于與nullptr,0。所以,請養成直接使用的習慣。 此外,在上面的代碼中,我們使用了decltype和std::is_same這兩個屬于現代C++的語法,簡 單來說decltype用于類型推導,而std::is_same用于比較兩個類型是否相同,我們會在后面 一節中詳細討論。
constexpr
??????? C++本身已經具備了常量表達式的概念,比如1+2, 3*4這種表達式總是會產生相同的結果并且沒 有任何副作用。如果編譯器能夠在編譯時就把這些表達式直接優化并植入到程序運行時,將能增加程序 的性能。一個非常明顯的例子就是在數組的定義階段:
// C++17 引入了 constexpr 關鍵字,它允許在編譯時計算常量表達式。
#include <iostream>
#define LEN 10/*
int len_foo(){int i = 2;return i;
}constexpr int len_foo_constexpr() {return 5;
}constexpr int fibonacci(const int n) {return n==1 || n == 2 ? 1 : fibonacci(n - 1) + fibonacci(n - 2);
}int main() {char arr_1[10]; //合法char arr_2[LEN]; //非法 int len = 10;//char arr_3[len]; //非法const int len_2 = len + 1;constexpr int len_2_constexpr = 1+2+3;//char arr_4[len_2_constexpr]; //合法char arr_4[len_2]; //非法//char arr_5[len_foo()]; //非法char arr_6[len_foo_constexpr() + 1]; //合法std::cout << "fibonacci(10) = " << fibonacci(10) << std::endl; // 輸出 55//1, 1, 2, 3, 5, 8, 13, 21, 34, 55std::cout << "fibonacci(10) = " << fibonacci(10) << std::endl; // return 0;}
*/
????????上面的例子中char arr_4[len_2]可能比較令人困惑,因為len_2已經被定義為了常量
char arr_4[len_2]為什么仍然是非法的呢?這是因為C++標準中數組的長度必須是一個常量表達式,而對len_2于而言,這是一個const常數,而不是一個常量表達式,因此(即便這種行為在大部分編譯器中都支持,但是)它是一個非法的行為,我們需要使用接下來即將介紹的C++11引入的constexpr特性來解決這個問題;而對于arr_5來說,C++98之前的編譯器無法得知len_foo()在運行期實際上是返回一個常數,這也就導致了非法的產生。
????????注意,現在大部分編譯器其實都帶有自身編譯優化,很多非法行為在編譯器優化的加持下會變得合法,若需重現編譯報錯的現象需要使用老版本的編譯器。
??????? C++11提供了constexpr讓用戶顯式的聲明函數或對象構造函數在編譯期會成為常量表達式,這個關鍵字明確的告訴編譯器應該去驗證len_foo()在編譯期就應該是一個常量表達式。此外,修飾的函數可以使用遞歸:
constexpr int fibonacci(const int n) {return n==1 || n == 2 ? 1 : fibonacci(n - 1) + fibonacci(n - 2);
}