C++11的重大改变

核心C++11已经有了很大的改变。现在它支持lambda表达式,自动类型推断,统一的初始化语法,委托构造函数,已删除和默认函数声明,nullptr,以及最重要的右值引用——一种预言将会改变创造和处理对象方式的技术。
C++11标准库同样增加了新的算法,新的容器类,原子运算,类型特性,正则表达式,新的智能指针,async()功能以及多线程库。
Lambda 表达式
 

[capture](parameters)->return-type {body}

有多少大写字母。使用for_each()遍历字符数组,下面的 lambda 表达式判断每一个字母是不是大写的。

  1. int main()

  2. {

  3. char s[] = "Hello World!";

  4. int Uppercase = 0; // lambda对其进行修改

  5. for_each(s, s+sizeof(s), [&Uppercase] (char c) {

  6. if (isupper(c))

  7. Uppercase++;

  8. });

  9. cout << Uppercase << " uppercase letters in: " << s << endl;

  10. }

自动类型推断auto和decltype
C++11利用这一地点,允许你以不指定类型的方式声明对象:

  1. auto x = 0; // 因为 0 是 int,所以 x 类型是 int

  2. auto c = 'a'; // char

  3. auto d = 0.5; // double

  4. auto national_debt = 14400000000000LL; //long long

当对象类型太冗长或者是在模板中自动生成时,自动类型推断尤其有用。考虑:

  1. vector<int>::const_iterator ci = vi.begin();

auto ci = vi.begin();

C++11 为捕获对象或表达式的类型提供了类似的机制。新的运算符decltype接收一个表达式并“返回”其类型:

  1. const vector<int> vi;

  2. typedef decltype (vi.begin()) CIT;

  3. CIT another_const_iterator;

统一初始化语法
C++至少有四种不同的初始化方法,有些是重叠的。
带有括号的初始化类似:

  1. std::string s("hello");

  2. int m = int(); // 默认初始化

在某些特殊情况,你也可以使用“=”达到相同目的:

  1. std::string s = "hello";

  2. int x = 5;

对于 POD(Plain Old Data,具有 C 兼容特点)聚合,可以使用大括号:

  1. int arr[4] = {0,1,2,3};

  2. struct tm today = {0};

最后,在构造函数中使用成员初始化器:

  1. struct S

  2. {

  3. int x;

  4. S(): x(0) {}

  5. };

初始化操作的多种变体是令人感觉困扰的重要原因之一,不仅仅对于新手而言。更糟糕的是,在C++03你不能对使用new[]分配空间的POD数组进行数组成员和POD本身的初始化。C++11使用统一的大括号标记清楚了这种混乱:

  1. class C

  2. {

  3. int a;

  4. int b;

  5. public:

  6. C(int i, int j);

  7. };

  8.  
  9. C c {0,0}; // C++11可用,等价于C c(0,0);

  10.  
  11. int* a = new int[3] { 1, 2, 0 }; // C++11可用

  12.  
  13. class X

  14. {

  15. int a[4];

  16. public:

  17. X() : a{1,2,3,4} {} // C++11 可用,成员数组初始化器

  18. };

对于容器,现在可以跟一长串push_back()调用说再见了。在C++11你可以直观地初始化容器:
vector<string> vs = { "first", "second", "third"};

  1. map singers = { {"Lady Gaga", "+1 (212) 555-7890"},

  2. {"Beyonce Knowles", "+1 (212) 555-0987"}};

类似的,C++11支持数据成员的类内初始化:

  1. class C

  2. {

  3. int a = 7; // C++11可用

  4. public:

  5. C();

  6. };

删除和默认函数
具备如下形式的函数成为默认函数(defaulted function):

  1. struct A

  2. {

  3. A() = default; // C++11

  4. virtual ~A() = default; // C++11

  5. };

=default;部分说明,编译器生成该函数的一个默认实现。默认函数有两个优势:比手工实现更有效;把程序员从手动定义这些函数的繁琐中解放出来。
与默认函数相反的是删除函数:

int func() = delete;

删除函数对防止对象复制很有用。回忆一下,C++为每个类自动声明一个拷贝构造函数和赋值运算符。为禁止复制,需要将这两个特殊的成员函数声明为=delete:

  1. struct NoCopy

  2. {

  3. NoCopy & operator =( const NoCopy & ) = delete;

  4. NoCopy ( const NoCopy & ) = delete;

  5. };

  6. NoCopy a;

  7. NoCopy b(a); // 编译错误,拷贝构造函数已经被删除

nullptr
终于,C++有了一个代表空指针常量的关键字。nullptr替换了充满bug的NULL宏,以及被用于空指针好多年的字面常量0。nullptr是强类型的:

  1. void f(int); // #1

  2. void f(char *);// #2

  3. // C++03

  4. f(0); // 调用哪一个 f?

  5. // C++11

  6. f(nullptr) // 无歧义,调用 #2

