C++11拾穗

C++11新關鍵字

alignas:指定對齊大小ios

alignof:獲取對齊大小程序員

decltypeexpress

auto(從新定義):可做爲返回值類型後置時的佔位符數組

static_assert:靜態斷言數據結構

using(從新定義):類型別名或者模板別名ide

noexcept:聲明函數不能夠拋出任何異常函數

export(棄用,不過將來可能留做他用)this

nullptrspa

constexpr:可在在編譯期確認的常量表達式3d

thread_local:等價於TLS

快速初始化成員變量

C++11中支持使用等號 = 或者花括號 {} 進行就地的(也就是在聲明的時候)非靜態成員變量初始化。例如:

struct init{
    int a = 1;
    double b {1.2};
}

在C++11標準支持了就地初始化非靜態成員的同時,初始化列表這個手段也被保留下來了。只不過初始化列表老是看起來「後做用於」非靜態成員。

final/override控制

C++11提供關鍵字final,做用是使派生類不可覆蓋它所修飾的虛函數。final關鍵字也可 用於基類中,可是這樣定義的虛函數就沒有意義了。final一般就是在繼承關係的「中途」終止派生類的重載。

在C++中對於基類聲明爲virtual的函數,以後的重載版本都不須要再聲明該重載函數爲virtual。即便在派生類中聲明瞭virtual,該關鍵字也是編譯器能夠忽略的。另外,有的虛函數會「跨層」,沒有在父類中聲明的接口有多是祖先的虛函數接口。因此C\++11引入了虛函數描述符override,來幫助程序員寫繼承結構複雜的類型。若是派生類在虛函數聲明時使用了override描述符,那麼該函數必須重載其基類中的同名函數,不然代碼將沒法經過編譯。

繼承構造函數

C++11提供了繼承構造函數,在派生類中使用using聲明,就能夠把基類中的構造函數繼承到派生類中。 但其實質是編譯器自動生成代碼,經過調用父類構造函數來實現,不是真正意義上的「繼承」,僅僅是爲了減小代碼書寫量。

class A {
    A(int i) {}
    A(double d, int i) {}
    A(char *c , double d, int i) {}
    //... 更多構造函數
};

class B : A {
    using A::A; //繼承構造函數
    //...
}

委派構造函數

C++11提供委派構造函數,能夠簡化多構造函數的類的編寫。若是咱們能將一個構造函數設定爲「基準版本」,則其餘構造函數能夠經過委派「基準版本」來進行初始化。咱們將這個「基準版本」稱爲目標構造函數。

class Info {
public:
    Info() { InitRest(); }//目標構造函數
    Info(int i) : Info() { type = i; }//委派構造函數
    Info(char c) : Info() { name = c; }//委派構造函數
    
private:
    void InitRest(); { /* 其餘初始化 */}
    int type {1};
    char name {'a'};
    //...
}

注意:委派構造函數不能有初始化列表。若是委派構造函數要給變量賦初值,初始化代碼必須放在函數體中。

在使用委派構造函數時,建議程序員抽象出最爲「通用」的行爲做爲目標構造函數。

移動語義

拷貝構造與移動構造
在C++11中,這樣的「偷走」臨時變量中資源的構造函數,被稱爲移動構造函數。而這樣的「偷」的行爲,則稱之爲「移動語義」。

class HasPtrMem(){
public:
    HasPtrMem() : d(new int(3)) {}
    HasPtrMem(const HasPtrMem & h) : d(new int(*h.d)) {}// 拷貝構造函數
    HasPtrMem(HasPtrMem && h) : d(h.d) {h.d = nullptr;}// 移動構造函數,需將臨時值的指針成員置空
    ~HasPtrMem() {delete d;}
private:
    int *d;
}

左值,右值,右值引用

關於左值,右值很難去作一個很是明確的定義,可是在C++中有一個被普遍認同的說法,那就是能夠取地址的、有名字的就是左值,反之,不能取地址的、沒有名字的就是右值。

不管是聲明一個左值引用仍是右值引用,都必須當即進行初始化。左值引用是具名變量值的別名,而右值引用則是不具名(匿名)變量的別名。

