C++11新特性——The C++ standard library, 2nd Edition 筆記(一)

前言html

  這是我閱讀《The C++ standard library, 2nd Edition》所作讀書筆記的第一篇。這個系列基本上會以一章一篇的節奏來寫,少數以C++03爲主的章節會和其它章節合併,一些內容較多的章節也會抽出幾個獨立的章節。這些博文不分析具體的應用情景,僅僅用來記錄C++11新增的知識點。關於C++11知識點的詳細解析,請參考C++11 FAQ;關於C++03以及STL的較詳盡解析,請參考相關著做或者網絡資料;推薦《C++ Primer,4th edition》和《The C++ Standard Library》。c++

 

(原書第三章:New Language Features)編程

新語言特性數組

  • 用模板做爲模板參數不須要再在結尾">"處留空格:
vector<list<int>> vli; // OK
  • nullptr 代替 NULL;前者的類型是 std::nullptr_t,後者是int。nullptr_t定義在<cstddef>中。
  • 擴展auto關鍵字含義,編譯器自動推導變量類型:
vector<VeryVeryLongLongType> foo; ... auto iter = foo.begin(); // iter是一個迭代器。
auto bar = [&](int)->bool{return ...}; // bar是一個lambda函數。能夠這樣調用:bar(3)。
  • 一致初始化與初始化列表(uniform initialization & initialization lists):
int values[] {1 , 1, 2, 3, 5, 8, 13}; // 初始化列表不能進行變量的不精確轉換int a {1.0};是錯誤的。
std::vector<std::string> vs {"c++", "is", "upgrading"};
std::initializer_list<int> il = {1, 2, 3, 4}; // 用來接受{1, 2, 3, 4}的新類型,initializer_list。
class Foo { ... Foo(std::initializer_list<int>& lst); ...}; ... Foo foo {1, 2, 3, 4};  // 注意:構造函數的參數列表能夠隱式轉換爲initializer_list
  • 基於區間(range-based)循環,它的僞代碼格式是:for(var : container) { statments; }
for (int i : {1, ,1, 2, 3, 5, 8, 13, 21}) // 初始化列表格式
    cout << i << endl;

