1.C++ 11 關鍵字
1.auto
我現在用auto,基本是在變數定義時根據初始化表示式自動推斷該變數的型別。
另外與關鍵字 decltype 連用,在宣告或定義函式時作為函式返回值的佔位符。
auto不能用來宣告函式的返回值。但如果函式有一個尾隨的返回型別時,auto是可以出現在函式宣告中返回值位置。這種情況下,auto並不是告訴編譯器去推斷返回型別,而是指引編譯器去函式的末端尋找返回值型別。
在下面這個例子中,函式返回值型別是 operator+ 操作符作用在T、U型別變數上的返回值型別。
template<class T, class U> auto add(T t, U u) -> decltype(t + u) { return t + u; }
2.using
C++ 11 中 使用using來代替typedef來命名,更加具有可讀性
using uint8=unsigned char; //等價於typedef unsigned char uint8; using FunctionPtr = void (*)(); //等價於typedef void (*FunctionPtr)(); template<typename T> using MapString = std::map<T, char*>; //定義模板別名,注意typedef無法定義模板別名,因為typedef只能作用於具體型別而非模板
C++ 11之前主要用於名字空間、型別、函式與物件的引入,實際上是去除作用域的限制
//引入名字空間 using namespace std; //引入型別 using std::iostream; //引入函式 using std::to_string; //引入物件 using std::cout; //在子類中解除父類同名函式的隱藏 class Base { public: void func() { cout << "in Base::func()" << endl; } void func(int n) { cout << "in Base::func(int)" << endl; } }; class Sub : public Base { public: using Base::func; //引入父類所有同名函式func,解除函式隱藏 void func() { cout<<"in Sub::func()"<<endl; } }; int main() { Sub s; s.func(); s.func(1); // Success! }
3.decltype
同作為適應C++模板和泛型程式設計需求而出現的型別推導的關鍵字,與auto不同的是,decltype總是以一個普通表示式作為引數,返回該表示式的型別,並且不會對錶達式進行求值。其規則:
const A* a = new A{0}; //第一種情況:
//如果 e 是一個變數或者類成員訪問表示式,假設e的型別是T,那麼的decltype(e)為T,decltype((e))為T& decltype(a->x) y; // type of y is double decltype((a->x)) z = y; // type of z is const double&,因為a一個常量物件指標 //第二種情況
//如果e是一個解引用操作,那麼decltype(e)和decltype((e))均為T& int* aa=new int; decltype(*aa) y=*aa; //type of y is int&,解引用操作 //第三種情況
//decltype(e)與decltype((e))均為T。struct A { double x; }; decltype(5) y; //type of y is int decltype((5)) y; //type of y is int const int&& RvalRef() { return 1; } decltype ((RvalRef())) var = 1; //type of var is const int&&
4.nullptr_t 和 nullptr
之前會用0來表示空指標,但存在0被隱式轉換成整型的可能,可能引發一些問題。
關鍵字nullptr是std::nullptr_t型別的值,可以用來指代空指標常量。
nullptr 和任何指標型別以及類成員指標型別的空值之間可以發生隱式型別轉換,同樣也可以隱式轉換為 bool 型(取值為false),但是不存在上述到整型的隱式轉換。
在使用 nullptr_t 與 nullptr 時,注意以下幾點:
(1)可以使用nullptr_t定義空指標,但所有定義為nullptr_t型別的物件行為上是完全一致的;
(2)nullptr_t 型別物件可以隱式轉換為任意一個指標型別;
(3)nullptr_t 型別物件不能轉換為非指標型別,即使使用reinterpret_cast進行強制型別轉換也不行;
(4)nullptr_t 型別物件不能用於算術運算表示式;
(5)nullptr_t 型別物件可以用於關係運算表示式,但僅能與 nullptr_t 型別或指標型別物件進行比較,當且僅當關係運算符為==、>=、<=時,如果相等則返回 true。
5.const constexpr
const用來申明常量表達式,可作用於函式返回值、函式引數、資料申明以及類的建構函式等。
常量表達式指值不會改變並且在編譯時期就得到計算結果的表示式
const int i=3; //i是一個常變數 const int j=i+1; //j是一個常變數,i+1是一個常量表達式 int k=23; //k的值可以改變,從而不是一個常變數 const int m=f(); //m不是常變數,m的值只有在執行時才會獲取
constexpr
//常量表達式函式 //函式必須有返回值; //函式體只有單一的return語句; //return語句中的表示式也必須是一個常量表達式; //函式在使用前必須已有定義。 constexpr int f(){return 1;} //常量表達式值 constexpr int i=3; //i是一個常變數 constexpr int j=i+1; //i+1是一個常變數 constexpr int k=f(); //只有f()是一個constexpr函式時,k才是一個常量表達式 const int *p=nullptr; //p是一個指向整型常量的指標(pointer to const) constexpr int *p1=nullptr; //p1是一個常量指標(const pointer) //自定義型別的建構函式 //建構函式體必須為空; //初始化列表只能使用常量表達式。 struct MyType { int i; constexpr MyType(int x):i(x){} }; constexpr MyType myType(1); //作用於函式模板 //由於函式模板引數的不確定性,例項化後的模板函式可能不滿足常量表達式的條件,此時,C++11標準規定,自動忽略constexpr。 struct NotConstType { int i; NotConstType(int x) :i(x) {} }; NotConstType myType; //constexpr作用於函式模板 template <typename T> constexpr T ConstExpFunc(T t) { return t; } int main() { NotConstType objTmp = ConstExpFunc(myType); //編譯通過,ConstExpFunc例項化為普通函式,constexpr被忽略 constexpr NotConstType objTmp1 = ConstExpFunc(myType); //編譯失敗 constexpr int a = ConstExpFunc(1); //編譯通過,ConstExpFunc例項化為常量表達式函式 } //constexpr超程式設計 //作用於遞迴函式來實現編譯時期的數值計算 C++11標準規定,常量表達式應至少支援512層遞迴 constexpr int Fibonacci(int n) { return (n == 1) ? 1 : (n == 2 ? 1 : Fibonacci(n - 1) + Fibonacci(n - 2)); } int main() { constexpr int fib8 = Fibonacci(8); //編譯期常量等於21 }
對比
const可以修飾函式引數、函式返回值、函式本身、類等,在不同的使用場景下,const具有不同的意義,不過大多數情況下,const描述的是“執行時常量性”,即在執行時資料具有不可更改性
constexpr可以修飾函式引數、函式返回值、變數、類的建構函式、函式模板等,是一種比const更加嚴格的約束,它修飾的表示式除了具有“執行時常量性”,也具有“編譯時常量性”,即constexpr修飾的表示式的值在編譯期間可知。
6.noexcept
這一個關於異常宣告exception specification的功能,用於指定函式可能丟擲的異常型別。C++11前,在函式宣告中會使用throw;C++11中引入noexcept。
voidFunc0() throw(runtime_error); //可能丟擲runtime_error型別的異常 voidFunc1() throw(); //不會丟擲任何異常 voidFunc2(); //沒有異常說明,可以丟擲任何型別的異常
如果函式丟擲了沒有在異常說明中列出的異常,則編譯器會呼叫標準庫函式unexpected。預設情況下,unexpected函式會呼叫terminate函式終止程式。
- noexcept作為修飾符
void Func() noexcept;
//此時同上述的throw(),表示函式不會丟擲異常,也就是說如果發生了,編譯器會直接呼叫std::terminate()終止程式執行。
//同時就效率討論,noexcept會較throw高一些
void Func() noexcept(常量表達式);
//如果常量表達式的結果為true,表示該函式不會丟擲異常;反之丟擲。 不規定表示式則預設為true;
- noexcept作為操作符
//第二個 noexcept 是一個操作符,如果其引數是一個有可能丟擲異常的表示式, //noexcept(T()) 則返回值為false,那麼 func5 有可能會丟擲異常 //否則,noexcept(T()) 返回true,func5 不會丟擲異常。這樣函式模板是否會丟擲異常,可以由表示式進行推導 template <typename T> void fun() noexcept(noexcept(T())) { throw 1; } class Base { public: virtual void f() {} }; class Test :public Base { public: ~Test() noexcept(true) {} }; class TestFalse :public Base { public: ~TestFalse() noexcept(false) {} }; int main(int argc, char **argv) { std::cout << noexcept(TestFalse()) << std::endl; // false std::cout << noexcept(Test()) << std::endl; // true try { fun<TestFalse>(); } catch (...) { std::cout << "throw" << std::endl; // throw } try { fun<Test>(); // terminate } catch (...) { std::cout << "throw" << std::endl; } return 0; }
7. final和override
final修飾類或虛擬函式,表明終結,不能再子類化或者在子類中不能被重寫。
override用於輔助檢查是否真正重寫了繼承的虛擬函式。
注意:override 和 virtual 一樣,只能在類體內申明或重寫虛擬函式時使用,在類體外重寫虛擬函式時使用 override 和 virtual 將無法通過編譯。
8.sizeof
sizeof… 運算子的作用是獲取 C++11 中可變引數模板中引數包中元素個數。類似 sizeof,sizeof… 返回一個常量表達式,而且不會對模板的實參求值
template<typename... Args> void g(Args... args) { cout<<sizeof...(Args)<<endl; //型別引數的數目 cout<<sizeof...(args)<<endl; //函式引數的數目 }
9.default delete
C++98和C++03編譯器在類中會隱式地產生四個函式:預設建構函式、拷貝建構函式、解構函式和賦值運算子函式,它們被稱為特殊成員函式。在 C++11 中,被稱為 “特殊成員函式” 的還有兩個:移動建構函式和移動賦值運算子函式。如果使用者申明瞭上面六種函式,編譯器則不會隱式產生。C++引入的default關鍵字,可顯示地、強制地要求編譯器為我們生成預設版本。
class DataOnly { public: DataOnly()=default; //default constructor ~DataOnly()=default; //destructor DataOnly(const DataOnly& rhs)=default; //copy constructor DataOnly& operator=(const DataOnly & rhs)=default; //copy assignment operator DataOnly(const DataOnly && rhs)=default; //C++11,move constructor DataOnly& operator=(DataOnly && rhs)=default; //C++11,move assignment operator };
delete在C++11前用作物件釋放運算子,在C++11中獲得了新的功能:
//禁止生成預設版本 class DataOnly { public: DataOnly()=delete; //default constructor ~DataOnly()=delete; //destructor DataOnly(const DataOnly& rhs)=delete; //copy constructor DataOnly& operator=(const DataOnly & rhs)=delete; //copy assignment operator DataOnly(const DataOnly && rhs)=delete; //C++11,move constructor DataOnly& operator=(DataOnly && rhs)=delete; //C++11,move assignment operator }; //過濾函式的形參型別 bool isLucky(int number); // original function bool isLucky(char) = delete; // reject chars bool isLucky(bool) = delete; // reject bools bool isLucky(double) = delete; // reject doubles and floats //在模板特例化中,也可以用 delete 來過濾一些特定的形參型別 class Widget { public: template<typename T> void processPointer(T* ptr){} }; template<> void Widget::processPointer<void>(void*)=delete; //deleted function template
10.static_assert
static_asset靜態斷言,用於檢測和診斷編譯器錯誤。
assert執行時斷言巨集
static_assert(斷言表示式,提示字串);
static_assert和type traits一起使用能發揮更大的威力。type traits是一些類模板,在編譯時提供關於型別的資訊,在標頭檔案<type_traits>中可以找到它們。這個標頭檔案中有好幾種類模板,有helper class,用來產生編譯時常量,有type traits class,用來在編譯時獲取型別資訊,還有就是type transformation class,他們可以將已存在的型別變換為新的型別。下面這段程式碼原本期望只作用於整數型別
template <typename T1, typename T2> auto add(T1 t1, T2 t2) { return t1 + t2; }
使用static_assert,應當注意:
(1)static_assert可以用在全域性作用域,名稱空間,類作用域,函式作用域,幾乎可以不受限制地使用;
(2)static_assert可以在幫助我們在編譯期間發現更多的錯誤,用編譯器來強制保證一些契約,改善編譯資訊的可讀性,尤其是用於模板的時候;
(3)編譯器在遇到一個static_assert語句時,通常立刻將其第一個引數作為常量表達式進行演算。如果第一個常量表達式依賴於某些模板引數,則延遲到模板例項化時再進行演算,這就讓檢查模板引數成為了可能;
(4)由於是static_assert編譯期間斷言,不生成目的碼,因此static_assert不會造成任何執行期效能損失