c++11介绍

废话少说

c++ 要比 java 早很多年出来,然而后来却被 java 比下去了,maybe 大家认为,java 更容易学习,实际情况呢,java 的确比 c++ 容易学习呀,有木有!想想 c++ 中的令人头疼的指针(指针数组,数组指针,指针的指针,等等),想想 c++ 中的内存要自己管理 (自己 new 自己 delete),想想 c++ 中的多重继承,再想想 c++ 标准库竟然没有提供文件系统、网络、多线程、GUI 和正则表达式等等这些大家灰常喜欢的工具!下面随便说几个例子:vector<pair<int,string>> v; 就这么简单的声明/定义语句,在原来的 c++ 中竟然都编译不通过,原因就在于 >> 最初是右移运算符!好吧,如果问下,c++ 中对象/变量初始化的方式有多少种,各适用于哪些情况?me 表示鸭梨真的很大,int i = 42 直接字面量赋值?int ii = int() 直接变量赋值? int a[] = {1,2,3,4,5} 大括号赋值?int iii (42) 小括号赋值?再问下 A a(); 这个语句是神马意思?是定义的一个 A 对象 a 吗?难道不是吗?!!!而实际情况是 A a(); 是一个函数声明。同样的诧异会出现在 int a(int()); 上, u 可能会认为它的意思是 int a = int(); !

c++11 的目的

c++ 的第一个标准是 98 年出来的,03 年简单修改(基本没有变化),到了 2011 年,c++ 作了一些比较大的更新,称为 c++11 (之前称为c++0x)。c++ 11 对 c++ 这门语言进行了一些改进,同时增加了一些新的特性和库,MS 用 "Modern c++" 描述之。c++ 11 推出的目的一方面是为了弥补以前的语言缺陷,另一方面也是为了与时俱进,加入时髦的函数式编程、类型推断等等,下面从别处拷来两个 c++11 的 aim :

  1. 强化 c++ 已有的能力,使得其能更好地用于系统编程以及库的构建;
  2. 使得 c++ 更容易 teach、learn 和 use;

新的特性和标准库

c++11 绝对是一门让人有点受不鸟的语言,一个重要原因恐怕在于它要兼容以前的标准,不过呢,使用新的特性和库还是能帮助 me 们更好的更轻松滴写一些程序。其中的有些特性,me 就比较喜欢,比如类型推断。下面先罗列一下,要介绍的东西,算是个导航吧。

  1. 类型推断和表达式求类型
  2. 统一的初始化方式
  3. 域内 for 循环
  4. lambda 表达式/匿名函数
  5. 函数类型
  6. 右值引用/移动语义
  7. hash表(库)
  8. begin/end 函数
  9. 模板中>>的使用
  10. 元组类型(库)
  11. 静态断言 static_assert
  12. 空指针
  13. 原生的字符串
  14. 固定长度整型
  15. 常量表达式 constexpr
  16. 枚举类 enum class
  17. 代理构造
  18. 类内成员初始化
  19. override/final 修饰符
  20. default/delete 拷贝
  21. 正则表达式(库)
  22. 多线程(库)
  23. 类型别名 using 新用法
  24. 可变参数模板以及 sizeof 的新用法
  25. 用户自定义字面量
  26. 属性 attribute
  27. 对齐 alignof

一个 demo

工欲善其事,必先利其器。么有一把利刃,学劈柴也不爽呀!如果编译器不支持 c++11,只纸上谈兵滴学一通,也没啥效果;幸运的是, gcc 4.8.x 和 clang 3.3 已经几乎完全支持 c++11 勒。Windows 下的小盆友们,可以在 SF 处寻找合适自己的二进制版本,如果配合一下 codeblocks 或是其他 IDE,几乎 perfect 了!先上一个 demo,体验一下:

  1. #include <iostream>
  2. #include <vector>
  3. #include <unordered_map>
  4. #include <algorithm>
  5.  
  6. using namespace std;
  7.  
  8. int main(int argc, char *argv[])
  9. {
  10.     vector<int> iv = {1, 2, 3, 4, 5};   // 新的初始化方式
  11.     unordered_map<string, int> people = {{"zhangsan", 12}, {"lisi", 21}, {"wangwu", 18}};  
  12.     // = 是可以忽略的,可以统一使用 {} 来初始化;people 是一个 hash map 结构
  13.  
  14.     for_each(iv.begin(), iv.end(), [](int x){cout << x << " ";});   // 输出 iv,使用了 for_each 算法,第三个参数是一个匿名函数
  15.  
  16.     cout << '\n' ;
  17.  
  18.     for(auto per : people){ // 新的 for 循环,迭代容器中每一个元素,auto 自动推断类型
  19.         cout << per.first << ":" << per.second << "\n";
  20.     }
  21.  
  22.     cout << endl;
  23.  
  24.     return 0;
  25. }
    关于上面程序的几点说明:
  1. 如果使用 gcc 编译的话,g++ 需要加上 -std=c++11 选项;
  2. people 使用新的容器类:unordered_map,是 hash 表(散列表)结构,同时 c++11 还加入了 unordered_set 容器类;
  3. vector 有了新的初始化方式,这种方式同样适合于 unordered_map 容器,也适合其他容器,如 vector、list,和内置数组;后面的 = 可有可无;
  4. 新的 for 循环形式,用来遍历容器,所有的标准容器、内置数组和其他用户自定义容器(只要包含 begin/end 成员函数或是能通过 begin/end 函数操作即可);
  5. 支持匿名函数,也就是 lambda 表达式;
  6. auto 用来作为类型“占位符”,编译器会自动推断类型,比如上面的 people 的元素 per 是 pair 类型;