nullptr适用于所有指针类型,包括函数指针和成员指针:

  1. const char *pc = str.c_str(); // 数据指针

  2. if (pc != nullptr)

  3. cout << pc << endl;

  4. int (A::*pmf)() = nullptr; // 成员函数指针

  5. void (*pmf)() = nullptr; // 函数指针

委托构造函数
在 C++11中,构造函数可以调用同一个类中另外的构造函数:

  1. class M // C++11委托构造函数

  2. {

  3. int x, y;

  4. char *p;

  5. public:

  6. M(int v) : x(v), y(0), p(new char [MAX]) {} // #1 目标

  7. M(): M(0) {cout << "delegating ctor" << endl;} // #2 委托

  8. };

构造函数#2是委托构造函数,调用了目标构造函数#1。
右值引用
C++03的引用类型只能绑定左值。C++11引入了新的引用类型——右值引用。右值引用可以绑定右值,例如临时变量和字面常量。
增加右值引用的主要原因是移动语义。不同于传统的拷贝,移动的含义是目标对象占有源对象的资源,将源对象设置为“空”状态。在这种情景下,拷贝一个对象既昂贵又不必要,应该使用移动操作符。为感受移动语义在性能上的优势,考虑交换字符串。一个原始的实现类似于:

  1. void naiveswap(string &a, string & b)

  2. {

  3. string temp = a;

  4. a = b;

  5. b = temp;

  6. }

这种实现很昂贵。拷贝字符串需要分配内存,将字符从源对象复制到目标对象。相比而言,移动字符串仅仅意味着交换两个数据成员,不需要分配内存、复制字符数组和释放内存:

  1. void moveswapstr(string& empty, string & filled)

  2. {

  3. // 伪代码,体会思想

  4. size_t sz = empty.size();

  5. const char *p = empty.data();

  6. // 移动filled的资源到empty

  7. empty.setsize(filled.size());

  8. empty.setdata(filled.data());

  9. // 移动empty的资源到filled

  10. filled.setsize(sz);

  11. filled.setdata(p);

  12. }

如果你正在实现一个支持移动的类,需要声明一个移动构造函数和移动复制运算符:

  1. class Movable

  2. {

  3. Movable (Movable&&); // 移动构造函数

  4. Movable&& operator=(Movable&&); // 移动复制运算符

  5. };

C++11 标准库大量使用了移动语义。许多算法和容器也为移动语义做了优化。
C++11 标准库
2003年,C++以库技术报告 1(TR1)的形式经历了一次大型重构。TR1包含了新的容器类(unordered_set,unordered_map,unordered_multiset和unordered_multimap)和许多新的库,例如正则表达式,元祖,函数对象包装器。下面是C++11标准库的特性:
线程库
毫无疑问,从程序员角度看,C++11最重要的改进就是并发。C++11有一个thread类,描述一个执行线程、promise和future(用于并发环境下同步的对象),用于发起并发任务的模板函数async()和用于声明线程独立的数据的存储类型thread_local。快速了解C++11线程库,请阅读Anthony Williams的文章Simpler Multithreading in C++0x。
新的智能指针类
C++98只定义了一个智能指针类,auto_ptr,而这个类现在已经被废弃了。C++11包含了新的智能指针类:shared_ptr和最近新加的unique_ptr。
新的算法
C++11标准库定义了模拟集合论操作的新的算法all_of()、any_of()和none_of()。下面几行将谓词ispositive()应用于范围[first, first+n),然后使用all_of()、any_of()和none_of()检测范围的属性:

  1. #include <algorithm>

  2. // C++11 代码

  3. // 所有元素都是正数吗?

  4. all_of(first, first+n, ispositive()); // false

  5. // 至少有一个元素是正数吗?

  6. any_of(first, first+n, ispositive()); // true

  7. // 没有元素是正数?

  8. none_of(first, first+n, ispositive()); // false

还有一个新的copy_n算法。使用copy_n()将一个有 5 个元素的数组复制到另外一个可说是小菜一碟:

  1. #include <algorithm>

  2. int source[5] = { 0, 12, 34, 50, 80 };

  3. int target[5];

  4. // 从源数组到目的数组拷贝 5 个元素

  5. copy_n(source, 5, target);

iota()算法创建一个递增的数字范围,就像首先给*first赋初始值,然后使用++递增。在下面的代码中,iota()将连续数值{10,11,12,13,14}赋值给数组a,将{'a', 'b', 'c'}赋值给字符数组c。

  1. include <numeric>

  2. int a[5] = {0};

  3. char c[3] = {0};

  4. iota(a, a+5, 10); // 将 a 修改为 { 10, 11, 12, 13, 14 }

  5. iota(c, c+3, 'a'); // {'a', 'b', 'c'}

C++11依然缺少一些有用的库,例如 XML API,socket,GUI,反射——当然,还有合理的自动垃圾回收器。

猜你喜欢

转载自blog.csdn.net/f110300641/article/details/83054612
今日推荐