自己一直用的是C++98规范来编程,对于C++11只闻其名却没用过其特性。近期因为工作的需要,需要掌握C++11的一些特性,所以查阅了一些C++11资料。因为自己有C++98的基础,所以从C++98过渡到C++11并不算特别吃力,读了一些书籍后,算是对C++11有了个比较基础的理解,感觉程序员还是要经常保持新语言新特性的更新,现在 C++ 标准都出到C++17了!这篇文章就是对C++11一些常用新特性的一些总结,以C++98C++11在语法上的差异来突出C++11新特性的非凡优势。

一、新语法

1.自动类型推导auto

auto的自动推导,用于从初始化表达式中推断出变量的数据类型。


  1. //C++98
  2. int a = 10;
  3. string s = "abc";
  4. float b = 10.0;
  5. vector<int> c;
  6. vector<vector<int> > d;
  7. map<int, string> m;
  8. m[1] = "aaa";
  9. map<int, string>::iterator it = m.begin();
  10. //C++11
  11. auto a1 = 10; //a1为int
  12. auto s1 = "abc"; //s1为string
  13. auto b1 = b;
  14. auto c1 = c;
  15. auto d1 = d;
  16. auto e1 = 'a';
  17. int* x = &a1;
  18. auto d1 = x;
  19. auto m1 = m.begin();
  20. auto x=1,y=2; //ok
  21. auto i=1.j=3.14; //compile error
  22. double a2 = 3.144;
  23. const auto a3 = a2; //const double
  24. auto a4 = a2; //double
  25. volatile int c2 = 3;
  26. auto c3 = c2; //int

2.萃取类型decltype

decltype可以通过一个变量或表达式得到类型。

  1. #include <iostream>
  2. #include <vector>
  3. using namespace std;
  4. int add(int a)
  5. {
  6. return ++a;
  7. }
  8. void fun(int a)
  9. {
  10. cout << "call function: [int]\n" << endl;
  11. }
  12. void fun(int *a)
  13. {
  14. cout << "call function: [int*]\n" << endl;
  15. }
  16. int main()
  17. {
  18. //C++11
  19. int aa = 10;
  20. decltype(aa) bb = 11;
  21. string ss = "hello intel";
  22. decltype(ss) ss1 = "hello";
  23. const vector<int> vec(1);
  24. decltype(vec[0]) cc = 1;
  25. decltype(0) dd = vec[0]; //dd是int类型
  26. decltype(add(1)) ee; //int
  27. int a[5];
  28. decltype(a) ff; //int[5]
  29. //decltype(fun) gg; 无法通过编译,是个重载函数
  30. return 0;
  31. }

3.nullptr

空指针标识符nullptr是一个表示空指针的标识,他不是一个整数,这是与我们常用的NULL宏的区别。NULL只是一个定义为常整数0的宏,而nullptr是C++11的一个关键字,一个內建的标识符。

  1. #include <iostream>
  2. #include <vector>
  3. using namespace std;
  4. void fun(int a)
  5. {
  6. cout << "call function: [int]\n" << endl;
  7. }
  8. void fun(int *a)
  9. {
  10. cout << "call function: [int*]\n" << endl;
  11. }
  12. int main()
  13. {
  14. //C++11
  15. fun(NULL); //call function: [int]
  16. fun(nullptr); //call function: [int*]
  17. int* p = NULL;
  18. fun(p); //call function: [int*]
  19. return 0;
  20. }

4.区间迭代range for

C++98C++11在使用语法上的差异如下:

  1. #include <iostream>
  2. #include <vector>
  3. using namespace std;
  4. int main()
  5. {
  6. //C++98
  7. vector<int> vec(8, 1);
  8. cout << "C++98 range for:" << endl;
  9. for (vector<int>::iterator it = vec.begin(); it != vec.end(); it++)
  10. {
  11. cout << *it << endl;
  12. }
  13. //C++11
  14. cout << "C++11 range for:" << endl;
  15. for (auto d : vec)
  16. {
  17. cout << d << endl;
  18. }
  19. return 0;
  20. }