更多的 demo 可以看 c++程序demo

具体细节

1. 类型推断

先看下使用方法:

// auto
auto x = expression;

// decltype
decltype(E)

大家喜欢类型推断的一个方面是,有时候 c++ 中的类型名太长了(Java 貌似也有这个缺点),比如 vector<pair<string,int>>::const_iterator it = iv.begin();前面那个东西,给人赶脚真不好,现在可以简单滴:auto it = iv.begin(); 勒!下面给出几个推断的例子:

  1. auto i = 3;    // i : int
  2. auto d = 4.5;   // d : double
  3. auto d2 = d;    // d2 : double
  4. auto str = "hello,world";   // str : const char* ! not string !
  5.  
  6. for (auto it = ivector.begin(); it != ivector.end(); ++it)
  7.     cout << *it << " ";
  8.  
  9. decltype(1) x; // x : int
  10. decltype(x) y; // y : int
  11. auto z = y;    // z : int

auto 是类型占位符,对应变量的类型由编译器根据后面的值进行推断,1 就是 int,1.0 就是 double,"" 可不是字符串!而 decltype 则是对一个表达式给出类型来表明一种类型,比如 decltype(1) 就是 int 。auto 主要使用在 me 们知道类型但是“懒得敲写”的时候,也用在泛型中 me 们不大确定类型但是编译器灰常确定的时候;如果 me 们对某处的类型灰常不确定,然后使用 auto 避开的话,赶脚有点得不偿失,可能会更大程度增加程序的不可理解性。下面再多给一些例子:

  1. int foo();
  2. auto x1 = foo(); // x1 : int
  3. const auto& x2 = foo(); // x2 : const int&,const 引用可以绑定到左值或是右值
  4. // auto& x3 = foo(); // x3 : int& 错误,不能绑定一个引用到临时值,非const引用只能绑定到左值而不能绑定到右值
  5.  
  6. float& bar();
  7. auto y1 = bar(); // y1 : float
  8. const auto& y2 = bar(); // y2 : const float&
  9. auto& y3 = bar(); // y3 : float&
  10.  
  11. A* fii();
  12. auto* z1 = fii(); // z1 : A*
  13. auto z2 = fii(); // z2 : A*
  14. // auto* z3 = bar(); // 错误,bar 并不返回指针
  15.  
  16. // 多变量声明和推断
  17. int i;
  18. auto a = 1, *b = &i;    // a : int, b : int*
  19. auto* x = new auto(1);  // x : int*
  20.  
  21. // 神奇的 lambda 表达式
  22. #include <iostream>
  23. using namespace std;
  24.  
  25. int main(int argc, char *argv[])
  26. {
  27.     auto my_lambda = [](){  cout << "hello,world" << '\n';};    // my_lambda : std::function<void()>
  28.     auto x = [](int i)->int { return 42; };  // x : std::function<int(int)>
  29.  
  30.     my_lambda();
  31.     cout << x(0) << endl;
  32.  
  33.     return 0;
  34. }
  35.  
  36.  
  37. // decltype
  38. decltype(1) x; // x : int
  39. decltype(x) y; // y : int
  40. auto z = y;    // z : int
  41. auto x1 = { 1, 2 }; // decltype(x1) is std::initializer_list<int>
  42.  
  43. template<class T, class U>
  44. auto mul(T x, U y) -> decltype(x*y)
  45. {
  46.     return x*y;
  47. }
  48.  
  49. void f(const vector<int>& a, vector<float>& b)
  50. {
  51.     typedef decltype(a[0]*b[0]) Tmp;
  52.     for (int i=0; i<b.size(); ++i) {
  53.         Tmp* p = new Tmp(a[i]*b[i]);
  54.         // ...
  55.     }
  56.     // ...
  57. }
  58.  
  59. struct List {
  60.     struct Link { /* ... */ };
  61.     Link* erase(Link* p);       // remove p and return the link before p
  62.     // ...
  63. };
  64. List::Link* List::erase(Link* p) { /* ... */ }
  65. auto List::erase(Link* p) -> Link* { /* ... */ }

