3.1.C++11語言新特性
3.1.1微小但重要的語法提升
nullptr和std::nullptr_t :
nullptr取代0或NULL,表示一個pointer(指標)只想指向的no value。std::nullptr_t定義在<cstddef>中,為基礎型別。
3.1.2以auto完成型別自動推導
以auto宣告的變數,會自動根據其初始值自動推匯出型別:(必須初始化)
如:auto i=42;
double f();
auto d=f();
可加額外限定符static:如:static auto vat=0.19;
3.1.3一致性初始化(Uniform Initialization)與初始列(Initializer List)
一致性初始化:面對任何初始化動作,都可以使用大括號。
如:int values[] {1,2,3}
std::vector<int>v {2,3,4,6,9};
初始列:強制value initialization,在local變數為某基礎型別時,若沒有明確的初始值,則會被初始化為0.
如:int j{};
注意:
如:int x1(5.3);對
Int x2=5.3;對
Int x3{5,0};不對, narrowing
Int x4={5.3}:不對,narrowing
因此,為了支援“使用者自定義型別之初值列”(initializer lista for user_defined types)概念,C++11提供了class template std::initializer_list<>,支援以一系列值進行初始化,或在“想要處理一系列值”的任何位置初始化。
如:
Void print (std::initializer_list<int>vals)
{
For(auto p=vals.begin();p!=vals.end();++p){ //process a list of values
std::cout<<*p<<”\n”;
}
}
print({12,3,5,6,3,767,54});//pass a list of values to print()
當“指明實參個數”和“指明一個初值列”的建構函式(ctor)同時存在,帶有初值列的那個版本勝出。
如:class P
{
Public:
P(int ,int);
P(std::initializer_list<int>);
};
P p(77,5); //calls P::P(int,iint)
P q{77,5}; //calls P::P(initializer_list)
P r{77,5,42}; //calls P::P(initializer_list)
P s={77,5}; //calls P::P(initializer_list)
如果上述“帶有一個初始值列”的建構函式不存在,那麼結婚搜兩個int的那個建構函式會被呼叫以初始化q和s,而r的初始化將無效。
explicit建構函式如果接受的是個初始列,會失去“初值列帶有0個、1個或多個初值”的隱式轉換能力。
3.1.4 Range-Based for 迴圈
C++11引入的一種嶄新的for迴圈形式,可以逐一迭代某個給定的區間、陣列、集合(range.array,or collection)內的每一個元素。其他語言可為foreach迴圈。
其一般性語法如下:
for(decl : coll){
statement
}
列印某集合內所有元素“的泛型函式如下:
template <typename T>
void printElements(const T& coll)
{
for(const auto& elem : coll){
std::cout<<elem<<std::endl;
}
}
注意:當元素在for迴圈中被初始化為decl,不得有任何顯示型別轉換(explicit type consversion)
3.1.5 Move語義和Rvalue Reference
C++11的一個重要特性:支援move semantic(搬遷語義),用以避免非必要拷貝(copy)和臨時物件(temporary)。
Rvalue和Lvalue Reference的過載規則(overloading rule)如下:
1、實現void foo(X&);不實現void foo(X&&);其行為同C++98:foo()可因lvalue但不因rvalue被呼叫。
2、實現void foo(const X&);不·實現void foo(X&&);其行為同C++98:foo()可因lvalue或rvalue被呼叫。
3、實現void foo(X&);void foo(X&&);或void foo(const X&);void foo(X&&);則可以區分“為rvalue服務”和“為lvalue服務”。“為rvalue服務”的版本被允許且應該提供move語義。
4、實現void foo(X&&);不實現void foo(X&),也不實現void foo(const X&),這時,foo()可因rvalue被呼叫(只提供move語義),其在unique pointer、file stream或string stream使用。
總之,若classs未提供move語義,只提供慣常的copy建構函式和copy assignment操作符,rvalue reference 可呼叫它們。即,std::move()意味著“呼叫move語義(若提供),否則呼叫copy語義。
返回Rvalue Reference
不需要也不該move()返回值,對於下面程式碼:
X foo()
{
X x;
…
return x;
}
需保證:若X有可用的copy或move建構函式,則編譯器可略去其中的copy版本;否則,若X有copy建構函式,X會被moved(搬移);若X有copy建構函式,X會被copied(複製);否則,報編譯器錯誤(compile-time error).
3.1.6 新式的字串字面常量(String Literal)
Raw String Literal
Raw string允許定義字元序列(character sequence),做法是確切寫下其內容使其成為一個raw character sequence。
Raw string以R“(開頭、以)”結尾,可內含line break.
如:“\\\\n”等價於R”(\\n)”,表示:兩個反斜線和一個n。
若在raw string內寫出,可使用定義符(delimiter),故raw string的完整語法為R”delim(..)delim”,delim是個字元序列(character sequence),最多16個基本字元,不能有反斜線(backslash)、空格(whitespace)和小括號(parenthesis)。
如:R”nc(a\
b\nc()”
)nc”;
等價於尋常的string literal : “a\\\n b\\nc()\”\n
含義:string內含一個a、一個反斜線、一個新行字元(newline)、若干空格(space),一個b、一個反斜線、一個n、一個c、一對小括號、一個雙引號、一個新行字元,以及若干空格。
Raw string literal 可用於定義正則表示式(regular expression)。
編碼的(Encoded)String Literal
使用編碼字首i(encoding prefix),可為string literal定義一個特殊的字元編碼(character encoding)
如:1.u8定義UTF-8編碼,
2.u 定義一個literal,字元為char16_t型別
3.U定義一個literal,字元為char32t型別
4.L定義一個wide string literal,字元為wchar_t的字元
3.1.7 關鍵字noexcept
C++11提供了關鍵字noexcept,用來指明某個函式無法或者不打算丟擲異常。如:void foo () noexcept;
聲明瞭foo()不打算丟擲異常。若有異常未在foo()內被處理,程式會終止。
兩種有用的丟擲異常:操作可能丟擲異常(任何異常),操作絕不會丟擲任何異常。
指定在某種情況下函式不丟擲異常:如,對於任意型別Type,全域性性的swap()通常定義如下:
void swap(Type& x,Type& y)noexcept(noexcept(x.swap(y)))
{
x.swap(y);
}在noexcept(…)中可指定一個Boolean條件,若不符合就不丟擲異常。
3.1.8 關鍵字constexpr
自C++11起,constexpr可用來讓表示式核定於編譯器。例如:
constexpr int square (int x)
{
return x*x;
}
float a[square(9)];//OK since C++11:a has 81 elements
關鍵字修復了一個在C++988使用數值許可權時出現的問題,如:std::numeric_limits<short>::max()在功能上等同於巨集INT_MAX,無法用作整數常量,但在C++11中,這式子被宣告為constexpr.
3.1.9 嶄新的Template特性
自C++11起,template可擁有“得以接受個數不定之template實參”得引數,此能力稱為variadic template。例如:
void print ()
{
}
template<typename T,typename… Types>
void print (const T& firstArg,const Types&… args)
{
std::cout<<firstArg<<std:;endl; //print first argument
print(args…); //call print()for remaining arguments
}
若傳入1個或者多個實參,上述的function template 就會被呼叫,通過遞迴呼叫,依此傳入每一個實參,並列印。只有提供一個nontemplate過載函式print(),才能結束整個遞迴動作。
Alias Template(帶別名的模板,或者叫Template Typedf)
自C++11起,支援template(partial)type definition>’然而由於關鍵字typename用於此處時總是出於某種原因而失敗,因而要引入關鍵字using,並引入一個新術語alias template。如:
template <typename T>
using Vec=std::vector<T,MyAlloc<T>>; //standard vector using own allocator
後,Vec<int>coll;就等價於std::vector<int,MyAlloc<int>>coll;
其他的新特性Template新特性
自C++11起,function template可擁有預設的template實參。此外,LOCAL type可被當作template實參。
3.1.10 Lambda
C++11引入lanbda,允許inline函式的定義式被用作一個引數,或是一個local物件。
Lambda的語法
Lambda是一發呢功能定義式,課被定義於語句(statement)或表示式(expression)內部。故可拿Lambda當作inline函式使用。
最小型的lambda函式沒有引數嗎,什麼也不做,如:
[]{
std::cout<<”hello lambda”<<std::endl;
}
可直接呼叫:
[]{
std::cout<<”hello lambda”<<std::endl;
}(); //prints”hello lambda”
或是把他傳給物件,是其能被呼叫:
auto L=[]{
std::cout<<”hello lambda”<<std::endl;
};
…
L(); //prints”hello lambda”
Lambda由一個lambda introducer引入:是一組方括號,可在內指明一個capture,用來在lambda內訪問“nonstatic外部物件”。
Lambda可以擁有引數,指明於小括號內,就像任何函式一樣:
auto L=[](const std::string& s){
std::cout<<s<<std<<endl;
};
L(“hello lambda”); //prints “hello lambda”
但是lambda不能是template,必須指明所有型別。lambda也可以返回某物,無需指明返回型別,會被自棟推匯出來。
Capture(用來訪問外部作用域)
在lambda introducer(每個lambda最開始的方括號)內,指明一個capture處理外部作用域內未被傳遞為實參的資料:
1.[=]表示外部作用域以by value方式傳遞給lambda,故次lambda被定義時,只能讀取可讀資料,不能改動。
2.[&]表示外部作用域以by reference 方式傳遞給lambda,故lambda被定義時,可對資料塗寫,前提是擁有許可權。
宣告lambda為mutable,則可以獲得passing by value和passing by reference混合體。
如:
int id=0;
auto f=[id]()mutable{
std::cout<<”id:”<<id<<std:;endl;
++id; //OK
};
id=42;
f();
f();
f();
std::cout<<id<<std::endl;
會產生以下輸出:
id:0
id:1
id:2
42
上述lambda的行為同以下function object:
class{
private:
int id; //coopy of outside id
public:
void operator()(){
std::cout<<”id”<<id<<std::endl;
++id; //OK
}
};
Lambda的型別
Lambda的型別,是不具名function object(或稱function)。可使用template或auto給該型別宣告物件,可使用decltype()寫下該型別。也可使用C++標準庫提供的std::function<>class template,指明一個一般化型別給function programming使用,class template提供了“明確指出函式的返回型別為lambda”的唯一辦法:
//lang/lambda1.cpp
#include<functional>
#include<iostream>
std:;function<int(int,int)>returnLambda()
{
return [](int x,int y){
return x*y;
};
}
int main()
{
auto lf=returnLambda();
std::cout<<lf(6,7)<<std::endl;
}
輸出:42
3.1.11 關鍵字decltype
新關鍵字decltype可讓編譯器找出表示式(express)型別,這是typeof得特性體現。C++11引入這個關鍵字是為了彌補原有的typeof缺乏的一致性和不完全。如:
std::map<std::string,float>coll;
decltype(coll)::value_type elem;
decltype的應用之一是應用宣告返回型別,另一用途是在metaprogramming(超程式設計)或用來傳遞一個lambda型別。
3.1.12 新的函式宣告語法(New Function Declaration Syntax)
在C++11中,可以將一個函式的返回型別轉而聲明於引數列之後:
template<typename T1,typename T2>
auto add(T1 x,T2 y)->decltype(x+y);
此語法同“為lambda宣告返回型別”是一樣的。
3.1.13 帶領域的(Scoped)Enumeration
C++11允許定義scope enumeration—又稱為strong enumeration 或enumeration class。如:
enum class Salutation:char{mr,ms,co,none};
重點在於,在enum之後指明關鍵字class.
優點如下:
1.不隱式轉換至/自int;
2.若數值(如mr)不在enumeration被宣告的作用域內,則必須改為Salutation::mr;
3.可明顯定義地層型別(underlying type,本例是char),獲得保證大小(若略去這裡的“:char”,預設型別是int);
4.可提前宣告(forward declaration)enumeration type,消除“為了新的enmerations value而重新變異“的必要—如果只有型別被使用的話。
注意:type trait std::underlying_type,可核定(evaluate)一個enumeration type的低層型別。標準異常的差錯值(error condition value)也是個scoped enumerator.
3.1.12 新的基礎型別(New Fundamental Data Type)
以下新的基本資料型別,定義於C++:
1.char16_t和char32_t
2.long long 和unsigned long long
3.std::nullptr_t
3.2 雖舊尤新的語言特性
Nontype Template Parameter(非型別模板引數)
對於標準的class bitset<>,可傳遞bit個數作為template實參,如下:
bitset<32>flags32; //bitset with 32 bits
Default Template Parameter(模板引數預設值)
Class template可擁有預設實參。如下宣告式允許我們在宣告class MyClass物件時指定1或2個template實參:
template<typename T,typename container=vector<T>>
class MyClass;
若只傳入一個實參,第二個實參會採用預設值:
MyClass<int>x1; //equivalent to:MyClass<int,vector<int>>
注意,預設的template實參可以其前一個實參為依據為定義。
關鍵字typename
關鍵字typename用來指明緊跟其後的是個型別。如,
template<typename T>
class MyClass{
typename T::SubType*ptr;
…
};
這裡的typename用來闡明“SubTypehi是個型別,定義於class T內”。因此ptr是個pointer,指向型別T::SubType。若沒有typename,SubType會被視為一個static。
基於“SubType必須是個型別”這樣的限定,任何被用來替換T的型別,都必須提供一個內層型別SubType.內層型別可以是個抽象資料型別(abstract data ttype)。template內的任何識別符號都被視為一個value,除非為它加上typename,在template宣告式中typename也可被用來取代class:template<typenmae T>class MyClass;
Member Template(成員模板)
Class的成員函式可以是template,但member template不可以是virtual。如:
class MyClass{
…
template<typename T>
void f(T);
};
上述的MyClass::f聲明瞭“一組”成員函式,其引數可以是任意型別。可傳遞任何實參給它們。只要該實參的型別提供“f()用到的所有操作”.
Member template的一個特殊形式是所謂template建構函式,通常被提供用於“物件被複制時給與隱式轉換”的能力。template建構函式並不壓制copy建構函式的隱私宣告。若型別完全吻合,則隱式的copy建構函式會被生成出來。
Nested(巢狀式)Class Template
巢狀式(Nested class)也可以是template;
template<typename T>
class MyClass{
..
template<typename T2>
class NestedClass;
…
};
3.2.1 基礎型別的明確初始化(Explicit Initialization for Fun)
使用“一個明確的建構函式呼叫,但不給實參“的語法,基礎型別會被設定初值為0。如果一個template強迫設定為0,其值就是所謂的zero initialized,否則就是default initialized。
3.2.2 main()定義式
main()只有兩種定義式具備可移植性:
int main()
{
…
}
和int main(int argc,char* argv[])
{
…
}
其中的argv,也就是命令列實參(command-line argument)所形成的array,也可定義為char**。返回型別必須是int 。若不想以“main()返回“方式結束C++程式,通常應該呼叫exit()、quick_exit()(C++11之後)或terminate()。