编辑“︁
C++11
”︁(章节)
跳转到导航
跳转到搜索
Template:Editnotice load/content
警告:
您没有登录。如果您进行任何编辑,您的IP地址会公开展示。如果您
登录
或
创建账号
,您的编辑会以您的用户名署名,此外还有其他益处。
反垃圾检查。
不要
加入这个!
== 核心語言使用性的加強 == 這些特色存在的主要目的是為了使C++能夠更容易使用。舉凡可以增進型別安全,減少程式碼重複,不易誤用程式碼之類的。 === 初始化列表 === 標準C++從C帶來了[[b:C++/Initializer list|初始化列表]](initializer list)的概念。這個構想是結構或是数组能夠依據成員在該結構內定義的順序透過給予的一串引數來產生。這些[[b:C++/Initializer list|初始化列表]]是遞迴的,所以結構的数组或是包含其他結構的結構可以使用它們。這對靜態列表或是僅是把結構初始化為某值而言相當有用。C++有构造函数,能夠重複对象的初始化。但單單只有那樣並不足以取代這項特色的所有機能。在C++03中,只允許在嚴格遵守POD的定義和限制條件的結構及類別上使用這項機能,非POD的型別不能使用,就連相當有用的STL容器std::vector也不行。 C++11將會把初始化列表的概念綁到型別上,稱作<tt>std::initializer_list</tt>。這允許构造函数或其他函数像參數般地使用[[b:C++/Initializer list|初始化列表]]。舉例來說: <syntaxhighlight lang="cpp"> class SequenceClass { public: SequenceClass(std::initializer_list<int> list); }; </syntaxhighlight> 這將允許<tt>SequenceClass</tt>由一連串的整數构造,就像: <syntaxhighlight lang="cpp"> SequenceClass someVar = {1, 4, 5, 6}; </syntaxhighlight> 這個构造函数是種特殊的构造函数,稱作[[b:C++/Initializer list|初始化列表构造函数]]。有著這種构造函数的類別在統一初始化的時候會被特別對待。 類別<tt>std::initializer_list<></tt>是個第一級的C++11標準程式庫型別。然而他們只能夠經由C++11編譯器透過{}語法的使用被靜態地构造。這個列表一經构造便可複製,雖然這只是copy-by-reference。初始化列表是常數;一旦被建立,其成員均不能被改變,成員中的資料也不能夠被變動。 因為初始化列表是真實型別,除了類別构造式之外還能夠被用在其他地方。正規的函数能夠使用初始化列表作為形参。例如: <syntaxhighlight lang="cpp"> void FunctionName(std::initializer_list<float> list); FunctionName({1.0f, -3.45f, -0.4f}); </syntaxhighlight> 標準容器也能夠以這種方式初始化: <syntaxhighlight lang="cpp"> vector<string> v = { "xyzzy", "plugh", "abracadabra" }; vector<string> v({ "xyzzy", "plugh", "abracadabra" }); vector<string> v{ "xyzzy", "plugh", "abracadabra" };//见下节“统一的初始化” </syntaxhighlight> === 統一的初始化 === 標準C++在初始化型別方面有著許多問題。初始化型別有數種方法,而且交換使用時不會都產生相同結果。傳統的建構式語法,看起來像是函式宣告,而且為了能使編譯器不會弄錯必須採取一些步驟。只有集合體和POD型別能夠被集合式的初始化(使用<code>SomeType var = {/*stuff*/};</code>)。 C++11將會提供一種統一的語法初始化任意的物件,它擴充了初始化串列語法: <syntaxhighlight lang="cpp"> struct BasicStruct { int x; float y; }; struct AltStruct { AltStruct(int _x, float _y) : x(_x), y(_y) {} private: int x; float y; }; BasicStruct var1{5, 3.2f}; AltStruct var2{2, 4.3f}; </syntaxhighlight> <tt>var1</tt>的初始化的運作就如同C-style的初始化串列。每個公開的變數將被對應於初始化串列的值給初始化。隱式型別轉換會在需要的時候被使用,這裡的隱式型別轉換不會產生範圍縮限(narrowing)。要是不能夠轉換,編譯便會失敗。(範圍縮限 (narrowing):轉換後的型別無法表示原型別。如將32-bit的整數轉換為16-bit或8-bit整數,或是浮點數轉換為整數。)<tt>var2</tt>的初始化則是簡單地呼叫建構式。 統一的初始化建構能夠免除具體指定特定型別的必要: <syntaxhighlight lang="cpp"> struct IdString { std::string name; int identifier; }; IdString var3{"SomeName", 4}; </syntaxhighlight> 該語法將會使用<tt>const char *</tt>參數初始化<tt>std::string</tt>。你也可以做像下面的事: <syntaxhighlight lang="cpp"> IdString GetString() { return {"SomeName", 4}; // 注意這裡不需要明確的型別 } </syntaxhighlight> 統一初始化不會取代建構式語法。仍然會有需要用到建構式語法的時候。如果一個類別擁有[[b:C++/Initializer list|初始化列表构造函数]](<tt>TypeName(initializer_list<SomeType>);</tt>),而初始化串列与构造函数的参数类型一致,那麼它比其他形式的建構式的優先權都來的高。C++11版本的<tt>std::vector</tt>將會有初始化串列建構式。這表示: <syntaxhighlight lang="cpp"> std::vector<int> theVec{4}; </syntaxhighlight> 這將會呼叫初始化串列建構式,而不是呼叫<tt>std::vector</tt>只接受一個尺寸參數產生相應尺寸vector的建構式。要使用這個建構式,使用者必須直接使用標準的建構式語法。 === 型別推導 === 在C++03和C,使用變數必須明確的指出其型別。然而,隨著模版型別的出現以及[[模板超編程]]的技巧,某物的型別,特別是函式定義明確的回返型別,就不容易表示。在這樣的情況下,將中間結果儲存於變數是件困難的事,可能會需要知道特定的超編程程式庫的內部情況。 C++11提供兩種方法緩解上述所遇到的困難。首先,有被明確初始化的變數可以使用[[Auto (C++)|<code>auto</code>]]關鍵字。对于指针类型,声明为auto* 或者auto 是一样的。对于引用类型,必须使用auto & 。這會依據該初始化子(initializer)的具體型別產生變數: <syntaxhighlight lang="cpp"> auto integralVariable = 5; auto unsignedVariable = 5UL; auto ptrToObject = new MyPackage::Object(); </syntaxhighlight> 在上面的简易例子中,程序员和编译器都能轻易判断出几个变量的类型。而通过使用<code>UL</code>后缀,<code>unsignedVariable</code>的类型自动成为<code>unsigned long</code>。最后,动态创建对象时已经要在 <code>new</code> 后输入类型,此时使用 <code>auto</code> 就能省略掉累赘的<code>MyPackage::Object*</code>声明。 <syntaxhighlight lang="cpp"> using namespace std::placeholders; auto someStrangeCallableType = std::bind(&SomeFunction, _2, _1, someObject); auto otherVariable = 5; </syntaxhighlight> 上面的例子中说明<code>auto</code>有利于C++支援函数式编程。这里注意<code>std::bind</code>也是C++11从[[Boost C++ Libraries]]中引入的模版函数。<code>std::bind</code>实现了偏函数,在上面的例子中它将函数<code>SomeFunction</code>的第三个参数绑定为<code>someObject</code>,并将第一和第二个参数的在参数列表中的顺序倒转,由此生成一个函数对象<code>someStrangeCallableType</code>。该函数对象的类型非常复杂,但编译器却能轻易地将其推导出来。 除此之外,[[Decltype|<code>decltype</code>]]能夠被用來在編譯期決定一個表示式的型別。舉例: <syntaxhighlight lang="cpp"> int someInt; decltype(someInt) otherIntegerVariable = 5; </syntaxhighlight> <code>decltype</code>和<code>auto</code>一起使用會更為有用,因為auto變數的型別只有編譯器知道。然而<code>decltype</code>對於那些大量運用運算子重載和特化的型別的程式碼的表示也非常有用。 <code>auto</code>對於減少冗贅的程式碼也很有用。舉例而言,程式員不用寫像下面這樣: <syntaxhighlight lang="cpp"> for (vector<int>::const_iterator itr = myvec.cbegin(); itr != myvec.cend(); ++itr) </syntaxhighlight> 而可以用更簡短的 <syntaxhighlight lang="cpp"> for (auto itr = myvec.cbegin(); itr != myvec.cend(); ++itr) </syntaxhighlight> 由于"myvec"实现了begin/end迭代器,C++11提供了基于范围的{{lang|en|for}}循环来大幅度省略代码。 <syntaxhighlight lang="cpp"> for (auto& x : myvec) </syntaxhighlight> 這項差異隨著程式員開始嵌套容器而更為顯著,雖然在這種情況下<code>typedef</code>是一個減少程式碼的好方法。 <code>decltype</code>所表示的型別可以和<code>auto</code>推導出來的不同。 <syntaxhighlight lang="cpp"> #include <vector> int main() { const std::vector<int> v(1); auto a = v[0];// a為int型別 decltype(v[0]) b = 0; // b為const int&型別,即 // std::vector<int>::operator[](size_type)const的回返型別 auto c = 0; // c為int型別 auto d = c; // d為int型別 decltype(c) e; // e為int型別,c實體的型別 decltype((c)) f = e; // f為int&型別,因為(c)是左值 decltype(0) g; // g為int型別,因為0是右值 } </syntaxhighlight> === 基于范围的for迴圈 === <tt>for</tt>语句將允許簡單的範圍迭代: <syntaxhighlight lang="cpp"> int my_array[5] = {1, 2, 3, 4, 5}; // double the value of each element in my_array: for (int &x : my_array) { x *= 2; } // similar but also using type inference for array elements for (auto &x : my_array) { x *= 2; } </syntaxhighlight> 上面<tt>for</tt>述句的第一部份定義被用來做範圍迭代的变量,就像被宣告在一般for迴圈的变量一樣,其作用域僅只於迴圈的範圍。而在":"之後的第二區塊,代表將被迭代的範圍。这种for语句还可以用于C型数组,初始化列表,和任何定义了<code>begin()</code>和<code>end()</code>来返回首尾迭代器的类型。 === Lambda函式與表示式 === 在標準C++,特別是當使用C++標準程式庫演算法函式諸如<tt>sort</tt>和<tt>find</tt>,使用者經常希望能夠在演算法函式呼叫的附近定義一个临时的述部函式(又称谓词函数,predicate function)。由于語言本身允許在函式內部定義類別,可以考虑使用函数对象,然而這通常既麻煩又冗贅,也阻礙了程式碼的流程。此外,標準C++不允許定義於函式內部的類別被用於模板,所以前述的作法是不可行的。 C++11對[[λ演算|lambda]](即[[匿名函数]])的支援可以解決上述問題。 一個lambda函式可以用如下的方式定義: <syntaxhighlight lang="cpp"> [](int x, int y) { return x + y; } </syntaxhighlight> 這個不具名函式的回返型別是<tt>decltype(x+y)</tt>。只有在lambda函式符合"return ''expression''"的形式下,它的回返型別才能被忽略。在前述的情況下,lambda函式僅能為一個述句。 在一個更為複雜的例子中,回返型別可以被明確的指定如下: <syntaxhighlight lang="cpp"> [](int x, int y) -> int { int z = x + y; return z + x; } </syntaxhighlight> 本例中,一個暫時的變數<tt>z</tt>被建立用來儲存中間結果。如同一般的函式,<tt>z</tt>的值不會保留到下一次該不具名函式再次被呼叫時。 如果lambda函式沒有傳回值(例如<tt>void</tt>),其回返型別可被完全忽略。 定義在與lambda函式相同作用域的變數參考也可以被使用。這種的變數集合一般被稱作closure([[闭包_(计算机科学)|閉包]])。 <syntaxhighlight lang="cpp"> [] // 沒有定义任何变量。使用未定义变量会引发错误。 [x, &y] // x以传值方式传入(默认),y以引用方式传入。 [&] // 任何被使用到的外部变量都隐式地以引用方式加以引用。 [=] // 任何被使用到的外部变量都隐式地以传值方式加以引用。 [&, x] // x显式地以传值方式加以引用。其余变量以引用方式加以引用。 [=, &z] // z显式地以引用方式加以引用。其余变量以传值方式加以引用。 </syntaxhighlight> closure被定義與使用如下: <syntaxhighlight lang="cpp"> std::vector<int> someList; int total = 0; std::for_each(someList.begin(), someList.end(), [&total](int x) { total += x; }); std::cout << total; </syntaxhighlight> 上例可計算<tt>someList</tt>元素的總和並將其印出。變數<tt>total</tt>是lambda函式closure的一部分,同時它以引用方式被传递入谓词函数,因此它的值可被lambda函式改變。 若不使用引用的符號''&'',則代表變數以傳值的方式傳入lambda函式。讓使用者可以用這種表示法明確區分變數傳遞的方法:傳值,或是傳參考。由於lambda函式可以不在被宣告的地方就地使用(如置入<tt>std::function</tt>物件中); 這種情況下,若變數是以傳參考的方式連結到closure中,是無意義甚至是危險的行為。 若lambda函式只在定義的作用域使用,則可以用<tt>[&]</tt>宣告lambda函式,代表所有引用到stack中的變數,都是以參考的方式傳入,不必一一顯式指明: <syntaxhighlight lang="cpp"> std::vector<int> someList; int total = 0; std::for_each(someList.begin(), someList.end(), [&](int x) { total += x; }); </syntaxhighlight> 變數傳入lambda函式的方式可能隨實做有所變化,一般期望的方法是lambda函式能保留其作用域函式的stack指標,藉此存取區域變數。 若使用<tt>[=]</tt>而非<tt>[&]</tt>,則代表所有的參考的變數都是傳值使用。 對於不同的變數,傳值或傳參考可以混和使用。比方說,使用者可以讓所有的變數都以傳參考的方式使用,但帶有一個傳值使用的變數: <syntaxhighlight lang="cpp"> int total = 0; int value = 5; [&, value](int x) { total += (x * value); }; </syntaxhighlight> <tt>total</tt>是傳參考的方式傳入lambda函式,而<tt>value</tt>則是傳值。 若一個lambda函式被定義於某類別的成員函式中,则可以使用該類別物件的參考,並且能夠存取其內部的成員。 <syntaxhighlight lang="cpp"> [](SomeType *typePtr) { typePtr->SomePrivateMemberFunction(); }; </syntaxhighlight> 這只有當該lambda函式創建的作用域是在<tt>SomeType</tt>的成員函式內部時才能運作。 在成員函式中指涉物件的this指標,必須要顯式的傳入lambda函式,否則成員函式中的lambda函式無法使用任何該物件的變數或函式。 <syntaxhighlight lang="cpp"> [this]() { this->SomePrivateMemberFunction(); }; </syntaxhighlight> 若是lambda函式使用[&]或是[=]的形式,<tt>this</tt>在lambda函式即為可見。 lambda函式是編譯器從屬型別的函式物件;這種型別名稱只有編譯器自己能夠使用。如果使用者希望將lambda函式作為參數傳入,該型別必須是模版型別,或是必須創建一個<tt>std::function</tt>去獲取lambda的值。使用<tt>auto</tt>關鍵字讓我們能夠儲存lambda函式: <syntaxhighlight lang="cpp"> auto myLambdaFunc = [this]() { this->SomePrivateMemberFunction(); }; auto myOnheapLambdaFunc = new auto([=] { /*...*/ }); </syntaxhighlight> lambda函数按照值方式捕获的环境中的变量,是不能修改的。否则,编译器会报错:“by copy capture cannot be modified in a non-mutable lambda”。其值是lambda函数定义时捕获的值,不再改变。如果在lambda函数定义时加上mutable关键字,则该捕获的传值变量是可以修改的,对同一个lambda函数的随后调用也会累加影响该捕获的传值变量,但对外界环境中被捕获的那个变量无影响。例如: <syntaxhighlight lang="cpp"> #include <iostream> using namespace std; int main() { size_t t = 9; auto f = [t]() mutable {return t; }; cout << f() << endl; t = 100; cout << f() << endl; cout << "t:" << t << endl; return 0; } </syntaxhighlight> === 返回型別後置的函式宣告 === [[C語言|標準C]]函式宣告語法對於C語言已經足夠。演化自C的C++除了C的基礎語法外,又擴充額外的語法。然而,當C++變得更為複雜時,它暴露出許多語法上的限制,特別是針對函數模板的宣告。下面的範例,不是合法的C++03: <syntaxhighlight lang="cpp"> template< typename LHS, typename RHS> Ret AddingFunc(const LHS &lhs, const RHS &rhs) {return lhs + rhs;} //Ret的型別必須是(lhs+rhs)的型別 </syntaxhighlight> <tt>Ret</tt>的型別由<tt>LHS</tt>與<tt>RHS</tt>相加之後的結果的型別來決定。即使使用C++11新加入的<tt>decltype</tt>來宣告<tt>AddingFunc</tt>的返回型別,依然不可行。 <syntaxhighlight lang="cpp"> template< typename LHS, typename RHS> decltype(lhs+rhs) AddingFunc(const LHS &lhs, const RHS &rhs) {return lhs + rhs;} //不合法的C++11 </syntaxhighlight> 不合法的原因在於<tt>lhs</tt>及<tt>rhs</tt>在定義前就出現了。直到剖析器解析到函數原型的後半部,<tt>lhs</tt>與<tt>rhs</tt>才是有意義的。 針對此問題,C++11引進一種新的函數定義與聲明的語法: <syntaxhighlight lang="cpp"> template< typename LHS, typename RHS> auto AddingFunc(const LHS &lhs, const RHS &rhs) -> decltype(lhs+rhs) {return lhs + rhs;} </syntaxhighlight> 這種語法也能套用到一般的函數定義與聲明: <syntaxhighlight lang="cpp"> struct SomeStruct { auto FuncName(int x, int y) -> int; }; auto SomeStruct::FuncName(int x, int y) -> int { return x + y; } </syntaxhighlight> 關鍵字'''auto'''的使用與其在自動型別推導代表不同的意義。 === 物件建構的改良 === 在C++03中,建構式不能呼叫其它的建構式;每個建構式必須自己初始化所有的成員或是呼叫一個共用的成員函式。基礎類別的建構式不能夠直接作為衍生類別的建構式;就算基類的建構式已經足夠,每個衍伸的類別仍必須實做自己的建構式。類別中non-constant的資料成員不能夠在宣告的地方被初始化,它們只能在建構式中被初始化。 C++11將會提供這些問題的解決方案。 C++11允許建構式呼叫其他建構式,這種做法稱作委託(delegation)构造。僅僅只需要加入少量的代碼,就能讓數個建構式之間達成功能復用(reuse)。[[Java]]以及[[C♯]]都有提供這種功能。C++11語法如下: <syntaxhighlight lang="cpp"> class SomeType { int number; string name; SomeType( int i, string& s ) : number(i), name(s){} public: SomeType( ) : SomeType( 0, "invalid" ){} SomeType( int i ) : SomeType( i, "guest" ){} SomeType( string& s ) : SomeType( 1, s ){ PostInit(); } }; class DCExcept{ public: DCExcept(double d) try : DCExcept(1, d){ cout << "run the body." << endl; }catch(...){ cout << "caught exception."<<endl; } private: DCExcept(int i, double d){ cout << "going to throw" << endl; throw 0; } int type; double date; }; </syntaxhighlight> C++03中,建構式執行結束代表物件建構完成; 而允許使用轉接建構式的C++11則是以"任何"一個建構式結束代表建構完成。使用委托的建構式,函式本體中的代碼將於被委托的建構式完成後繼續執行(如上例的<tt>PostInit()</tt>)。若基類使用了委托建構式,則衍生類別的建構式會在"所有"基底類別的建構式都完成後,才會開始執行。 C++11允許衍生類別手動繼承基底類別的建構式,編譯器可以使用基底類別的建構式完成衍生類別的建構。而將基類的建構式帶入衍生類的動作,無法選擇性地部分帶入,要不就是繼承基類全部的建構式,要不就是一個都不繼承(不手動帶入)。此外,若牽涉到多重繼承,從多個基底類別繼承而來的建構式不可以有相同的函式簽名(signature)。而衍生類別的新加入的建構式也不可以和繼承而來的基底建構式有相同的函式簽名,因為這相當於重複宣告。 語法如下: <syntaxhighlight lang="cpp"> class BaseClass { public: BaseClass(int iValue); }; class DerivedClass : public BaseClass { public: using BaseClass::BaseClass; }; </syntaxhighlight> 此語法等同於DerivedClass宣告一個<tt>DerivedClass(int)</tt>的建構式。同時也因為DerivedClass有了一個繼承而來的建構式,所以不會有預設建構式。 另一方面,C++11可以使用以下的語法完成数据成員的原地(in-place)初始化: <syntaxhighlight lang="cpp"> class SomeClass { public: SomeClass() {} explicit SomeClass(int iNewValue) : iValue(iNewValue) {} private: int iValue = 5; }; </syntaxhighlight> 若是建構式中沒有設定<tt>iValue</tt>的初始值,則會採用類別定義中的成員初始化,令<tt>iValue</tt>初值為5。在上例中,無參數版本的建構式,<tt>iValue</tt>便採用預設所定義的值;而帶有一個整數參數的建構式則會以指定的值完成初始化。 成員初始化除了上例中的賦值形式(使用"="),也可以採用建構式以及統一形的初始化(uniform initialization,使用"{}")。 === 顯式虛函數重載=== 在C++裡,在子類別中容易意外的重載虛函數。舉例來說: <syntaxhighlight lang="cpp"> struct Base { virtual void some_func(); }; struct Derived : Base { void some_func(); }; </syntaxhighlight> <code>Derived::some_func</code>的真實意圖為何?程序員真的試圖重載該虛函數,或這只是意外?這也可能是<code>base</code>的維護者在其中加入了一個與<code>Derived::some_func</code>同名且擁有相同簽名的虛函式。 另一個可能的狀況是,當基類中的虛函式的簽名被改變,子類中擁有舊簽名的函式就不再重載該虛函式。因此,如果程序員忘記修改所有子類,執行期將不會正確呼叫到該虛函式正確的實現。 C++11將加入支援用來防止上述情形產生,並在編譯期而非執行期捕獲此類錯誤。為保持向後兼容,此功能將是選擇性的。其語法如下: <syntaxhighlight lang="cpp"> struct Base { virtual void some_func(float); }; struct Derived : Base { virtual void some_func(int) override; // 錯誤格式:Derive::some_func並沒有override Base::some_func virtual void some_func(float) override; // OK:顯式改寫 }; </syntaxhighlight> 編譯器會檢查基底類別是否存在一虛擬函數,與衍生類別中帶有聲明<code>override</code>的虛擬函數,有相同的函數簽名(signature);若不存在,則會回報錯誤。 C++11也提供指示字<code>final</code>,用來避免類別被繼承,或是基底類別的函數被改寫: <syntaxhighlight lang="cpp"> struct Base1 final { }; struct Derived1 : Base1 { }; // 錯誤格式:class Base1已標明為final struct Base2 { virtual void f() final; }; struct Derived2 : Base2 { void f(); // 錯誤格式:Base2::f已標明為final }; </syntaxhighlight> 以上的範例中,<code>virtual void f() final;</code>聲明一新的虛擬函數,同時也表明禁止衍生函數改寫原虛擬函數。 <code>override</code>與<code>final</code>都不是語言關鍵字(keyword),只有在特定的位置才有特別含意,其他地方仍舊可以作為一般指示字(identifier)使用。 === 空指標 === 早在1972年,C語言誕生的初期,常數[[0]]帶有常數及空指標的雙重身分。 C使用preprocessor macro <code>NULL</code>表示空指標,讓<code>NULL</code>及<code>0</code>分別代表空指標及常數0。 <code>NULL</code>可被定義為<code>((void*)0)</code>或是<code>0</code>。 C++並不採用C的規則,不允許將<code>void*</code>隱式轉換為其他型別的指標。為了使代碼<code>char* c = NULL;</code>能通過編譯,NULL只能定義為<code>0</code>。這樣的決定使得函數多載無法區分代碼的語意: <syntaxhighlight lang="cpp"> void foo(char *); void foo(int); void foo(nullptr_t); </syntaxhighlight> C++建議<code>NULL</code>應當定義為<code>0</code>,所以<code>foo(NULL);</code>將會呼叫<code>foo(int)</code>,這並不是程序員想要的行為,也違反了代碼的直觀性。0的歧義在此處造成困擾。 C++11引入了新的關鍵字來代表空指標常數:[[Nullptr|<code>nullptr</code>]],將空指標和整數0的概念拆開。 <code>nullptr</code>的型別為<code>nullptr_t</code>,能隱式轉換為任何指標或是成員指標的型別,也能和它們進行相等或不等的比較。而<code>nullptr</code>不能隱式轉換為整數,也不能和整數做比較。 為了向下相容,<code>0</code>仍可代表空指標常數。 <syntaxhighlight lang="cpp"> char* pc = nullptr; // OK int * pi = nullptr; // OK int i = nullptr; // error bool b = nullptr; // OK foo(pc); // 调用foo(char *), 而不是 foo(int); foo(nullptr); // 调用foo(nullptr_t); </syntaxhighlight> 值得注意的是上面的 foo(nullptr_t) 被隐式转换为 foo(char *) 只会发生在该函数不存在其它的指针类型重载(比如 foo(int*), foo(MyClass*)等)时候,否则就会产生歧义错误(可以通过显示声明一个 foo(nullptr_t) 来消除该歧义)。 在C++11的标准类型头文件中,nullptr_t 类型应该被声明为: <syntaxhighlight lang="cpp"> typedef decltype(nullptr) nullptr_t; </syntaxhighlight> 而不是: <syntaxhighlight lang="cpp"> typedef int nullptr_t; // C++11之前的标准需要定义 NULL 为 0 typedef void *nullptr_t; // ANSI C 定义 NULL 为 ((void*)0) </syntaxhighlight> === 強型別列舉 === 在C++03中,列舉型別不是型別安全的。列舉型別被視為整數,这使得两种不同的列舉型別之間可以進行比較。C++03唯一提供的安全機制是一个整数或一个枚举型值不能隱式轉換到另一个列舉別型。此外,列舉所使用整數型別及其大小都由實作方法定義,皆無法明確指定。最後,列舉的名稱全數暴露於枚举类型的[[作用域]]中,因此兩個不同的列舉,不可以有相同的列舉名。(好比<tt> enum Side{ Right, Left }; </tt>和<tt> enum Thing{ Wrong, Right }; </tt>不能一起使用。) C++11引進了一種特別的"列舉類",可以避免上述的問題。使用<tt>enum class</tt>的語法來宣告: <syntaxhighlight lang="cpp"> enum class myEnumeration { Val1, Val2, Val3 = 100, Val4 /* = 101 */, }; </syntaxhighlight> 此種列舉為型別安全的。列舉類別不能隱式地轉換為整數;也無法與整數數值做比較。(表示式<code>Enumeration::Val4 == 101</code>會觸發編譯期錯誤)。 列舉類別所使用型別必須顯式指定。在上面的範例中,使用的是預設型別<tt>int</tt>,但也可以指定其他型別: <syntaxhighlight lang="cpp"> enum class Enum2 : unsigned int {Val1, Val2}; </syntaxhighlight> 列舉類別的[[作用域]](scoping)不包含枚举值的名字。使用枚举值的名字,必須明確限定于其所屬的枚举类型。例如,前述列舉類別Enum2,<tt>Enum2::Val1</tt>是有意義的表示法,而單獨的<tt>Val1</tt>則否。 此外,C++11允許為傳統的列舉指定使用型別: <syntaxhighlight lang="cpp"> enum Enum3 : unsigned long {Val1 = 1, Val2}; </syntaxhighlight> 列舉名<tt>Val1</tt>定義於Enum3的列舉範圍中(Enum3::Val1),但為了向后相容性, <tt>Val1</tt>仍然可以於所属枚举类型所在的作用域中單獨使用。 在C++11中,列舉類別的前置聲明(forward declaration)也是可行的,只要使用可指定型別的新式列舉即可。之前的C++無法寫出列舉的前置聲明,是由於無法確定列舉變數所佔的空間大小,C++11解決了這個問題: <syntaxhighlight lang="cpp"> enum Enum1; // C++與C++11中不合法;無法判別大小 enum Enum2 : unsigned int; // 合法的C++11 enum class Enum3; // 合法的C++11,列舉類別使用預設型別int enum class Enum4: unsigned int; // 合法的C++11 enum Enum2 : unsigned short; // 不合法的C++11,Enum2已被聲明為unsigned int </syntaxhighlight> === 角括號 === C++03的剖析器一律將">>"視為右移運算子。但在嵌套樣板定義式中,絕大多數的場合其實都代表兩個連續右角括號。為了避免剖析器誤判,撰碼時不能把右角括號連著寫。 C++11變更了剖析器的解讀規則;當遇到連續的右角括號時,会在合理的情况下将右尖括號解析為樣板引數的結束符號。给使用<code>></code>,<code>>=</code>,<code>>></code>的表达式加上圆括号,可以避免其与圆括号外部的左尖括号相匹配: <syntaxhighlight lang="cpp"> template<bool bTest> class SomeType; std::vector<SomeType<1>2>> x1; // 解讀為std::vector of "SomeType<true> 2>", // 非法的表示式,整數1被轉換為bool型別true std::vector<SomeType<(1>2)>> x1; // 解讀為std::vector of "SomeType<false>", // 合法的C++11表示式,(1>2)被轉換為bool型別false </syntaxhighlight> === 顯式型別轉換子 === C++98引入了關鍵字<tt>explicit</tt>来避免用戶自定的單引數建構式被當成隱式型別轉換子。但是,却没有限制明确定义的类型转换函数。比方說,一個smart pointer類別具有一個<tt>operator bool()</tt>,被定義成若該smart pointer不为null則傳回true,反之傳回false。遇到這樣的代碼時:<tt>if(smart_ptr_variable)</tt>,編譯器可以藉由<tt>operator bool()</tt>隱式轉換成布林值,和测试原生指標的方法一樣。但是這類隱式轉換同樣也會發生在非預期之處。由於C++的<tt>bool</tt>型別也是算术型別,能隱式換為整數甚至是浮點數。拿物件轉換出的布林值做布林運算以外的數學運算,往往不是程序員想要的。 在C++11中,關鍵字<tt>explicit</tt>修飾符也能套用到型別轉換函数上。如同建構式一樣,它能避免型別轉換函数被隱式轉換調用。但C++11特別指定,在<tt>if</tt>條件式、迴圈、邏輯運算等需要布林值的地方,将其作为显式类型转换,因此即使对应的类型转换函数被explicit修饰也可以调用。这主要为了解决[https://en.wikibooks.org/wiki/More_C%2B%2B_Idioms/Safe_bool safe bool]{{Wayback|url=https://en.wikibooks.org/wiki/More_C%2B%2B_Idioms/Safe_bool |date=20151208081521 }}问题。 === 模板的別名 === 在進入這個主題之前,各位應該先弄清楚「模板」和「型別」本質上的不同。class template (類別模板,是模板)是用來產生template class(模板類別,是型別)。在C++03中,<code>typedef</code>可定義模板類別一個新的型別名稱,但是不能夠使用<code>typedef</code>來定義模板的別名。舉例來說: <syntaxhighlight lang="cpp"> template< typename first, typename second, int third> class SomeType; template< typename second> typedef SomeType<OtherType, second, 5> TypedefName; // 在C++03是不合法的 </syntaxhighlight> 這不能夠通過編譯。 為了定義模板的別名,C++11將會增加以下的語法: <syntaxhighlight lang="cpp"> template< typename first, typename second, int third> class SomeType; template< typename second> using TypedefName = SomeType<OtherType, second, 5>; </syntaxhighlight> <tt>using</tt>也能在C++11中定義一般型別的別名,等同<tt>typedef</tt>: <syntaxhighlight lang="cpp"> typedef void (*PFD)(double); // 傳統語法 using PFD = void (*)(double); // 新增語法 </syntaxhighlight> ===模板参数的缺省值 === C++98支持类模板的模板参数默认值,不支持函数模板的模板参数默认值。C++11可以支持函数模板的模板参数默认值。类模板的模板参数默认值需要从右到左依次出现;函数模板的默认模板参数没有此约束。 <syntaxhighlight lang="cpp"> void DefParm(int m = 3) {} // c++98编译通过,c++11编译通过 template <typename T = int> class DefClass {}; // c++98编译通过,c++11编译通过 template <typename T = int> void DefTempParm() {}; // c++98编译失败,c++11编译通过 //以下为C++11编译: template <typename T1, typename T2 = int> class DefClass1 {}; template <typename T1 = int, typename T2> class DefClass2 {}; // ERROR: 无法通过编译:因为模板参数的默认值没有遵循“由右往左”的规则 template <typename T, int i = 0> class DefClass3 {}; template <int i = 0, typename T> class DefClass4 {}; // ERROR: 无法通过编译:因为模板参数的默认值没有遵循“由右往左”的规则 template <typename T1 = int, typename T2> void DefFunc1(T1 a, T2 b) {}; // OK 函数模板不用遵循“由右往左”的规则 template <int i = 0, typename T> void DefFunc2(T a) {}; // OK 函数模板不用遵循“由右往左”的规则 //通常,如果能够从函数实参中推导出类型的话,那么默认模板参数就不会被使用,反之,默认模板参数则可能会被使用: template <class T, class U = double> void f(T t = 0, U u = 0) {}; void g() { f(1, 'c'); // f<int, char>(1, 'c') f(1); // f<int, double>(1, 0), 使用了默认模板参数double f(); // 错误: T无法被推导出来 f<int>(); // f<int, double>(0, 0), 使用了默认模板参数double f<int, char>(); // f<int, char>(0, 0) } </syntaxhighlight> === 無限制的unions === 在C++03中,並非任意的型別都能做為union的成員。比方說,帶有non-trivial 建構式的型別就不能是union的成員。在新的標準裡,移除了所有对union的使用限制,除了其成員仍然不能是引用型別。這一改變使得union更強大,更有用,也易於使用。<ref>{{Cite web |url=http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2008/n2544.pdf |title=N2544 |accessdate=2009-02-06 |archive-date=2008-12-21 |archive-url=https://web.archive.org/web/20081221083351/http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2008/n2544.pdf |dead-url=no }}</ref> 但是如果union成员具有非平凡的[https://en.wikipedia.org/wiki/Special_member_functions 特殊成员函数]{{Wayback|url=https://en.wikipedia.org/wiki/Special_member_functions |date=20151208104850 }},则编译器不会为union生成对应的特殊成员函数,必须手工定义。 以下為C++11中union使用的簡單範例: <syntaxhighlight lang="cpp"> struct Point { Point() {} Point(int x, int y): x_(x), y_(y) {} int x_, y_; }; union U { int z; double w; Point p; // 在C++03中是不合法(point有一non-trivial建構式),但是在C++11是合法的 U() {} // 由于 Point 成员的存在,必须要定义一个构造函数 U(const Point& pt) : p(pt) {} // 通过初始化列表构造 Point 对象 U& operator=(const Point& pt) { new (&p) Point(pt); return *this; } // 通过原地new方式赋值构造Point对象 }; </syntaxhighlight> 這一改變僅放寬union的使用限制,不會影響既有的舊代碼。
摘要:
请注意,所有对Local Chinese Wikipedia的贡献均可能会被其他贡献者编辑、修改或删除。如果您不希望您的文字作品被随意编辑,请不要在此提交。
您同时也向我们承诺,您提交的内容为您自己所创作,或是复制自公共领域或类似自由来源(详情请见
Project:著作权
)。
未经许可,请勿提交受著作权保护的作品!
取消
编辑帮助
(在新窗口中打开)
导航菜单
个人工具
未登录
讨论
贡献
创建账号
登录
命名空间
页面
讨论
大陆简体
不转换
简体
繁體
大陆简体
香港繁體
澳門繁體
大马简体
新加坡简体
臺灣正體
查看
阅读
编辑
查看历史
更多
搜索
导航
首页
最近更改
随机页面
MediaWiki帮助
工具
链入页面
相关更改
特殊页面
页面信息