1. 程式人生 > >3.1.C++11語言新特性

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{};

注意:

窄化(narrowing)精度降低或者造成數值變動,對大括號不成立

如: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()可因lvaluervalue被呼叫。

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()。