值得指出的是,是否能够使用基于范围的for循环,必须依赖一些条件。首先,就是for循环迭代的范围是可确定的。对于类来说,如果该类有begin和end函数,那么for_each之间就是for循环迭代的范围。对于数组而言,就是数组的第一个和最后一个元素间的范围。其次,基于范围的for循环还要求迭代的对象实现+ + 和==等操作符。对于STL中的容器,如string、array、map等使用起来是不会有问题的。下面是C++11操作vector和数组的实践:

  1. #include <iostream>
  2. #include <vector>
  3. using namespace std;
  4. int main()
  5. {
  6. vector<int> vec(8, 1);
  7. //C++11
  8. cout << "C++11 value range for:" << endl;
  9. /*d非引用,修改d不会影响vector里的值*/
  10. for (auto d : vec) //d中存储的是vec中的值
  11. {
  12. d = 2;
  13. }
  14. for (auto d : vec)
  15. {
  16. cout << d << endl;
  17. }
  18. cout << "C++11 reference range for:" << endl;
  19. /*当迭代变量d为引用时,vector里的值可以被修改*/
  20. for (auto &d : vec)
  21. {
  22. d = 2;
  23. }
  24. for (auto d : vec)
  25. {
  26. cout << d << endl;
  27. }
  28. //数组for_each
  29. char arr[] = {'a','b','c','d'};
  30. for (auto &d : arr)
  31. {
  32. d -= 32;
  33. }
  34. for (auto d : arr)
  35. {
  36. cout << d << endl;
  37. }
  38. //遍历二维数组,注意迭代变量row必须为引用。如果你想用 range for 的方法,来遍历更高维的数组 (dim > 2),那么你只需要:除了最内层循环之外,其他所有外层循环都加入 '&' 即可。
  39. int array2[5][5] = {0};
  40. for (auto &row : array2)
  41. for (auto col : row)
  42. cout << col << endl;
  43. return 0;
  44. }

5.返回类型后置语法

先看下面这个例子,编译器在推导decltype(t1+t2)时表达式中t1和t2都未声明,所以编译失败。


  1. #include <iostream>
  2. #include <vector>
  3. using namespace std;
  4. template<class T1, class T2>
  5. decltype(t1 + t2) sum(T1 t1, T2 t2)
  6. {
  7. return t1 + t2;
  8. }
  9. int main()
  10. {
  11. auto total = sum(1, 2);
  12. cout << total << endl;
  13. return 0;
  14. }

所以C++11引入新语法,即把函数的返回值移至参数声明之后,复合符号->decltype(t1+t2)被称为追踪返回类型。而原本的函数返回值由auto占据。

  1. #include <iostream>
  2. #include <vector>
  3. using namespace std;
  4. template<class T1, class T2>
  5. auto sum(T1 t1, T2 t2) ->decltype(t1+t2)
  6. {
  7. return t1 + t2;
  8. }
  9. int main()
  10. {
  11. auto total = sum(1, 2);
  12. cout << total << endl;
  13. return 0;
  14. }

6.final和override

  1. struct B
  2. {
  3. virtual void f1(int) const;
  4. virtual void f2();
  5. void f3();
  6. };
  7. struct D1 : public B
  8. {
  9. void f1(int) const override; //ok
  10. void f2(int) override; //error,B中没有形如f2(int)的函数
  11. void f3() override; //error,f3不是虚函数
  12. void f4() override; //error,B中无f4函数
  13. };
  14. struct D2 : public B
  15. {
  16. void f1(int) const final; //不许后续的其他类覆盖
  17. };
  18. struct D3 :public D2
  19. {
  20. void f2();
  21. void f1(int) const; //error,final函数不可覆盖
  22. };

final还可以用于防止继承的发生

  1. class NoDerived final
  2. {
  3. };
  4. class Bad :NoDerived //NoDerived不可做基类
  5. {
  6. };
  7. class Base
  8. {
  9. };
  10. class Last final :Base
  11. {
  12. };
  13. class Bad2 :Last //Last不可做基类
  14. {
  15. };

7.=default和=delete

对于 C++ 的类,如果程序员没有为其定义特殊成员函数,那么在需要用到某个特殊成员函数的时候,编译器会隐式的自动生成一个默认的特殊成员函数,比如拷贝构造函数,或者拷贝赋值操作符。