標準庫在<type_traits>頭文件中提供了3個模板類:is_rvalue_reference、is_lvalue_reference、is_reference,可供咱們進行判斷引用類型是左值引用仍是右值引用。

std::move:強制轉化爲右值

C++11中提供了std::move這個函數使咱們能將一個左值強制轉化爲右值引用,繼而咱們能夠經過右值引用使用該值,以用於移動語義。

// 使用上面例子中的HasPtrMem
HasPtrMem a;
HasPtrMem b(std::move(a));// 調用移動構造函數

事實上,爲了保證移動語義的傳遞,程序員在編寫移動構造函數的時候,應該老是記得使用std::move轉換擁有形如堆內存、文件句柄等資源的成員爲右值,這樣依賴,若是成語支持移動構造的話,就能夠實現其移動語義。而即便成員沒有移動構造函數,那麼接受常量左值的構造函數版本也會輕鬆地實現拷貝構造,所以也不會引發大的問題。

顯示轉換操做符

在C++中,有一個很是好也很是壞的特性,就是隱式類型轉換。隱式類型轉換的「自動性」可讓程序員免於層層構造類型。但也是因爲它的自動性,會在一些程序員意想不到的地方出現嚴重的但不易被發現的錯誤。

關鍵字explicit主要用於修飾的構造函數,其做用主要就是阻止構造函數的隱式轉換。

C\++11中將explicit的使用範圍擴展到了自定義的類型轉換操做符上,以支持所謂的「顯示類型轉換」。

列表初始化

在C\++11中可使用花括號「{}」來進行初始化,這種初始化方式被稱爲列表初始化,已經稱爲C++語言的一個基本功能。咱們甚至可使用列表初始化的方式對vector、map等非內置的複雜的數據類型進行初始化。

並且,C++11中,標準老是傾向於使用更爲通用的方式來支持新的特性。標準模板庫中容器對初始化列表的支持源自<initilizer_list>這個頭文件中的initilizer_list類模板的支持。咱們能夠經過聲明已initilizer_list<T>模板類爲參數的構造函數,來使得自定義的類使用列表初始化。

enum Gender {boy, girl};
class People{
public:
    People(initilizer_list<pair<string, Gender>> l) {//initilizer_list的構造函數
        auto i = l.begin();
        for (; i != l.end(); ++i)
            data.pushback(*i);
    }
private:
    vector<pair<string, Gender>> data;
};

People ship2012 = {{"Garfield", boy}, {"HelloKitty", girl}};

另外,使用列表初始化能夠有效的防止類型收窄。類型收窄通常是指一些可使得數據變化或者精度丟失的隱式類型轉換。

auto類型推導

auto聲明的變量必須被初始化,以使編譯器可以從其初始化表達式中推導出其類型。

auto使用時需注意:

(1)、可使用const、volatile、pointer(*)、reference(&)、rvalue reference(&&)等說明符和聲明符來修飾auto關鍵字;

(2)、用auto聲明的變量必須初始化;

(3)、auto不能與其它任何類型說明符一塊兒使用;

(4)、方法、參數或模板參數不能被聲明爲auto;

(5)、定義在堆上的變量,使用了auto的表達式必須被初始化;

(6)、auto是一個佔位符,不是類型,不能用於類型轉換或其它一些操做,如sizeof、typeid;

(7)、auto關鍵字內聲明的聲明符列表的全部符號必須解析爲同一類型;

(8)、auto不能自動推導成CV-qualifiers(constant& volatile qualifiers),除非被聲明爲引用類型;

(9)、auto會退化成指向數組的指針,除非被聲明爲引用;

(10)、auto不能做爲函數的返回型,在C++14中是能夠的。

decltype

decltype的類型推導並非像auto同樣是從變量聲明的初始化表達式得到變量的類型,decltype老是以一個普通的表達式爲參數,返回該表達式的類型。而與auto相同的是,做爲一個類型指示符,decltype能夠將得到的類型來定義另一個變量。與auto相同,decltype類型推導也是在編譯時進行的。

decltype只能接受表達式作參數,像函數名作參數的表達式decltype(hash)這種是沒法經過編譯的。

追蹤返回類型

