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
相关文章
相关标签/搜索