C++11允许我们使用=default来要求编译器生成一个默认构造函数,也允许我们使用=delete来告诉编译器不要为我们生成某个默认函数

  1. class B
  2. {
  3. B() = default; //显示声明使用默认构造函数
  4. B(const B&) = delete; //禁止使用类对象之间的拷贝
  5. ~B() = default; //显示声明使用默认析构函数
  6. B& operator=(const B&) = delete; //禁止使用类对象之间的赋值
  7. B(int a);
  8. };

8.lambda表达式

简单来说,Lambda函数也就是一个函数(匿名函数),它的语法定义如下:

  1. [capture](parameters) mutable ->return-type{statement}
  1. [=,&a,&b]表示以引用传递的方式捕捉变量a和b,以值传递方式捕捉其它所有变量;
  2. [&,a,this]表示以值传递的方式捕捉变量a和类的this指针,引用传递方式捕捉其它所有变量。
  1. #include <iostream>
  2. using namespace std;
  3. int main()
  4. {
  5. auto f = []() {cout << "hello world!" << endl; };
  6. f(); //hello world!
  7. int a = 123;
  8. auto f1 = [a] { cout << a << endl; };
  9. f1(); //123
  10. auto f2 = [&a] {cout << a << endl; };
  11. a = 789;
  12. f2(); //789
  13. //隐式捕获:让编译器根据函数体中的代码来推断需要捕获哪些变量
  14. auto f3 = [=] {cout << a << endl; };
  15. f3(); //789
  16. auto f4 = [&] {cout << a << endl; };
  17. a = 990;
  18. f4(); //990
  19. auto f5 = [](int a, int b)->int {return a + b; };
  20. printf("%d\n", f5(1, 2)); //3
  21. return 0;
  22. }

lambda表达式在C++下的应用,排序

  1. #include <stdio.h>
  2. #include <algorithm>
  3. #include <vector>
  4. using namespace std;
  5. void print(char arr[], int len)
  6. {
  7. for (int i = 0; i < len; i++)
  8. {
  9. printf("%d ", arr[i]);
  10. }
  11. printf("\n");
  12. }
  13. bool cmp(char a, char b)
  14. {
  15. if (a > b)
  16. return true;
  17. else
  18. return false;
  19. }
  20. int main()
  21. {
  22. //c++98
  23. char arr1[] = { 2,5,2,1,5,89,36,22,89 };
  24. int len = sizeof(arr1) / sizeof(char);
  25. sort(arr1, arr1 + len, cmp);
  26. print(arr1, len);
  27. //c++11
  28. char arr2[] = { 2,5,2,1,5,89,36,22,89 };
  29. int len2 = sizeof(arr2) / sizeof(char);
  30. sort(arr2, arr2 + len2, [](char a, char b)->bool {return a > b; });
  31. print(arr2, len2);
  32. return 0;
  33. }

9.std::move

std::move是为性能而生,通过std::move,可以避免不必要的拷贝操作。std::move是将对象的状态或者所有权从一个对象转移到另一个对象,只是转移,没有内存的搬迁或者内存拷贝。

  1. #include <iostream>
  2. #include <utility>
  3. #include <vector>
  4. #include <string>
  5. int main()
  6. {
  7. std::string str = "Hello";
  8. std::vector<std::string> v;
  9. //调用常规的拷贝构造函数,新建字符数组,拷贝数据
  10. v.push_back(str);
  11. std::cout << "After copy, str is \"" << str << "\"\n"; //After move, str is "Hello"
  12. //调用移动构造函数,掏空str,掏空后,最好不要使用str
  13. v.push_back(std::move(str));
  14. std::cout << "After move, str is \"" << str << "\"\n"; //After move, str is ""
  15. std::cout << "The contents of the vector are \"" << v[0]
  16. << "\", \"" << v[1] << "\"\n"; //The contents of the vector are "Hello", "Hello"
  17. }

二、STL新内容

1.std::array

  1. 使用 std::array保存在栈内存中,相比堆内存中的 std::vector,我们就能够灵活的访问这里面的元素,从而获得更高的性能;同时正式由于其堆内存存储的特性,有些时候我们还需要自己负责释放这些资源。

  2. 使用std::array能够让代码变得更加现代,且封装了一些操作函数,同时还能够友好的使用标准库中的容器算法等等,比如 std::sort。