再补充一点关于 decltype 的说法:decltype 是一个运算符,有点像 sizeof (编译的时候就能求值),只不过 decltype 是确定类型而已;其次,auto 常用于变量,decltype 常用于函数;如果 auto 和 decltype 都能使用的话,auto 显得要方便一点,至少短一点。

2. 初始化器以及统一的初始化方式

初始化器在 demo 中已经有所体现,就是 {1,2,3,4,5} 这种玩意的东西! c 中 {} 括起来的东西,一般当做一个块 (block) 来处理,但也偶尔作为数组的初始化方式,c++ 将其提升为所有容器,包括内置数组和所有对象的初始化方式。初始化器的类型是 std::initializer_list<T>。实际上初始化器就是另外一种(标准)容器,用来初始化其他东西,达到初始化方式的统一。这种方式使用起来太方面了,下面给一些例子:

  1. int iarray[] = { 3, 2, 7, 5, 8 };
  2. vector<int> ivector = {1, 2, 3, 4, 5, 6};   // 调用 constructor std::vector<int>(std::initializer_list<int>);
  3. auto p = new vector<double>{1,2,3,4};
  4.  
  5. int f(const vector<double>&);
  6. int x = f({1,2,3,4});    // 曾经这种使用方法是 error !
  7.  
  8. struct S { double a, b; };
  9. S s1{1,2};  // 没有构造函数
  10.  
  11. complex<double> z { 1,2,};  // 有构造函数
  12.  
  13.  
  14. vector<double> v = { 1, 2, 3.456, 99.99 };
  15. list<pair<string,string>> languages = {
  16.     {"Nygaard","Simula"}, {"Richards","BCPL"}, {"Ritchie","C"}
  17. };
  18. map<vector<string>,vector<int>> years = {
  19.     { {"Maurice","Vincent", "Wilkes"},{1913, 1945, 1951, 1967, 2000} },
  20.     { {"Martin", "Ritchards"}, {1982, 2003, 2007} },
  21.     { {"David", "John", "Wheeler"}, {1927, 1947, 1951, 2004} }
  22. };
  23. years.insert({{"Bjarne","Stroustrup"},{1950, 1975, 1985}});
  24.  
  25.  
  26. void f(initializer_list<int>);
  27. f({1,2});
  28. f({23,345,4567,56789});
  29. f({});  // 空
  30. f{1,2}; // 错误,缺少函数调用的 ()
  31.  
  32.  
  33. // 初始化类(结构体)对象/变量
  34. struct Employee{
  35.     int nID;
  36.     int nAge;
  37.     double fWage;
  38. };
  39.  
  40. Employee ant = {1, 42, 60000.0};

初始化器并不会 narrow,也就是 vector<int> vd = {1.5, 2.0, 4.2}; 这里的使用要么是 error,要么就是 warning,决不允许“悄无声息”就将 double 变成 int !其次,关于初始化器和 auto 配合使用,有时候会有“意想不到”(贬义)的效果:

  1. // Don’t mix std::initializer_list with auto
  2. int n;
  3. auto w(n); // int
  4. auto x = n; // int
  5. auto y {n}; // std::initializer_list<int>
  6. auto z = {n}; // std::initializer_list<int>

总之来说,initializer 是个灰常好用的东西,有了统一的初始化方式,实际上,c++ 中那种保留原来 c 的方式和之前的比较怪异的方式,就应该渐渐不再使用了,O__O"…

3. 域内 for 循环

容器元素就是一系列元素,以前 STL 库中有个 for_each 来进行迭代,其实已经很不错了,不过 c++11 中将这种迭代能力直接赋予常用的 for 循环,实际上会更好一点,因为 for_each 相当于把要对元素的处理放置到了一个函数中,而 for 是放置在紧跟着的 {} 块中,应该说更直观些,可能效率也更高一些。然后说,for 的这种迭代适用于内置数组、字符串、初始化器和标准容器,同样适用于用户定义的容器,只要容器可以 x.begin()/x.end() 或是 begin(x)/end(x) 调用即可;

  1. for (auto x: ivector){  // x 只读
  2.     cout << x;
  3. }
  4.  
  5. for (auto& x: ivector){ // x 可以修改
  6.     cout << ++x;
  7. }
  8.  
  9.  
  10. // 对 int 数组进行升序排序,然后输出
  11. #include <iostream>
  12. #include <algorithm>
  13.  
  14. using namespace std;
  15.  
  16. int main()
  17. {
  18.     int a[] =  {1, 10, 2, 3, 8, 6, 4, 7, 5, 9}; // = can be omited
  19.  
  20.     sort(begin(a), end(a)); // sort
  21.  
  22.     for(auto x : a) // output
  23.         cout << x << " ";
  24.        
  25.     cout << endl;
  26.  
  27.     return 0;
  28. }