vector<int> vect; ...
for (auto& item : vect) // 要改變值,要用 auto&, 引用
    item *= 3;
  • move語義和右值引用。支持move語義是C++11最重要的新特性。這個特性主要用來避免沒必要要的拷貝和臨時變量。
    • 右值引用能夠指向匿名對象、臨時對象,但不能是常量,由於它須要能被修改,以支持資源轉移。右值的特性之一是它不能夠取地址。
    • 右值引用的記號是&&,好比 int&& a = 5; 注意,a 雖然是一個右值引用變量,但它自己確是一個左值,由於它能夠取地址。要轉換成右值引用的話,須要用到下面的move函數。
    • move函數在<utility>頭文件中,爲了實現將普通變量轉換爲右值引用。從語義上講,被move過的對象再也不有效。可是move一個相似int,無論理資源,不涉及複雜狀態的對象則毫無影響。其實,move只對定義了move constructor或者move assignment operator的對象真正有做用。move過的對象再也不有效。
    • move constructor 是以 T&&爲參數的T類型的constructor,而move assignment operator是以T&&爲參數的operator=的重載版本。再次提醒,這裏的右值引用必定不會是const,由於move語義要改變右值引用對象的值,將它持有的資源搬移過來。
    • 右值引用配合move語義,能夠直接將資源從臨時對象「剪切」並「粘貼」到目標對象上,提升效率。右值引用用來引出「剪切」和「粘貼」操做,具體的過程,則由move constructor和move assignment operator完成。
    • 左值引用(lvr)和右值引用(rvr)的重載規則:
      • 只實現了void foo(T&) : 不接受rvr,只接受lvr。
      • 只實現了void foo(const T&) : 能夠接收rvr和lvr。
      • 實現了void foo(const T&)和void foo(T&&) : 能夠區分出rvr和lvr兩種調用。若是有move語義,就使用T&&格式;不然使用普通拷貝。
      • 只實現了void foo(T&&) : 智能接受rvr。
    • 返回值的rvr:
      • 返回值不該該使用move函數,編譯器會根據狀況去調用。對於以下的代碼段,如下的行爲是能夠保證的。
        X foo()
        {
          X x; ... return x;    
        }
      • 若是定義了copy constructor或者move copy constructor,首先執行NRV(named return value)優化(參考《Inside the C++ object model》2.3節)。NRV在C++11以前已經被普遍支持,在C++11中把它規範化。
      • 若是定義了move copy constructor,執行move語義。
      • 不然,若是定義了copy constructor,執行copy語義。
      • 不然,編譯時錯誤。
      • 注意!返回值類型不能是rvr,由於rvr始終仍是引用。引用返回的局部變量是沒有意義的。
    • 關於右值引用的深刻理解,可參考另外一篇博文:C++右值引用
  • 新的字符串字面值:
    • Raw String Literal : 不須要轉義的字符串字面值。
    • const char* pFile = R"(C:\Windows\Somepath.file)";// 等價於 "C:\\Windows\\Somepath.file";
      const char* pName = R"nc(Alcohol"(wine)")nc"; // 若是字符串中出現"(或者)",能夠加入分隔符,好比nc。
      const char* pUtf8 = u8"utf8字符串"; // u8表示utf8字符串;
      const char16_t* pChar16t = u"雙字節字符串"; // u表示雙字節字符串;
      const char32_t* pChar32t = U"四字節字符串"; // U表示四字節字符串;
      const wchar_t* pwchart = L"寬字符串"; // L表示寬字符串;
    •  注意,R格式的字面值,能夠用u8,u,U,L修飾。網絡

  • noexcept關鍵字,有兩層含義:
    • 做爲聲明,表示函數不會拋出異常,例: void foo() noexcept; 至關於throw()。聲明時能夠附帶參數。
    • 做爲運算符,返回某個函數是否會拋出異常,例: vector<int> vi; noexcept(vi.at[0]);// true。
  • constexpr關鍵字
    • 表示表達式能夠在編譯時求值,能夠被當作常量處理。例如: std::numeric_limits<short>::max()就是一個被constexpr修飾的表達式。它能夠用來初始化數組的長度了。
  • 模板的新特性
    • 模板參數個數可變(這個特性有點瘋狂): 
      template <typename T, typename ... Types> 
      void print(const T& arg0, const Types&... args)
      {
          std::cout << arg0 <<std::endl; // 打印第一個值。
          print(args...); // 打印剩餘的值。遞歸嗎?
      }

      std::tuple<>大量使用這個特性。閉包

    • 模板別名 : 
    • template<typename T>
      using Vec = std::vector<T, MyAlloc<T>>; // Vec是一個別名模板,它是std::vector<T, MyAlloc<T>>的別名;
      // 模板別名的聲明格式: template<typename T> using alias = ...;
      Vec<int> vec;
    • 其餘特性 : 函數模板能夠有默認參數,局部類型也能夠做爲模板參數等
  • lambda函數(匿名函數),好比 [=, &b](int a)->int{return a + b;},它是實現閉包的關鍵。
    • =, &b叫作capture(捕獲),它用來限定lambda能夠訪問的外部context中非靜態變量的方式。
    • [=, &b] 整個叫作lambda introducer(lambda引導器)。
    • {...}中的內容爲函數體。
    • lambda能夠有參數,mutable修飾符,exception說明,屬性修飾符和返回值類型。全部都是可選的,可是隻要出現一個,那麼小括號()就必定要有。因此,lambda的格式就有兩種:
      • [...]{...}
      • [...](...)mutableopt, throwopt ->returnTypeopt {...} 
    • 若是沒有寫返回值,lambda的返回值根據return的類型來自動推導。
    • capture的格式,[=]表示全部外部變量都是傳值的,外部變量只讀;[&]表示全部外部變量都是傳引用的,對外部變量讀寫;也能夠爲單個外部變量指定訪問模式,好比[=, &a]表示除了a以外,全部其餘變量都是傳值的。
    • lambda表達式均可以轉換爲仿函數。
    • lambda的類型是匿名函數對象,每一個lambda對象的類型都不同。聲明lambda的類型須要模板,或者auto關鍵字,或者decltype關鍵字。此外,也能夠用std::function類模板,指定一種函數式編程(FP)的通用類型。
  • decltype關鍵字
    • decltype(exp) 表示 exp的類型,能夠用在全部須要變量聲明的地方。
    • decltype的典型應用:聲明返回值類型(見下一條);在聲明容器,須要functor類型做爲模板參數時,傳遞lambda的類型。
  • 新的函數聲明語法
    • 當函數的返回值依賴於包含參數的表達式,好比:
      template<typename T1, typename T2>
      auto add(T1 x, T2 y)->decltype(x + y) {...} // 與lambda的聲明相似。add的返回類型,是x + y的類型。可是在C++11以前是不可行的。
  • 做用域受限的枚舉
    • enum class Salution : char{mr, ms, co, none};
    • 也叫作「強枚舉」或者「枚舉類」;
    • 它與int之間的隱式轉換是不可能的;
    • 枚舉值,好比mr,不在Salution聲明的做用域中,Salution::mr纔在;
    • 能夠用": type"指定構成枚舉的數據類型,默認爲int
    • 支持前向聲明;
    • 類型特性std::underling_type能夠返回構成枚舉值的類型。
  • 新的基本數據類型
    • char16_t 和 char32_t;
    • long longunsigned long long;
    • std::nullptr_t
相關文章
相關標籤/搜索