std::array 会在编译时创建一个固定大小的数组,std::array 不能够被隐式的转换成指针,使用 std::array 很简单,只需指定其类型和大小即可:

  1. #include <stdio.h>
  2. #include <algorithm>
  3. #include <array>
  4. void foo(int* p)
  5. {
  6. }
  7. int main()
  8. {
  9. std::array<int, 4> arr = {4,3,1,2};
  10. foo(&arr[0]); //OK
  11. foo(arr.data()); //OK
  12. //foo(arr); //wrong
  13. std::sort(arr.begin(), arr.end()); //排序
  14. return 0;
  15. }

2.std::forward_list

std::forward_list 使用单向链表进行实现,提供了 O(1) 复杂度的元素插入,不支持快速随机访问(这也是链表的特点),也是标准库容器中唯一一个不提供 size() 方法的容器。当不需要双向迭代时,具有比 std::list 更高的空间利用率。

  1. #include <stdio.h>
  2. #include <algorithm>
  3. #include <iostream>
  4. #include <string>
  5. #include <forward_list>
  6. int main()
  7. {
  8. std::forward_list<int> list1 = { 1, 2, 3, 4 };
  9. //从前面向foo1容器中添加数据,注意不支持push_back
  10. list1.pop_front(); //删除链表第一个元素
  11. list1.remove(3); //删除链表值为3的节点
  12. list1.push_front(2);
  13. list1.push_front(1);
  14. list1.push_front(14);
  15. list1.push_front(17);
  16. list1.sort();
  17. for (auto &n : list1)
  18. {
  19. if (n == 17)
  20. n = 19;
  21. }
  22. for (const auto &n : list1)
  23. {
  24. std::cout << n << std::endl; //1 2 2 4 14 19
  25. }
  26. return 0;
  27. }

3.std::unordered_map和std::unordered_set

无序容器中的元素是不进行排序的,内部通过 Hash 表实现,插入和搜索元素的平均复杂度为 O(constant),在不关心容器内部元素顺序时,能够获得显著的性能提升。

C++11 引入了两组无序容器:std::unordered_map/std::unordered_multimap 和 std::unordered_set/std::unordered_multiset。

下面给出unordered_map和unordered_set的使用方法。

  1. #include <stdio.h>
  2. #include <algorithm>
  3. #include <iostream>
  4. #include <string>
  5. #include <unordered_map>
  6. #include <unordered_set>
  7. void foo(int* p)
  8. {
  9. }
  10. int main()
  11. {
  12. //unordered_map usage
  13. std::unordered_map<std::string, int> um = { {"2",2},{"1",1},{"3",3} };
  14. //遍历
  15. for (const auto &n : um)
  16. {
  17. std::cout << "key:" << n.first << " value:" << n.second << std::endl;
  18. }
  19. std::cout << "value:" << um["1"] << std::endl;
  20. //unordered_set usage
  21. std::unordered_set<int> us = { 2,3,4,1};
  22. //遍历
  23. for (const auto &n : us)
  24. {
  25. std::cout << "value:" << n << std::endl;
  26. }
  27. std::cout << "value:" << us.count(9) << std::endl; //判断一个数是否在集合内,1存在0不存在
  28. std::cout << "value:" << *us.find(1) << std::endl; //查找一个特定的数是否在集合内,找到就返回该数的迭代器位置
  29. return 0;
  30. }

三、智能指针

1. std::shared_ptr

shared_ptr使用引用计数,每一个shared_ptr的拷贝都指向相同的内存。每使用他一次,内部的引用计数加1,每析构一次,内部的引用计数减1,减为0时,删除所指向的堆内存。shared_ptr内部的引用计数是安全的,但是对象的读取需要加锁。

  1. #include <stdio.h>
  2. #include <memory>
  3. #include <iostream>
  4. int main()
  5. {
  6. //auto ptr = std::make_shared<int>(10);
  7. std::shared_ptr<int> ptr(new int(10));
  8. std::shared_ptr<int> ptrC(ptr);
  9. auto ptr2 = ptr;
  10. {
  11. auto ptr3 = ptr2;
  12. std::cout << "pointer1.use_count() = " << ptr.use_count() << std::endl; //4
  13. std::cout << "pointer2.use_count() = " << ptr2.use_count() << std::endl; //4
  14. }
  15. std::cout << "pointer1.use_count() = " << ptr.use_count() << std::endl; //3
  16. std::cout << "pointer2.use_count() = " << ptr2.use_count() << std::endl; //3
  17. int *p = ptr.get(); //获取原始指针
  18. std::cout << "pointer1.use_count() = " << ptr.use_count() << std::endl; //3
  19. std::cout << "pointer2.use_count() = " << ptr2.use_count() << std::endl; //3
  20. return 0;
  21. }