好用很多,有木有!

4. lambda 表达式/匿名函数

学过函数式编程语言的应该都不陌生——匿名函数 和 lambda表达式。c/c++ 的传统是先定义函数,然后调用函数;当然也可以使用函数指针,让它指向某个函数,然后通过指针调用函数。STL 中有些算法,现在拿排序来说,默认情况下从小到大排序,但是如果需要从大到小排序,me 们就得给出判断标准。所谓的判断标准,就是给一个判断,这个判断可以对两个参数 a、b 给出一个 bool 值,如果 a<b 返回 ture,否则返回 false,这就是标准!可以赶脚出,这个所谓的判断标准,就是一个函数,或是一个可以像函数一样调用的对象(叫函数对象,或是函数子)。但是有的时候,比如为了修改上面的排序为从大到小,去定义一个函数,或是一个类,有点得不偿失,或是有点不够自然,maybe 这个函数只在 sort 中使用了,这样,O__O"…

其次,函数式编程几乎被各种语言引进,Java 中有?C# 中有?F# ? Golang ? JavaScript 就不说勒,应该是资格灰常老的了。下面给出一个跟上面程序类似的程序,只是排序顺序相反而已:

  1. #include <iostream>
  2. #include <algorithm>
  3.  
  4. using namespace std;
  5.  
  6. int main()
  7. {
  8.     int a[] =  {1, 10, 2, 3, 8, 6, 4, 7, 5, 9}; // = can be omited
  9.  
  10.     sort(begin(a), end(a), [](int a, int b)->bool{  return b<a; }); // sort
  11.  
  12.     for(auto x : a) // output
  13.         cout << x << " ";
  14.  
  15.     cout << endl;
  16.  
  17.     return 0;
  18. }

sort 的第三个参数,me 使用了一个匿名函数,或是说 lambda 表达式,使用的 b<a 的标准。如果 a<b 表示递增的话,那么 b<a 自然就是递减咯!来一个统计字符串大写字符个数的程序吧:

  1. #include <iostream>
  2. #include <algorithm>
  3. #include <cctype>
  4. using namespace std;
  5.  
  6. int main(int argc, char *argv[])
  7. {
  8.     char test[] = "Hello,World!";
  9.     int upper = 0;
  10.  
  11.     for_each(test, test+sizeof(test), [&upper](char c){ //     for_each(begin(test), end(test), [&upper](char c){
  12.         if(isupper(c))    ++upper;
  13.     });
  14.     cout << upper << endl;
  15.  
  16.     return 0;
  17. }

匿名函数也就这样了?再多给一些例子:

  1. void test()
  2. {
  3.     int x = 4;
  4.     int y = 5;
  5.    
  6.     [&](){x = 2;y = 2;}();  // x == 2, y == 2
  7.     [=]() mutable{x = 3;y = 5;}();  // x == 2, y == 2
  8.     [=,&x]() mutable{x = 7;y = 9;}();   // x == 7, y == 2
  9. }
  10.  
  11. // 递归 lambda
  12. function<int(int)> f = [&f](int n)
  13. {
  14.     return n <= 1 ? 1 : n * f(n - 1);
  15. };
  16. int x = f(4); //x = 24
  17.  
  18. int a = 42;
  19. count_if(v.begin(), v.end(), [&a](int x){ return x == a;});

匿名函数的格式:

[&ref,=val](int a, int b) -> double{}

&ref 表明是引用函数外的 ref 变量;(有点引用传递的味道)
=val 表明使用函数外 val 的一份值;(有点值传递的味道)
int a, int b 是参数,比较直观;
-> int 表明返回值的类型;

特别的,[&] 表明引用所有函数外的变量;[=] 表明对函数外的变量都使用值的拷贝;貌似这个东西也可以混合用,比如 [&ref, =val],或是 [&ref, =];

匿名函数可以出现在任何函数可以出现的地方,比如算法的判断标准、比如函数调用、比如多线程的 target 函数等。

5. 函数类型

让 c++ 更彻底下吧,让函数也能成为一种类型,让函数也可以作为值进行传递!

  1. // 处理普通函数
  2. #include <iostream>
  3. #include <functional>
  4. using namespace std;
  5.  
  6. int sum(int a, int b) { return a + b; }
  7.  
  8. int main()
  9. {
  10.     function<int (int, int)> fsum = &sum;   //  auto fsum = sum;   // auto fsum = &sum;
  11.     cout << fsum(4,2) << endl;
  12. }
  13.  
  14. // 处理成员函数
  15. #include <iostream>
  16. #include <functional>
  17. using namespace std;
  18.  
  19. struct Foo
  20. {
  21.     void f(int i){  cout << i << '\n';}
  22. };
  23.  
  24. int main()
  25. {
  26.     function<void(Foo&, int)> fmember = mem_fn(&Foo::f);
  27.     Foo foo;
  28.     fmember(foo, 42);
  29.  
  30.     return 0;
  31. }