利用auto和decltype以及返回類型後置的語法就能實現追蹤返回類型。好比,咱們想寫一個泛型的加法函數時可能直觀的寫下以下代碼。

template <typename T1, typename T2>
decltype(t1 + t2) sum(const T1& t1, const T2& t2) {
   return t1 + t2;
}

可是,這樣寫是編譯不過的,由於編譯器只會從左往右地讀入符號,這裏的t1和t2在編譯器看來都是未聲明的。正確的寫法是這樣的。

template <typename T1, typename T2>
auto sum(const T1& t1, const T2 & t2) -> decltype(t1 + t2) {
    return t1 + t2;
}

基於範圍的for循環

語法很簡單,不贅述。主要使用時須要注意兩點。一是使用基於範圍的for循環須要for循環迭代的範圍是可肯定的。二是,基於範圍的for循環要求迭代對象實現++和==等操做符,這點標準庫中的容器不會有問題,但用戶自定義的類須要本身實現。

強類型枚舉

原來的枚舉類型有非強類型做用域,容許隱式轉換爲整型,佔用存儲控件及符號性不肯定的缺點。C++11引入了強類型枚舉來解決問題。

聲明強類型枚舉只須要在enum後加上關鍵字class。好比:

enum class Type { General, Light, Medium, Heavy};

強類型枚舉具備一下幾點優點:

  • 強做用域,強類型枚舉成員的名稱不會輸出到其父做用域空間。
  • 轉換限制,強類型枚舉成員的值不能夠與整型隱式地相互轉換。
  • 能夠指定底層類型。強類型枚舉默認的底層類型爲int,但也能夠顯式地指定底層類型,具體方法爲在枚舉名稱後面加上":type",其中type能夠是除了wchar_t之外的任何整型。好比:

enum class Type: char { General, Light, Medium, Heavy};

因爲enum class是強類型做用域的,故匿名的enum class極可能什麼都作不了。

常量表達式函數

常量表達式函數須要知足幾個條件,不然不能用constexpr關鍵字進行修飾:

  • 函數只能包含return語句。
  • 函數必須有返回值。
  • 在使用前必須已經定義。
  • return返回語句中不能使用很是量表達式的函數、全局數據,且必須是一個常量表達式。

constexpr int GetConst() { return 1; }

常量表達式值

const int i = 0;

constexpr int j = 0;

constexpr表示的就是編譯期常量,const表示的是運行期常量。

大部分狀況下這兩個定義是沒有區別的。不過i只要在全局範圍內聲明,編譯器必定會爲它產生數據;而對於j,若是沒有地方調用它,編譯器能夠選擇不爲它生成數據。

而且,默認只有內置類型才能修飾爲常量表達式值,自定義類型若是要成爲常量表達式值,必須定義一個constexpr修飾的構造函數。

tuple元組

當咱們但願將一些數據組合成單一對象,但又不想麻煩地定義一個新的數據結構來表示這些數據時,tuple是很是有用的。通常,tuple能夠用於函數返回多個返回值。

tuple容器, 可使用直接初始化, 和"make_tuple()"初始化, 訪問元素使用"get<>()"方法, 注意get裏面的位置信息, 必須是常量表達式(const expression)。

能夠經過"std::tuple_size<decltype(t)>::value"獲取元素數量; "std::tuple_element<0, decltype(t)>::type"獲取元素類型。

若是tuple類型進行比較, 則須要保持元素數量相同, 類型能夠比較。

#include <iostream>  
#include <vector>  
#include <string>  
#include <tuple>  
  
using namespace std;  
  
std::tuple<std::string, int>  
giveName(void)  
{  
    std::string cw("Caroline");  
    int a(2013);  
    std::tuple<std::string, int> t = std::make_tuple(cw, a);  
    return t;  
}  
  