3 2. std::unique_ptr

std::unique_ptr 是一种独占的智能指针,它禁止其他智能指针与其共享同一个对象,从而保证代码的安全:

  1. #include <stdio.h>
  2. #include <memory>
  3. #include <iostream>
  4. int main()
  5. {
  6. std::unique_ptr<int> ptr(new int(10));
  7. //auto ptr2 = ptr; //非法
  8. //虽说unique_ptr是不可复制的,但我们可以使用std::move将其独占权转移到其他的unique_ptr
  9. auto ptr2(std::move(ptr));
  10. std::cout << *ptr2 << std::endl;
  11. return 0;
  12. }

3. std::weak_ptr

先观察下面的代码,如果我们在类father中使用的是shared_ptr son的话,father和son的实例并没有顺利回收,输出如下:

  1. father !
  2. son !

以上问题就是shared_ptr的环形引用问题。为了避免shared_ptr的环形引用问题,需要引入一个弱引用weak_ptr, weak_ptr是为了配合shared_ptr而引入的一种智能指针,弱引用不会引起引用计数增加,它更像是shared_ptr的一个助手而不是智能指针,因为它不具有普通指针的行为,没有重载operator*和->,它的最大作用在于协助shared_ptr工作,像旁观者那样观测资源的使用情况.

  1. #include <iostream>
  2. #include <memory>
  3. using namespace std;
  4. class father;
  5. class son;
  6. class father {
  7. public:
  8. father() {
  9. cout << "father !" << endl;
  10. }
  11. ~father() {
  12. cout << "~~~~~father !" << endl;
  13. }
  14. void setSon(shared_ptr<son> s) {
  15. son = s;
  16. }
  17. private:
  18. //shared_ptr<son> son;
  19. weak_ptr<son> son; // 用weak_ptr来替换
  20. };
  21. class son {
  22. public:
  23. son() {
  24. cout << "son !" << endl;
  25. }
  26. ~son() {
  27. cout << "~~~~~~son !" << endl;
  28. }
  29. void setFather(shared_ptr<father> f) {
  30. father = f;
  31. }
  32. private:
  33. shared_ptr<father> father;
  34. };
  35. void test() {
  36. shared_ptr<father> f(new father());
  37. shared_ptr<son> s(new son());
  38. f->setSon(s);
  39. s->setFather(f);
  40. }
  41. int main()
  42. {
  43. test();
  44. return 0;
  45. }

输出:

  1. father !
  2. son !
  3. ~~~~~~son !
  4. ~~~~~father !