让成员函数的使用像普通函数一样:

  1. #include <iostream>
  2. #include <functional>
  3. using namespace std;
  4.  
  5. struct Foo
  6. {
  7.     void f(int i, int j){  cout << i << " " << j << '\n';}
  8. };
  9.  
  10. int main()
  11. {
  12.     Foo foo;
  13.     std::function<void(int, int)> fmember = std::bind(&Foo::f, foo, std::placeholders::_1, std::placeholders::_2);
  14.  
  15.     fmember(42, 100);
  16. }

让 bind 来得更猛烈些:

  1. #include <iostream>
  2. #include <functional>
  3. using namespace std;
  4. using namespace std::placeholders;
  5.  
  6. float divide(float a, float b){ return a/b; }
  7.  
  8. int main()
  9. {
  10.     cout << "6/1 = " << divide(6,1) << '\n';
  11.     cout << "6/2 = " << divide(6,2) << '\n';
  12.     cout << "6/3 = " << divide(6,3) << '\n';
  13.     cout << '\n';
  14.  
  15.     function<float(float, float)> inv_divide = bind(divide, _2, _1);
  16.     cout << "1/6 = " << inv_divide(6,1) << '\n';
  17.     cout << "2/6 = " << inv_divide(6,2) << '\n';
  18.     cout << "3/6 = " << inv_divide(6,3) << '\n';
  19.     cout << '\n';
  20.  
  21.     function<float(float)> divide_by_6 = bind(divide, _1, 6);
  22.     cout << "1/6 = " << divide_by_6 (1) << '\n';
  23.     cout << "2/6 = " << divide_by_6 (2) << '\n';
  24.     cout << "3/6 = " << divide_by_6 (3) << '\n';
  25.  
  26.     cout << endl;
  27.  
  28.     return 0;
  29. }

时间有限,具体的分析以后慢慢来。

Tags: 

Article type: 

Comments

sf ……

6. 右值引用和移动语义

这貌似是一个比较大的话,关于引用、常量引用、右值引用,还涉及到临时量、static 量和堆上存储的量。先说左值和右值的问题,大略滴可以这么说:

  1. 能出现在赋值运算符左侧的,左值;只能出现在赋值运算符右侧的,右值;
  2. 能取地址的,是左值;不能取地址的是右值;

然后说说,以前的引用和常量引用:

  1. 非const引用只能绑定到左值;
  2. const引用既可以绑定到左值,也可以绑定到右值;

对于右值,只有 const 引用能绑定到,但是如果使用 const 引用的话,引用的值就么法修改了,于是乎 c++11 又引入了右值引用,当然,可能不是为了解决这个问题,更多的是效率问题,因为,很多时候的临时量对 me 们反而是灰常有用的,为了使用,难道就一定要完全再拷贝一份吗?!!

  1. // (普通的)引用
  2. int a = 3;
  3. int & ra = a;
  4.  
  5. const int & rc = 1;
  6. const int & ra = a;
  7.  
  8. const int & rb = a+1;   // ok
  9.  
  10. // 右值引用:&&
  11. X a;
  12. X f();
  13. X& r1 = a;              // 绑定 r1 到 a (左值)
  14. X& r2 = f();            // 错误,f() 是一个右值,不能绑定
  15.  
  16. X&& rr1 = f();  // 不错:绑定 rr1 到一个临时值
  17. X&& rr2 = a;    // 错误,不能绑定到左值
  18.  
  19. template<class T>
  20. void swap(T& a, T& b)   //几乎“完美交换”
  21. {
  22.     T tmp = move(a);    // could invalidate a
  23.     a = move(b);                // could invalidate b
  24.     b = move(tmp);              // could invalidate tmp
  25. }
  26.  
  27. typedef vector<float> BigObj;
  28. void f(BigObj&&);   // 右值引用,隐含滴说,可以拿引用的临时量开刀
  29. void f(BigObj&);    // 左值引用,是对函数外对象的别名,函数内使用就如同在函数外使用
  30.  
  31. //test1
  32. BigObj x = createBigObject();
  33. f(x);   // BigObj(const BigObj&), x 是一个左值
  34. f(createBigObject());   //BigObj(BigObj&&),函数参数是一个临时量,右值
  35.  
  36. //test2
  37. BigObj x = createBigObject();
  38. f(move(x)); // move makes from input value – rvalue,将左值转换为右值,但是原来的值变不变呢?(这点有点不清楚)
  39.  
  40. //test3
  41. BigObj createBigObject()
  42. {
  43.     BigObj object(100000);
  44.     return object; // BigObj(BigObj&&)
  45. }
  46. BigObj x = createBigObject();  // 移动构造!