int main()  
{  
    std::tuple<int, double, std::string> t(64, 128.0, "Caroline");  
    std::tuple<std::string, std::string, int> t2 =  
            std::make_tuple("Caroline", "Wendy", 1992);  
  
    //返回元素個數  
    size_t num = std::tuple_size<decltype(t)>::value;  
    std::cout << "num = " << num << std::endl;  
  
    //獲取第1個值的元素類型  
    std::tuple_element<1, decltype(t)>::type cnt = std::get<1>(t);  
    std::cout << "cnt = " << cnt << std::endl;  
  
    //比較  
    std::tuple<int, int> ti(24, 48);  
    std::tuple<double, double> td(28.0, 56.0);  
    bool b = (ti < td);  
    std::cout << "b = " << b << std::endl;  
  
    //tuple做爲返回值  
    auto a = giveName();  
    std::cout << "name: " << get<0>(a)  
            << " years: " << get<1>(a) << std::endl;  
  
    return 0;  
}

nullptr

在C++11中,nullptr是一個所謂「指針空值類型」的編譯期常量。指針空值類型被命名爲nullptr_t。

須要注意的是,nullptr是C++11中的關鍵字,它是有類型的,且僅能夠被隱式轉化爲指針類型,其類型定義是:

typedef decltype(nullptr) nullptr_t;

lambda函數

lambda表達式的語法定義以下:

\[capture] (parameters) mutable ->return-type {statement};

  • \[capture]:捕捉列表。捕捉列表老是出如今lambda函數的開始處。實質上,[]是lambda引出符(即獨特的標誌符),編譯器根據該引出符判斷接下來的代碼是不是lambda函數。捕捉列表可以捕捉上下文中的變量以供lambda函數使用。捕捉列表由一個或多個捕捉項組成,並以逗號分隔,捕捉列表通常有如下幾種形式:

    • [var] 表示值傳遞方式捕捉變量var。
    • [=] 表示值傳遞方式捕捉全部父做用域的變量(包括this指針)。
    • [&var] 表示引用傳遞捕捉變量var。
    • [&] 表示引用傳遞捕捉全部父做用域的變量(包括this指針)。
    • [this]表示值傳遞方式捕捉當前的this指針。
    • [=,&a,&b]表示以引用傳遞的方式捕捉變量 a 和 b,而以值傳遞方式捕捉其餘全部的變量。
    • [&,a,this]表示以值傳遞的方式捕捉 a 和 this,而以引用傳遞方式捕捉其餘全部變量。
備註:父做用域是指包含lambda函數的語句塊。
  • (parameters):參數列表。與普通函數的參數列表一致。若是不須要參數傳遞,則能夠連同括號()一塊兒省略。
  • mutable :mutable修飾符。默認狀況下,lambda函數老是一個const函數,mutable能夠取消其常量性。在使用該修飾符時,參數列表不可省略(即便參數爲空)。
  • ->return-type :返回類型。用追蹤返回類型形式聲明函數的返回類型。出於方便,不須要返回值的時候也能夠連同符號->一塊兒省略。此外,在返回類型明確的狀況下,也能夠省略該部分,讓編譯器對返回類型進行推導。
  • {statement} :函數體。內容與普通函數同樣,不過除了可使用參數以外,還可使用全部捕獲的變量。
// 簡單示例
int a = 20, b = 10;

auto totalAB = [] (int x, int y)->int{ return x + y; };
int aAddb = totalAB(a, b);
cout << "aAddb :" << aAddb << endl;

// lambda與STL
vector<int> v{ 1, 2, 3, 4, 5 }; 
  
for_each( v.begin(), v.end(), [] (int val)  
{  
    cout << val;  
} );

在現階段,一般編譯器都會把lambda函數轉化爲一個仿函數對象。這是編譯器實現lambda函數的一種方式。所以,C++11中,lambda函數能夠視爲仿函數的一種等價形式。

原生字符串字面量

原生字符串字面量的意思就是所見即所得,在代碼中的字符串常量是怎麼樣的,咱們獲得的就是怎麼樣的,不須要轉義字符來控制特定的字符。

在C++11中程序員只須要在字符串前面加入前綴字母R,並在引號中使用括號左右標識便可將該字符串聲明爲原生字符串了。

// 輸出帶引號的字符串"你好"
int main()
{
    std::string str = "\"你好\"";
    std::cout << str << std::endl;

    const char* str1 = R"("你好")";
    std::cout << str1 << std::endl;

    return 0;
}
相關文章
相關標籤/搜索