在C++98基础上学习C++11新特性的更多相关文章

  1. c++学习书籍推荐《深入理解C++11 C++11新特性解析与应用》下载

    百度云及其他网盘下载地址:点我 编辑推荐 <深入理解C++11:C++11新特性解析与应用>编辑推荐:C++标准委员会成员和IBM XL编译器中国开发团队共同撰写,权威性毋庸置疑.系统.深 ...

  2. C++ 11学习和掌握 ——《深入理解C++ 11:C++11新特性解析和应用》读书笔记(一)

    因为偶然的机会,在图书馆看到<深入理解C++ 11:C++11新特性解析和应用>这本书,大致扫下,受益匪浅,就果断借出来,对于其中的部分内容进行详读并亲自编程测试相关代码,也就有了整理写出 ...

  3. 【转】Cocos2d-x 3.1.1 学习日志6--30分钟了解C++11新特性

    [转]Cocos2d-x 3.1.1 学习日志6--30分钟了解C++11新特性 Cocos2d-x 3.1.1 学习日志6--30分钟了解C++11新特性

  4. C++11新特性总结 (一)

    1. 概述 最近在看C++ Primer5 刚好看到一半,总结一下C++11里面确实加了很多新东西,如果没有任何了解,别说自己写了,看别人写的代码估计都会有些吃力.C++ Primer5是学习C++1 ...

  5. 从零开始一起学习SLAM | C++新特性要不要学?

    LAM,C++编程是必备技能.不过,大家在学校里学习的书本一般比较老,主要还是C++98那些老一套. 本文所谓的C++新特性是指C++11及其以后的C++14.C++17增加的新关键字和新语法特性.其 ...

  6. [转载] C++11新特性

    C++11标准发布已有一段时间了, 维基百科上有对C++11新标准的变化和C++11新特性介绍的文章. 我是一名C++程序员,非常想了解一下C++11. 英文版的维基百科看起来非常费劲,而中文版维基百 ...

  7. Java学习之==>Java8 新特性详解

    一.简介 Java 8 已经发布很久了,很多报道表明Java 8 是一次重大的版本升级.Java 8是 Java 自 Java 5(发布于2004年)之后的最重要的版本.这个版本包含语言.编译器.库. ...

  8. C++11新特性——range for

    很多编程语言都有range for语法功能,自C++11起,终于将这个重要功能加入C++标准中.range for语句,可以方便的遍历给定序列中的每个元素并对其执行某种操作. 1.基本语法 for(d ...

  9. C++11新特性总结 (二)

    1. 范围for语句 C++11 引入了一种更为简单的for语句,这种for语句可以很方便的遍历容器或其他序列的所有元素 vector<int> vec = {1,2,3,4,5,6}; ...

随机推荐

  1. Linux Shell脚本攻略学习总结:二

    比较与测试 程序中的流程控制是由比较和测试语句来处理的. 我们可以用if,if else 以及逻辑运算符来执行测试,而用一些比较运算符来比较数据项.另外,有一个test 命令也可以用来进行测试.让我们 ...

  2. ARM Linux内核Input输入子系统浅解

    --以触摸屏驱动为例 第一章.了解linux input子系统         Linux输入设备总类繁杂,常见的包括有按键.键盘.触摸屏.鼠标.摇杆等等,他们本身就是字符设备,而linux内核将这些 ...

  3. 【一天一道LeetCode】#8. String to Integer (atoi)

    一天一道LeetCode系列 (一)题目 Implement atoi to convert a string to an integer. Hint: Carefully consider all ...

  4. How Tomcat Works读书笔记三-------连接器

    几个概念 HttpServlet,Servlet Servlet是一个接口,定义了一种网络服务,我们所有的servlet都要实现它(或它的子类) HttpServlet是一个抽象类,它针对的就是htt ...

  5. leetcode之旅(9)-Reverse Linked List

    题目描述: Reverse a singly linked list. click to show more hints. Hint: A linked list can be reversed ei ...

  6. The 1st tip of DB Query Analyzer

     The 1st tip of DB Query Analyzer               Ma Genfeng   (Guangdong Unitoll Services incorporate ...

  7. Nginx使用图片处理模块

    Nginx可以编写很多额外的模块,这里我们需要按照能够通过URL响应返回缩放且含图片水印功能的模块. 1.安装一些使用过程中会用到的工具 yum install libgd2-devel yum in ...

  8. 理解java值传递与引用传递

    1.基本类型和引用类型在内存中的保存 Java中数据类型分为两大类,基本类型和对象类型.相应的,变量也有两种类型:基本类型和引用类型.基本类型的变量保存原始值,即它代表的值就是数值本身:而引用类型的变 ...

  9. Java内存模型_volatile

    volatile变量自身具有下列两点特性: 可见性:锁的happens-before规则保证释放锁和获取锁的两个线程之间的内存可见性.意味着对一个volatile变量的读,总是能看到(任意线程)对这个 ...

  10. Linux下DB2数据库安装教程

    最近因为工作需要在学习DB2数据库,本教程讲解DB2数据库在inux下的安装步骤. 安装前请查看 DB2版本和许可证 说明来增加了解,先弄明白改安装什么版本,这里我用的是最新的Express-C版本, ...