关于一些细节,可以去看看引用和右值引用移动构造等文章。

7. hash 结构(散列表)

c++11 新添的 hash 表结构的两种容器类:unordered_map 和 unordered_set,这里其实么有太多说的,就是两种更有效的容易而已,关于 unordered_map,demo 就是一个例子。

8. begin(x)/end(x) 函数

在前面已经多次使用了,比如排序数组。本来所有的容器类都有提供 x.begin() 和 x.end() 函数,而 begin(x)/end(x) 可能让某些代码更简单些,me 们不是什么时候都想写个类,写很多成员函数的!

9. 模板中的<<的使用

在 STL 中连着的两个 < 只要语义合法,语法就是合法的,虽然是一个细节改进,但是让之前的小盆友会方便很多,O__O"…。

10. 元组类型

赶时髦的东西,元组 (tuple) 类型。方便在于,可以简单滴将若干个数据放置到一起,不用去重新声明一个类型,O__O"…如果那多麻烦呀,就跟有些函数只在某一个地方使用一遍就要声明一个函数或是一个类一样!元组中的数据类型不需要一致,比数组灵活,比结构体、类方便。下面是一些使用的例子:

  1. // 元组类型 std::tuple
  2. tuple<string,int> t2("Kylling",123);
  3.  
  4. auto t = make_tuple(string("Herring"),10, 1.23);        // t will be of type tuple<string,int,double>
  5. string s = get<0>(t);
  6. int x = get<1>(t);
  7. double d = get<2>(t);
  8.  
  9. tuple<int,float,string> t(1,2.f,”text”);
  10. int x = get<0>(t);
  11. float y = get<1>(t);
  12. string z = get<2>(t);
  13.  
  14. int myint;
  15. char mychar;
  16. tuple<int,float,char> mytuple;
  17. // packing values into tuple
  18. mytuple = make_tuple (10, 2.6, 'a');
  19. // unpacking tuple into variables
  20. tie(myint, ignore, mychar) = mytuple;
  21.  
  22. int a = 5;
  23. int b = 6;
  24. tie(b, a) = make_tuple(a, b);
  25.  
  26.  
  27. struct Student
  28. {
  29.     string name;
  30.     int classId;
  31.     int numPassedExams;
  32.     bool operator<(const Student& rhs) const{
  33.         return tie(name, classId, numPassedExams) <
  34.             tie(rhs.name, rhs.classId, rhs.numPassedExams);
  35.     }
  36. };
  37. set<Student> students;

应该将 c++11 看成是一个浑然一体的东西,比如上面的元组类型,那么长的名字,有时候不配合 auto 使用,那得多麻烦呀,O__O"…(me 是这么看的。)

精诚所至,金石为开

11. 静态诊断

c 很久之前就提供了一个 assert 函数(准确滴说是一个—— 宏 !),如果表达式的结果为 0,就终止程序。当然定义 NDEBUG 之后就会去掉所有的 assert 宏,为了不在发行版中出现诊断信息。不过以前的 assert 是运行期的,只有到运行的时候,才能发现问题,这里的 static_assert 是编译期的,也就是编译的时候就能发现的问题,当然,用处不一样!

  1. static_assert(sizeof(int) >= 4, "int needs to be 4 bytes to use this code");
  2. static_assert(__cplusplus > 199711L, "Program requires C++11 capable compiler");    // 宏__cplusplus > 199711L 表明 是 C++11

算是小细节一个。

12. 空指针

空指针也算是小细节一个,之前的 NULL 和 0,很容易造成函数调用时的一些歧义或是莫名其妙的东西,就因为 NULL 可能是 0, 0 是一个 int,也是一个空指针,O__O"…,NULL 是作为 int 还是作为 char* 来调用某些重载函数呢?!c++11 引入 nullptr 来解决这个问题。

