c++11新特性介绍
更多關于STL文章——STL學習筆記
文章目錄
- c++11 介紹
- 核心語言的運行期表現強化
- 右值引用和move語義
- 關鍵字 constexpr 泛化的常量表示式
- 核心語言使用性的加強
- template 表達式內的空格
- nullptr 和 std::nullptr_t
- 一致初始化與初值列(Initializer List)
- 核心語言能力的提升
- Range-Based for循環
- String Literal 字符串字面常量
- 關鍵字 noexcept
- 關鍵字 decltype
- 關鍵字 auto
- 嶄新的 template
- Lambda 表達式
- 新的函數聲明語法
- 強類型枚舉
- iota 函數
- 參考資料
c++11 介紹
(維基百科)C++11,先前被稱作C++0x,即ISO/IEC 14882:2011,是C++編程語言的一個標準。它取代第二版標準ISO/IEC 14882:2003(第一版ISO/IEC 14882:1998公開于1998年,第二版于2003年更新,分別通稱C++98以及C++03,兩者差異很小),且已被C++14取代。相比于C++03,C++11標準包含核心語言的新機能,而且擴展C++標準程序庫,并入了大部分的C++ Technical Report 1程序庫(數學的特殊函數除外)。 ISO/IEC JTC1/SC22/WG21 C++標準委員會計劃在2010年8月之前完成對最終委員會草案的投票,以及于2011年3月召開的標準會議完成國際標準的最終草案。然而,WG21預期ISO將要花費六個月到一年的時間才能正式發布新的C++標準。為了能夠如期完成,委員會決定致力于直至2006年為止的提案,忽略新的提案[1]。最終于2011年8月12日公布,并于2011年9月出版。
核心語言的運行期表現強化
右值引用和move語義
(維基百科)在C++03及之前的標準,臨時對象(稱為右值"R-values",因為它們通常位于賦值運算符右側)無法被改變,在C中亦同(且被視為等同于const T&)。盡管如此,在某些情況下臨時對象仍然可能會被改變,但這種表現也被視為是一個有用的漏洞。
C++11增加一個新的非常量引用(reference)類型,稱作右值引用(R-value reference),標記為T &&。右值引用所綁定的臨時對象可以在該臨時對象被初始化之后做修改,這是為了允許move語義。
C++03低性能問題之一,就是在以傳值方式傳遞對象時隱式發生的耗時且不必要的深度拷貝。舉例而言,std::vector本質上是一個C-style數組及其大小的封裝,如果一個std::vector的臨時對象是在函數內部或者函數回返時創建,要將其存儲就只能透過生成新的std::vector并且把該臨時對象所有的數據復制過去(為了討論上的方便,這里忽略回返值優化)。然后該臨時對象會被析構,其使用的內存會被釋放。
在C++11,把一個vector的右值引用作為參數std::vector的"move構造函數",可以把右值參數所綁定的vector內部的指向C-style數組的指針復制給新的vector,然后把該指針置null。由于臨時變量不會被再次使用,所以不會有代碼去訪問該null指針;又因為該指針為null,當該臨時對象超出作用域時曾經指向的內部C-style數組所使用的內存不會被釋放。因此,該操作不僅無形中免去了深拷貝的開銷,而且還很安全。
右值引用作為數據類型的引入,使得函數可以重載區分它的參數是值類型、傳統的左值引用還是右值引用。這讓除了標準庫的現有代碼無須任何改動就能等到性能提升。一個回返std::vector的函數的回返類型無須為了調用move構造函數而顯式修改為std::vector&&,因為臨時對象自動作為右值。(但是,如果std::vector是沒有move構造函數的C++03版,由于傳統的左值引用也可以綁定到臨時對象上,因此具有const std::vector&參數的復制構造函數會被調用,導致一次顯著的內存分配。)
出于安全的考慮,推行了一些限制。具名的變量被認定為左值,即使它是被聲明為右值引用數據類型;為了獲得右值必須使用顯式類型轉換,如模板函數std::move()。右值引用所綁定的對象應該只在特定情境下被修改,主要用于move構造函數中。
類型 && 引用名 = 右值表達式;
例如:
#include <iostream> using namespace std;class A { public:A(int n){cout<< n*n <<endl;} };int main() {auto a = A();//錯誤auto & b = A(10); //錯誤auto && c = A(10); //正確 } //輸出100又如:
bool is_r_value(int &&) { return true; } bool is_r_value(const int &) { return false; }int main() {int i{3};cout<< is_r_value(i) <<endl; // i為具體變量,即使被宣告成右值引用類型,i作為實參表達式也不會被認定是右值表達式。//輸出0cout<< is_r_value(std::move<int&>(i)) <<endl;//輸出1 }由于右值引用的語義特性以及對于左值引用(L-value references;regular references)的某些語義修正,右值引用讓開發者能夠提供函數參數的完美轉發(perfect function forwarding)。當與不定長參數模板結合,這項能力允許函數模板能夠完美地轉送參數給其他接受這些特定參數的函數。最大的用處是轉送構造函數參數,創造出能夠自動為這些特定參數調用正確構造函數的工廠函數(factory function)。
引入右值引用的主要目的是提高程序運行的效率。有些對象在復制時需要進行深復制,深復制往往非常耗時。合理使用右值引用可以避免沒有必要的深復制操作。
關鍵字 constexpr 泛化的常量表示式
constexpr 可以讓表達式核定于編譯期,如:
constexpr int sqrt(int && n){ return n*n; } int main() {int a[sqrt(9)] = {1,2,3,4}; //正確 a有81個元素//如果去掉 constexpr 編譯器將報 a大小不明確的錯誤 }這個關鍵字修正了一個在 c++98 使用數值極限時出現的問題。在c++11之前,如下式子
std::numeric_limits::max();
無法被用作一個整數常量,雖然它在功能上等同于宏 INT_MAX。如今,在c++11中這樣一個式子被聲明為 constexpr,于是我們可以這樣應用:
array<int,std::numeric_limits<int>::max()> arr;用constexpr修飾函數將限制函數的行為。首先,該函數的回返值類型不能為void。第二,函數的內容必須依照"return expr"的形式。第三,在參數替換后,expr必須是個常量表示式。這些常量表示式只能夠調用其他被定義為constexpr的函數,或是其他常量表示式的數據參數。最后,有著這樣修飾符的函數直到在該編譯單元內被定義之前是不能夠被調用的。
聲明為constexpr的函數也可以像其他函數一樣用于常量表達式以外的地方,此時不需要滿足后兩點。
核心語言使用性的加強
template 表達式內的空格
c++11中不再要求兩個 template 表達式的閉符之間放一個空格
vector<map<string,int> > phone; //所有標準均正確vector<map<string,int>> phone;//從c++11標準開始正確nullptr 和 std::nullptr_t
c++11 允許使用 nullptr 取代 0 或 NULL,用來表示一個 pointer (指針)指向所謂的 no value(此不同于擁有一個不確定的值)。這個新特性特別能幫助你在“null pointer 被解釋為一個整數值”時避免誤解。
#include <iostream> using namespace std;void fun(int){cout<<"Type int is called!"<<endl;} void fun(void*){cout<<"Type void* is called!"<<endl;} int main() {fun(0);fun(NULL);//Qt Creator 提示 call to 'fun' is ambiguous//匹配異常,NULL無法區分兩個重載的fun函數 ,有歧義。fun(nullptr); }程序運行結果:
Type int is called!
Type void* is called!
nullptr 是個新關鍵字。它被自動轉換為各種pointer 類型,但不會被轉換為任何整數類型。它擁有類型 std::nullptr_t ,定義于<cstddef>,所以你現在甚至可以重載函數令它們接受null pointer。注意,std::nullptr_t 被視為一個基礎類型。
typedef decltype(nullptr) nullptr_t;
一致初始化與初值列(Initializer List)
在c++11之前,初始化可因為小括號、大括號或賦值操作符(assignment operator)的出現而發生。如何初始化一個變量或對象,很容易混淆。
為此c++11引入了“一致初始化”(Uniform Initialization)概念沒,意思是面對任何初始化動作,你可以使用相同語法,也就是使用大括號。
以下皆成立:
然而請注意,窄化(narrowing)——也就是精度降低或造成數值變動——對大括號而言是不可成立的。例如:
int x1(5.3); //正確,5.3被窄化為5int x2 = 5.3; //正確,5.3被窄化為5int x3{5.3};//錯誤 double不能窄化為intint x4 = {5.3};//錯誤 double不能窄化為intchar c1{7};//正確 7為int,沒有窄化char c2{666666}; //錯誤,666666超過char最大值,窄化std::vector<int> v1 {1,2,3,4};//正確std::vector<int> v2 {1,2,3.3,4.6};//錯誤,double不能窄化為intBjarne Stroustrup 在 [Stroustrup:FAQ] 對此例的說明:“判定是否窄化轉換時,c++11 用以避免許多不兼容性的做法是,依賴初值設定(initializer)的實際值(如上例的7)而非只是依賴類型。如果一個值可被標的類型精確表述,其間的轉換就不算轉化。浮點數轉換至整數,永遠是一種窄化——即使是7.0轉為7。”
為了支持“用戶自定義類型之初值列”概念,c++11 提供了class template std::initializer_list<>,用來支持以一系列值進行初始化,或在“你想要處理一系列值”的任何地點進行初始化。例如:
#include <iostream> using namespace std;void print(std::initializer_list<int> vals) {for (auto p=vals.begin();p!=vals.end();++p) {cout<<*p<<" ";}cout<<endl; }int main() {print({1,2,3,4,5,6,7,8,9,10});//輸出1,2,3,4,5,6,7,8,9,10 }當指明實參個數 和 指明一個初值列的構造函數同時存在,帶有初值列的那個版本勝出:
#include <iostream> using namespace std;class P { public:P(int,int);P(std::initializer_list<int>); };int main() {P a(1,2); //調用P(int,int)P b{1,2}; //調用P(initializer_list)P c{1,2,3};//調用P(initializer_list)P d = {1,2};//調用P(initializer_list) }如果上述“帶有一個初值列”的構造函數不存在,那么接受兩個 int 的那個構造函數會被調用以初始化 b和d ,而 c 的初始化將無效。
由于初值列的關系,explicit 之于“接受一個以上實參”的構造函數也變得關系重大。如今你可以令“多數值自動類型轉換”不再起作用,即使初始化以 = 語法進行。
同樣地,explicit 構造函數如果接受的是個初值列,會失去“初值列帶有0個、1個初值”的隱式轉換能力。
核心語言能力的提升
Range-Based for循環
c++11 引入了一種嶄新的 for 循環形式,可以逐一迭代某個給定的區間、數組、集合(range、array、collection)內的每一個元素。其他編程語言可能稱此為 foreach 循環。其一般性語法如下:
for (decl : coll) {
statement
}
其中 decl 是給定之 coll 集合中的每個元素的聲明,針對這些元素,給定的 statement 會被執行。例如下面針對傳入的初值列中的每個元素,調用給定的語句,于是在標準輸出裝置 cout 輸出元素值:
for(int i:{1,2,3,4,5,6})std::cout<< i << std::endl;如果要將 vector vec的每個元素 elem乘以 3,可以這么做:
std::vector<int> vec{1,2,3,4,5};for(auto& elem:vec)elem *= 3;這里“聲明 elem 為一個 reference”很重要,若不這么做,for循環中的語句會作用在元素的一份 local copy 身上(當然或許有時候你想要這樣)
這意味著,為了避免調用每個元素的 copy 構造函數和析構函數,你通常應該聲明當前元素為一個 const reference。于是一個用來“打印某集合內所有元素”的泛型函數應該寫成這樣:
String Literal 字符串字面常量
Raw string 允許我們定義字符序列,Raw string以 R"( 開頭,以 **)"**結尾,可以包含 line break。這可以避免使用轉義字符。
例如表示字符串“\n”。尋常字面常量可定義為"\\n" 。也可以定義它為 raw string literal R"(\n)" 。
要在raw string 內寫出) " 可以使用定義符 (delimiter)。因此,一個 raw string 的完整語法是 R" delim (...) delim" ,其中 delim 是個字符序列 ,最多16個基本字符,不可含反斜線、空格和小括號。
如:
定義正則表達式的時候非常有用。
- 編碼的 String Literal
只要使用編碼前綴,就可以為string literal 定義一個特殊的字符編碼。
Raw string 開頭的那個R的前面還可以放置一個編碼前綴。
關鍵字 noexcept
noexcept 該關鍵字告訴編譯器,函數不會發生異常,這有利于編譯器對程序做更多的優化。如果在運行時,noexcept函數向外拋出了異常(如果函數內部捕獲了異常并完成處理,這種情況不算拋出異常),程序會直接終止,調用 std::terminate() 函數,該函數內部會調用 std::abort() 終止程序。
c++的異常處理是在運行時而不是編譯時檢測的。為了實現運行時檢測,編譯器創建額外的代碼,然而這會妨礙程序優化。
在實踐中,一般兩種異常拋出是常用的:
后面這一種方式在以往的c++版本中常用 throw() 表示,在c++11 已經被 noexcept 代替。
int sqrt(int && x) throw()//C++11之前 { return x*x;}int sqrt(int && x) noexcept //自C++11起 { return x*x;}- 有條件的 noexcept
在上述示例中 noexcept 其實是 noexcept(true),表示其所限定的 sqrt函數絕對不發生異常。然而,使用方式可以更加靈活,表明在一定條件下不發生異常。
int sqrt(int && n) noexcept(noexcept(n*n)) { return n*n; }它表示如果 n*n 不發生異常,那么函數sqrt(int && n)一定不發生異常。
- 什么時候該使用 noexcept
使用noexcept表明函數或操作不會發生異常,會給編譯器更大的優化空間。然而并不是加上它就能提高效率。
以下情形鼓勵使用:
沒有把握的情況下,不要輕易使用 noexcept 。
關鍵字 decltype
decltype 可讓編譯器找出表達式類型。
std::map<std::string,float> coll; decltype (coll)::value_type elem;decltype 的應用之一是聲明返回類型,另一個用途是在metaprogramming 或用來傳遞一個 lambda 類型。
關鍵字 auto
auto 用來自動推導變量類型。
auto i = 10; //intauto j = 10.0; //doublestd::vector<int> coll{1,2,3};auto it = coll.begin(); //vector<int>::iteratorauto f = [=](){cout<<"hello"<<endl;};f();嶄新的 template
- Variadic Template
自c++11起,template 可擁有那種“得以接受個數不定之template實參”的參數。此能力稱為variadic template。
如,一個可以打印元素的不定參print:
void print(){} template<typename T,typename ...Types> void print(const T& firstArg,const Types&... args) {for(auto i:firstArg) std::cout<<i<<" ";std::cout<<std::endl;print(args...); }如果傳入 1或多個參數,上述的 function template就會被調用,它會把第一實參區分開來,允許第一實參被打印,然后遞歸調用 print()并傳入其余實參。你必須提供一個non-template 重載函數print(),才能結束整個遞歸動作。
- Alias Template
自 c++11 起,支持 template (partial) type definition。然而由于關鍵字 typename 用于此處總是出于各種原因失敗,所以引入關鍵字using,并因此引入一個新術語 alias template。
template<typename T> using Vec = std::vector<T>; Vec<int> coll;等價于
std::vector<int> coll;Lambda 表達式
lambda表達式可以當作inline函數使用,常用于for_each等以函數為參數的算法中。
如:
輸出1000以內的平方數
輸出結果為:
1 4 9 16 25 36 49 64 81 100 121 144 169 196 225 256 289 324 361 400 441 484 529 576 625 676 729 784 841 900 961
[](int x, int y) { return x + y; }這個不具名函數的回返類型是decltype(x+y)。只有在lambda函數符合"return expression"的形式下,它的回返類型才能被忽略。在前述的情況下,lambda函數僅能為一個述句。
在一個更為復雜的例子中,回返類型可以被明確的指定如下:
[](int x, int y) -> int { int z = x + y; return z + x; }定義在與lambda函數相同作用域的參數引用也可以被使用。這種的參數集合一般被稱作closure(閉包)。
[] // 沒有定義任何變量。使用未定義變量會引發錯誤。
[x, &y] // x以傳值方式傳入(默認),y以引用方式傳入。
[&] // 任何被使用到的外部變量都隱式地以引用方式加以引用。
[=] // 任何被使用到的外部變量都隱式地以傳值方式加以引用。
[&, x] // x顯式地以傳值方式加以引用。其余變量以引用方式加以引用。
[=, &z] // z顯式地以引用方式加以引用。其余變量以傳值方式加以引用。
新的函數聲明語法
(維基百科)標準C函數聲明語法對于C語言已經足夠。演化自C的C++除了C的基礎語法外,又擴展額外的語法。然而,當C++變得更為復雜時,它暴露出許多語法上的限制,特別是針對函數模板的聲明。下面的示例,不是合法的C++03:
template< typename LHS, typename RHS> Ret AddingFunc(const LHS &lhs, const RHS &rhs) {return lhs + rhs;} //Ret的型別必須是(lhs+rhs)的型別Ret的類型由LHS與RHS相加之后的結果的類型來決定。即使使用C++11新加入的decltype來聲明AddingFunc的回返類型,依然不可行。
template< typename LHS, typename RHS> decltype(lhs+rhs) AddingFunc(const LHS &lhs, const RHS &rhs) {return lhs + rhs;} //不合法的C++11不合法的原因在于lhs及rhs在定義前就出現了。直到剖析器解析到函數原型的后半部,lhs與rhs才是有意義的。
針對此問題,C++11引進一種新的函數定義與聲明的語法:
template< typename LHS, typename RHS> auto AddingFunc(const LHS &lhs, const RHS &rhs) -> decltype(lhs+rhs) {return lhs + rhs;}這種語法也能套用到一般的函數定義與聲明:
struct SomeStruct {auto FuncName(int x, int y) -> int; };auto SomeStruct::FuncName(int x, int y) -> int {return x + y; }關鍵字auto的使用與其在自動類型推導代表不同的意義。
強類型枚舉
c++11 允許我們定義強類型枚舉,例如:
enum class direction : int {center,right,left,up,down };只需要在 enum 后指明關鍵字 class。強類型枚舉有如下優點:
- 不會隱式轉換至 int
- 如果數值(例如 left )不在enum被聲明的作用域內,必須寫為 direction::left
- 你可以明顯定義底層類型(默認為int)并因此獲得一個保證大小。
- 提前聲明 enumeration type 是可能的,那會消除“為了新的 enumeration type 而重新編譯”的必要——如果只有類型被使用的話
iota 函數
iota 函數可將給定區間的值設定為從某值開始的連續值,例如將連續十個整數設定為從 1 開始的連續整數(即 1、2、3、4、5、6、7、8、9、10)。
#include <iostream> #include <numeric> #include <array> using namespace std;int main() {array<int,1000> arr{};iota(arr.begin(),arr.end(),1);for(auto i:arr)cout<<i<<" ";cout<<endl; }輸出結果為:1到1000
參考資料
- wikipedia
- The C++ Standard Library (Nicolai M. Josuttis 著)(侯捷譯)
總結
以上是生活随笔為你收集整理的c++11新特性介绍的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 栈应用之简单迷宫问题(C语言版)
- 下一篇: Python 内建函数大全