nullptr 的引入,虽然一定程度上会消除将来的混淆,但是过去的混淆还是解决不了。其次,新东西的添加,总会和现存的发现关系,很多“微妙的”关系,下面给一些例子:

  1. char* p = nullptr;
  2. int* q = nullptr;
  3. char* p2 = 0;           // 0 still works and p==p2
  4.  
  5. void f(int);
  6. void f(char*);
  7.  
  8. f(0);                   // call f(int)
  9. f(nullptr);             // call f(char*)
  10.  
  11. void g(int);
  12. g(nullptr);             // error: nullptr is not an int
  13. int i = nullptr;        // error nullptr is not an int
  14.  
  15. char* ch = nullptr; // ch has the null pointer value
  16. char* ch2 = 0; // ch2 has the null pointer value
  17. int n = nullptr; // error
  18. int n2 = 0; // n2 is zero
  19.  
  20. if( ch == 0 ); // evaluates to true
  21. if( ch == nullptr ); // evaluates to true
  22. if( ch ); // evaluates to false
  23. if( n2 == 0 ); // evaluates to true
  24. if( n2 == nullptr ); // error
  25. if( nullptr ); // error, no conversion to bool
  26. if( nullptr == 0 ); // error
  27. // arithmetic
  28. nullptr = 0; // error, nullptr is not an lvalue
  29. nullptr + 2; // error
  30.  
  31. char* ch3 = expr ? nullptr : nullptr; // ch1 is the null pointer value
  32. char* ch4 = expr ? 0 : nullptr; // error, types are not compatible
  33. int n3 = expr ? nullptr : nullptr; // error, nullptr can’t be converted to int
  34. int n4 = expr ? 0 : nullptr; // error, types are not compatible
  35.  
  36. sizeof( nullptr ); // ok
  37. typeid( nullptr ); // ok
  38. throw nullptr; // ok

苍天呀,谁来救救 me !

13. 原生字符串 (raw string)

通常 c 和 c++ 的字符串,特殊字符要转义,得,现在不用了,O__O"…

  1. string test = R"(C:\A\B\C\D\file1.txt)";
  2. test = R"(First Line.\nSecond line.\nThird Line.\n)";
  3. test =
  4. R"(First Line.
  5. Second line.
  6. Third Line.)";
  7. cout << test << endl;

你要问 me 从哪里来,me 的故乡在 O__O"…

14. 固定长度的整型类型

首先说引入 long long 类型,至少 64 位,shit !至少的意思是,现在可以理解为 64 位,将来可能不是!这里是 64 位,别处可能不是!不好办,于是吧,再引入固定长度的整型:

// 头文件 <cstdint>
int8_t/uint8_t
int16_t/uint16_t
int32_t/uint32_t
int64_t/uint64_t

好吧,不知道平时会不会用,虽然很有用,就是名字长一些,O__O"…

15. 常量表达式

常量表达式,就是编译器就能求值的表达式,下面给例子:

  1. constexpr int Fib(int n)
  2. {
  3.     return n<=2 ? 1 : Fib(n-1)+Fib(n-2);
  4. }
  5. cout << Fib(15);    // 编译时
  6. int a = 15;
  7. cout << Fib(a); // 运行时
  8.  
  9. enum Flags { good=0, fail=1, bad=2, eof=4 };
  10. constexpr int operator|(Flags f1, Flags f2) { return Flags(int(f1)|int(f2)); }
  11.  
  12. void f(Flags x)
  13. {
  14.     switch (x) {
  15.         case bad:         /* ... */ break;
  16.         case eof:         /* ... */ break;
  17.         case bad|eof:     /* ... */ break;
  18.         default:          /* ... */ break;
  19.         }
  20. }
  21.  
  22. constexpr int x1 = bad|eof;     // ok
  23. void f(Flags f3)
  24. {
  25.         constexpr int x2 = bad|f3;      // error: can't evaluate at compile time
  26.         int x3 = bad|f3;                // ok
  27. }
  28.  
  29. // 常量表达式用于对象
  30. struct Point {
  31.         int x,y;
  32.         constexpr Point(int xx, int yy) : x(xx), y(yy) { }
  33. };
  34.  
  35. constexpr Point origo(0,0);
  36. constexpr int z = origo.x;
  37.  
  38. constexpr Point a[] = {Point(0,0), Point(1,1), Point(2,2) };
  39. constexpr int x = a[1].x;       // x becomes 1

肿么说呢,me 不说勒,累勒,O__O"…

精诚所至,金石为开

16. 枚举类

  1. int main(int argc, char argv[])
  2. {
  3.     enum class Color{RED, BLUE};
  4.     enum class Fruit{BANANA, APPLE};
  5.  
  6.     Color a = Color::RED; // note: RED is not accessible any more, we have to use Color::RED
  7.     Fruit b = Fruit::BANANA; // note: BANANA is not accessible any more, we have to use Fruit::BANANA
  8.  
  9.     if (a == b) // compile error here, as the compiler doesn't know how to compare different types Color and Fruit
  10.         cout << "a and b are equal" << endl;
  11.     else
  12.         cout << "a and b are not equal" << endl;
  13.  
  14.     return 0;
  15. }

枚举类要比之前的枚举要好一些,主要就是作用域和名字冲突上。

17. 代理构造函数

  1. class Foo
  2. {
  3. public:
  4.     Foo(){
  5.         // code to do A
  6.     }
  7.     Foo(int nValue): Foo(){ // use Foo() default constructor to do A
  8.         // code to do B
  9.     }
  10. };
  11.  
  12. class A
  13. {
  14.     int a;
  15.     class bad_A {};
  16. public:
  17.     A(int x){
  18.         if (0<x && x<=42) a=x; else throw bad_A();
  19.     }
  20.     A() : A(42){ }
  21.     A(string s) : A(stoi(s)){ } // stoi <string> c++11
  22. };

就是构造函数可以调用其他构造函数而已!注意,这里又多了个 stoi 函数!

18. 类内成员初始化

首先得搞清楚,原来 c++ 内的成员怎么初始化的。maybe 会说,就在构造函数中呗?那 static 的呢? const 的呢? static const 的呢?O__O"…下面给出以前的初始化的一个程序:

  1. #include<iostream>
  2. using namespace std;
  3.  
  4. class Test
  5. {
  6. public:
  7.     Test():cint(5) {a=3; b=4;}
  8.     Test(int in_a) : a(in_a), cint(5) {}
  9.     Test(int in_a, int in_b) : a(in_a), b(in_b), cint(5) {}
  10. public:
  11.     int a;
  12.     int b;
  13.     string h;
  14.     string s;
  15.     const int cint;
  16.     static int sint;
  17.     const static int csint;
  18. };
  19.  
  20. int Test::sint; // must be defined outer class
  21. const int Test::csint = 10;
  22.  
  23. int main()
  24. {
  25.  
  26.     Test test;
  27.     cout << test.a << " " << test.b << " " << test.cint << " " << test.sint << " " << test.csint << " " << test.h << " " << test.s << endl;
  28.  
  29.     return 0;
  30. }

下面给一个 c++11 允许的初始化程序:

  1. #include<iostream>
  2. using namespace std;
  3.  
  4. class Test
  5. {
  6. public:
  7.     Test() {a=3; b=4;}
  8.     Test(int in_a) : a(in_a) {}
  9.     Test(int in_a, int in_b) : a(in_a), b(in_b) {}
  10. public:
  11.     int a = 1;
  12.     int b = 2;
  13.     string h = "hello";
  14.     string s = "world";
  15.     const int cint = 12;
  16.     static int sint;
  17.     const static int csint = 20;
  18. };
  19.  
  20. int Test::sint; // must be defined outer class
  21.  
  22. int main()
  23. {
  24.  
  25.     Test test;
  26.     cout << test.a << " " << test.b << " " << test.cint << " " << test.sint << " "<< test.h << " " << test.s << endl;
  27.  
  28.     return 0;
  29. }

如此繁复的东西,who 能记住这么多?!maybe 有人说,u 记不住,不代表别人记不住丫!好吧,O__O"…

19. override/final修饰符

从名字上应该就能看出来它们是干嘛的:override 表明这是一个重写,fianl 表明自己不能被重写或是被继承。bingo !

  1. #include<iostream>
  2. using namespace std;
  3.  
  4. struct Base
  5. {
  6.     virtual void some_func(float){  cout << "Base::some_fun(float)" << '\n';}
  7. };
  8. struct Derived : Base
  9. {
  10.     virtual void some_func(int){  cout << "Derived::some_fun(int)" << '\n';}
  11.     // void some_func(float){  cout << "Derived::some_fun(float)" << '\n';}
  12. };
  13.  
  14. int main()
  15. {
  16.     Base* pb = new Derived();
  17.  
  18.     pb->some_func(1);
  19.     pb->some_func(1.0);
  20.  
  21.     delete pb;
  22.  
  23.     return 0;
  24. }
  25.  
  26.  
  27.  
  28. struct Base
  29. {
  30.     virtual void some_func(float){  cout << "Base::some_fun(float)" << '\n';}
  31. };
  32. struct Derived : Base
  33. {
  34.     virtual void some_func(int) override{  cout << "Derived::some_fun(int)" << '\n';}   // error!!!
  35.     // void some_func(float){  cout << "Derived::some_fun(float)" << '\n';}
  36. };
  37.  
  38.  
  39. struct Base1 final {};
  40. struct Derived1 : Base1{
  41.     //error
  42. };
  43. struct Base2{
  44.     virtual void f() final;
  45. };
  46. struct Derived2 : Base2 {
  47.     void f(); //error
  48. };

不多说了。

20. default/delete拷贝

暂时也不说,因为 me 已知么明白是咋嚒回事,O__O"…。

精诚所至,金石为开

21. 正则表达式

这是标准库新添的东西,此处略。

22. 多线程

标准库新添的东西,可以看c++并发多线程编程一篇。

精诚所至,金石为开