当前位置:   article > 正文

C++11特性详解(万字)

C++11特性详解(万字)

个人主页:Lei宝啊 

愿所有美好如期而遇


统一的初始化列表

{}初始化

这种初始化方式我们建议用第一种,但是以后看见下面两种也不要感到疑惑,是可以这样初始化的。

内置类型初始化
  1. int main()
  2. {
  3. int a = 1;
  4. int b = { 1 };
  5. int c{ 1 };
  6. return 0;
  7. }
自定义类型初始化

我们简单实现一个日期类

  1. class Date
  2. {
  3. public:
  4. Date(){}
  5. Date(int year, int month, int day)
  6. :_year(year)
  7. ,_month(month)
  8. ,_day(day)
  9. {}
  10. private:
  11. size_t _year;
  12. size_t _month;
  13. size_t _day;
  14. };
  15. int main()
  16. {
  17. Date a;
  18. Date b(1, 1, 1);
  19. Date c{ 1,1,1 };
  20. //多参数的隐式类型转换,{1,1,1}构造一个Date类型的临时对象,去拷贝构造d对象。
  21. Date d = { 1,1,1 };
  22. return 0;
  23. }
容器对象的初始化(原理是std::initializer_list)
 以vector为例:
  1. #include <vector>
  2. using namespace std;
  3. int main()
  4. {
  5. //与Date是不同的,这里不是多参数隐式类型转换,
  6. //是C++11特性,添加了initializer_list构造函数
  7. vector<int> v = { 1,2,3,4,5,6 };
  8. /*
  9. 底层是这样的
  10. vector(initializer_list<value_type> il)
  11. {
  12. for(auto &e : il)
  13. {
  14. *finish = e;
  15. finish++;
  16. }
  17. }
  18. */
  19. vector<int> v{ 1,2,3,4,5,6 };
  20. return 0;
  21. }

initializer_list是个模板类,只要我们使用{},像下面那样,那么类型就是initializer_list。 

auto il = { 10, 20, 30 };  // the type of il is an initializer_list

所有容器在C++11都新增了这样的构造函数。 

所以我们可以这样使用:

vector<int> v = { 1,2,3,4,5,6 };

原理就是{1,2,3,4,5,6}先构造一个initializer_list对象,再使用这个对象进行拷贝构造,或者我们可以直接这样:

  1. //直接构造
  2. vector<int> v({ 1,2,3,4,5,6 });
  3. //其实就类似于这样:
  4. //单参数隐式类型转换,先构造再拷贝构造
  5. //string s = "hello world !";
  6. //直接构造
  7. //string s("hello world !");

 那么我们走一遍这个代码的流程:vector<int> v = { 1,2,3,4,5,6 };

首先v需要进行拷贝构造,而构造他的对象的类型应该是initializer_list<int>,所以使用{1,2,3,4,5,6}构造一个Initializer_list<int>的对象,然后对v进行拷贝构造。

在这个拷贝构造中,将initializer_list对象中的元素全部取出挨个赋给vector。

以map为例:
map<string, int> m3 = {{"1",1},{"2",2}};

map元素的类型为pair<string,int>,并且需要进行拷贝构造,这样的一个对象类型为initializer_list<pair<string,int>>,于是需要使用{"1",1},{"2",2}构造一个pair<string,int>类型的对象,我们看pair:


所以构造pair时first_type的类型应该是const char*,而不是string,所以这里还需要多一层构造。

C++11于是新增了这样的构造函数:

我们写代码来理解这个构造函数:

  1. template<T1, T2>
  2. struct pair
  3. {
  4. //C++98
  5. pair(const T& key, const T& value)
  6. :fisrt(key)
  7. ,second(value)
  8. {}
  9. //C++11
  10. template<U, V>
  11. pair(U&& a, V&& b)
  12. :first(a)
  13. ,second(b)
  14. {}
  15. T1 first;
  16. T2 second;
  17. }

之前,C++98是这样的,pair(const string& key, const int& value)

现在,C++11是这样的,pair(const char*&& a, int&& b),于是这里进行了直接构造,省却了一层拷贝构造,节省了资源。

声明

3.1 auto

C++98 auto 是一个存储类型的说明符,表明变量是局部自动存储类型,但是局部域中定义局
部的变量默认就是自动存储类型,所以 auto 就没什么价值了。
C++11 中废弃 auto 原来的用法,将 其用于实现自动类型腿断。这样要求必须进行显示初始化,让编译器将定义对象的类型设置为初 始化值的类型。

3.2 decltype

关键字 decltype 将变量的类型声明为表达式指定的类型。

3.3 nullptr

由于 C++ NULL 被定义成字面量 0 ,这样就可能回带来一些问题,因为 0 既能指针常量,又能表示
整形常量。所以出于清晰和安全的角度考虑, C++11 中新增了 nullptr ,用于表示空指针。
  1. #ifndef NULL
  2. #ifdef __cplusplus
  3. #define NULL   0
  4. #else
  5. #define nullptr   ((void *)0)
  6. #endif
  7. #endif

这样nullptr就表示空指针。

右值引用和移动语义

左值引用、右值引用及移动语义-CSDN博客,这个是博主单独写的(所以也可以看出右值引用的移动语义在C++11中的地位,很重要),这里不再重复去写了。

模板中的万能引用

  1. #include <iostream>
  2. #include <string>
  3. using namespace std;
  4. void Fun(int& x)
  5. {
  6. cout << "左值引用" << endl;
  7. }
  8. void Fun(const int& x)
  9. {
  10. cout << "const 左值引用" << endl;
  11. }
  12. void Fun(int&& x)
  13. {
  14. cout << "右值引用" << endl;
  15. }
  16. void Fun(const int&& x)
  17. {
  18. cout << "const 右值引用" << endl;
  19. }
  20. //万能引用
  21. /*
  22. 模板中T&& 不代表右值引用,而是万能引用
  23. 如果传值为左值,那么就是左值引用
  24. 如果传值为右值,那么就是右值引用
  25. */
  26. template<typename T>
  27. void PerfectForward(T&& t)
  28. {
  29. Fun(t);
  30. }
  31. int main()
  32. {
  33. PerfectForward(10); // 右值
  34. int a;
  35. PerfectForward(a); // 左值
  36. PerfectForward(std::move(a)); // 右值
  37. const int b = 8;
  38. PerfectForward(b); // const 左值
  39. PerfectForward(std::move(b)); // const 右值
  40. return 0;
  41. }

但是为什么全成了左值引用呢?我们看PerfectForward函数,我们传右值过去时,也就是右值引用,而右值引用 引用右值后,右值引用属性变为左值,所以调用的全是左值的函数。

于是我们提到完美转发。

完美转发

std::forward 完美转发在传参的过程中保留对象原生类型属性,也就是说,当右值引用 引用右值后,右值引用属性变为左值,但他引用的对象的类型仍然为右值,只是我们可以通过右值引用操作他,而完美转发将会使得右值引用保持引用对象的类型,即右值属性。(左值引用也是同理)
  1. template<typename T>
  2. void PerfectForward(T&& t)
  3. {
  4. //forward<T>(val) 完美转发
  5. Fun(forward<T>(t));
  6. }

新的类功能

默认成员函数
原来 C++ 类中,有 6 个默认成员函数:
  1. 构造函数
  2. 析构函数
  3. 拷贝构造函数
  4. 拷贝赋值重载
  5. 取地址重载
  6. const 取地址重载
最后重要的是前 4 个,后两个用处不大。默认成员函数就是我们不写编译器会生成一个默认的。
C++11 新增了两个:移动构造函数和移动赋值运算符重载
注意:
  1. 如果你没有实现移动构造,析构函数,拷贝构造,以及拷贝赋值重载中的任意一个,那么编译器会自动生成一个默认移动构造,否则,不生成。
  2. 如果你没有实现移动赋值重载,析构函数,拷贝构造,以及拷贝赋值重载中的任意一个,那么编译器会自动生成一个默认移动赋值重载,否则,不生成。
  3. 如果你提供了移动赋值或移动构造,那么编译器不会提供默认拷贝构造和默认拷贝赋值重载。
  • 如果你提供了某些让编译器不愿意为你主动生成默认函数的函数,那么你可以使用default让他生成默认成员函数。
  • 如果你什么函数也不提供,但是也不愿意让编译器提供默认函数,那么你可以使用delete不让他以及所有人生成。
举例一: 

 ​​​

于是我们使用default:

举例二:

可变模板参数

可变参数模板是模板编程时,模板参数(template parameter)的个数可变的情形。这种模板类型支持一个函数或模板类可以接受任意多个参数,且这些参数可以是任何类型。

可变参数模板是C++ 11新增的最强大的特性之一,它对参数进行高度泛化,能表示0到任意个数、任意类型的参数。

下面就是一个基本可变参数的函数模板
  1. // Args是一个模板参数包,args是一个函数形参参数包
  2. // 声明一个参数包Args...args,这个参数包中可以包含0到任意个模板参数。
  3. template <class ...Args>
  4. void ShowList(Args... args)
  5. {}
上面的参数 args 前面有省略号,所以它就是一个可变模版参数,我们把带省略号的参数称为 参数
,它里面包含了 0 N N>=0 )个模版参数。我们无法直接获取参数包 args 中的每个参数的,
只能通过展开参数包的方式来获取参数包中的每个参数,这是使用可变模版参数的一个主要特
点,也是最大的难点,即如何展开可变模版参数。由于语法不支持使用 args[i] 这样方式获取可变
参数,所以我们的用一些奇招来一一获取参数包的值。
递归函数方式展开函数包
  1. #include <iostream>
  2. #include <string>
  3. using namespace std;
  4. //终止函数
  5. template<class T>
  6. void _ShowList(T value)
  7. {
  8. //...
  9. cout << value << endl;
  10. }
  11. template<class T, class ...Args>
  12. void _ShowList(T value, Args... args)
  13. {
  14. //...
  15. cout << value << " ";
  16. _ShowList(args...);
  17. }
  18. template <class ...Args>
  19. void ShowList(Args... args)
  20. {
  21. _ShowList(args...);
  22. }
  23. int main()
  24. {
  25. ShowList("string", 1, '?');
  26. return 0;
  27. }

lambda表达式

在C++98中,如果我们使用sort进行排序,那么我们可以通过控制传给sort的比较对象来进行不同的排序方式:

即不同的comp对象,他可以是静态成员函数名,也可以是重载了operator()的类的对象。

如果我们要对这样的一组数据按照不同方式进行比较:

  1. struct Goods
  2. {
  3. string _name;  // 名字
  4. double _price; // 价格
  5. int _evaluate; // 评价
  6. Goods(const char* str, double price, int evaluate)
  7. :_name(str)
  8. , _price(price)
  9. , _evaluate(evaluate)
  10. {}
  11. };

那么我们要去写多个不同的仿函数吗?这是不是很麻烦?

所以我们使用lambda表达式就会比较简单,现在我们先介绍lambda表达式:

lambda 表达式书写格式:
[capture-list] (parameters) mutable -> return-type { statement }

   捕捉列表          参数列表      修饰符           返回值          函数体 

参数解释说明:

[capture-list] 捕捉列表该列表总是出现在lambda函数的开始位置,编译器根据[]判断接下来的代码是否为lambda函数捕捉列表能够捕捉上下文中的变量供lambda 函数使用

捕获列表说明
捕捉列表描述了上下文中那些数据可以被 lambda 使用 ,以及 使用的方式传值还是传引用
  • [var]:表示值传递方式捕捉变量var
  • [=]:表示值传递方式捕获所有父作用域中的变量(包括this)
  • [&var]:表示引用传递捕捉变量var
  • [&]:表示引用传递捕捉所有父作用域中的变量(包括this)
  • [this]:表示值传递方式捕捉当前的this指针

(parameters) 参数列表:类似于普通函数的参数列表,相同用法。(若不需要参数传递,可以省略)

mutable:当我们使用捕捉列表以值传递方式进行捕捉,使用mutable取消他的常量性,使其在函数体中可以进行修改。(其实用处不大)

return-type:返回值类型,没有返回值时默认为void,此时可省略返回值类型,当返回值类型明确时,返回值类型也可以省略。

statement:函数体,不可省略,在函数体内,不仅可以使用参数,还可以使用捕获到的变量。

举例使用:

举例一:
  1. #include <iostream>
  2. using namespace std;
  3. int main()
  4. {
  5. int a = 0, b = 1;
  6. // [](int a, int b){return a + b; };
  7. auto add = [](int a, int b) ->int {return a + b; };
  8. cout << add(1, 2) << endl;
  9. return 0;
  10. }
举例二:
  1. int main()
  2. {
  3. int a = 0, b = 1;
  4. auto add = [a, b]() {return a + b; };
  5. cout << add() << endl;
  6. return 0;
  7. }

举例三:
  1. int main()
  2. {
  3. int a = 1, b = 2;
  4. auto add = [](int& a, int& b) {
  5. int temp = a;
  6. a = b;
  7. b = temp;
  8. };
  9. add(a, b);
  10. cout << "a: " << a << endl;
  11. cout << "b: " << b << endl;
  12. return 0;
  13. }

举例四:
  1. int main()
  2. {
  3. int a = 0, int b = 1;
  4. auto add = [a, b]() {
  5. int temp = a;
  6. a = b;
  7. b = temp;
  8. };
  9. return 0;
  10. }

  1. int main()
  2. {
  3. int a = 0, b = 1;
  4. auto add = [a, b]()mutable {
  5. int temp = a;
  6. a = b;
  7. b = temp;
  8. };
  9. add();
  10. cout << "a: " << a << endl;
  11. cout << "b: " << b << endl;
  12. return 0;
  13. }

  1. int main()
  2. {
  3. int a = 0, b = 1;
  4. auto add = [&a, &b](){
  5. int temp = a;
  6. a = b;
  7. b = temp;
  8. };
  9. add();
  10. cout << "a: " << a << endl;
  11. cout << "b: " << b << endl;
  12. return 0;
  13. }

  1. int main()
  2. {
  3. int a = 0, b = 1;
  4. auto add = [&](){
  5. int temp = a;
  6. a = b;
  7. b = temp;
  8. };
  9. add();
  10. cout << "a: " << a << endl;
  11. cout << "b: " << b << endl;
  12. return 0;
  13. }

  1. int main()
  2. {
  3. int a = 0, b = 1;
  4. auto add = [=]()mutable{
  5. int temp = a;
  6. a = b;
  7. b = temp;
  8. };
  9. add();
  10. cout << "a: " << a << endl;
  11. cout << "b: " << b << endl;
  12. return 0;
  13. }

举例五:

混合使用

  1. int main()
  2. {
  3. int a = 0, b = 1, c = 2;
  4. //所有变量以值传递方式捕捉,除了c,c以引用传递方式捕捉
  5. auto add1 = [=, &c]() {return a + b + c; };
  6. //所有变量以引用传递方式捕捉,除了c,c以值传递方式捕捉
  7. auto add2 = [&, c]() {return a + b + c; };
  8. return 0;
  9. }
举例六:
  1. int main()
  2. {
  3. int a = 1, b = 2;
  4. auto print = [] {cout << "hello world !\n"; };
  5. print();
  6. return 0;
  7. }

 lambda表达式的底层其实就是仿函数。

解决排序:
 
  1. #include <vector>
  2. #include <algorithm>
  3. struct Goods
  4. {
  5. string _name; // 名字
  6. double _price; // 价格
  7. int _evaluate; // 评价
  8. Goods(const char* str, double price, int evaluate)
  9. :_name(str)
  10. ,_price(price)
  11. ,_evaluate(evaluate)
  12. {}
  13. };
  14. struct compare
  15. {
  16. bool operator()(const Goods& a, const Goods& b)
  17. {
  18. return a._price > b._price;
  19. }
  20. };
  21. int main()
  22. {
  23. vector<Goods> v = { { "苹果", 2.1, 5 }, { "香蕉", 3, 4 }, { "橙子", 2.2,
  24. 3 }, { "菠萝", 1.5, 4 } };
  25. //sort的compare对象要进行排序的是v的元素,也就是Goods的对象
  26. sort(v.begin(), v.end(), [](const Goods& g1, const Goods& g2) {
  27. return g1._price > g2._price;
  28. });
  29. sort(v.begin(), v.end(), [](const Goods& g1, const Goods& g2) {
  30. return g1._evaluate > g2._evaluate;
  31. });
  32. sort(v.begin(), v.end(), compare());
  33. return 0;
  34. }

 

包装器

function

function 包装器 也叫作适配器。 C++ 中的 function 本质是一个类模板,也是一个包装器
普通函数:
  1. double func(double a)
  2. {
  3. return a / 2;
  4. }
  5. struct Functor
  6. {
  7. double operator()(double d)
  8. {
  9. return d / 3;
  10. }
  11. };
  12. template<class F, class T>
  13. T useF(F f, T x)
  14. {
  15. static int count = 0;
  16. cout << "count:" << ++count << endl;
  17. cout << "count:" << &count << endl;
  18. //f是什么?仿函数?函数名?lambda表达式?
  19. return f(x);
  20. }
  21. int main()
  22. {
  23. /*
  24. 下面这些代码会实例化出三份useF函数
  25. */
  26. // 函数名
  27. cout << useF(func, 11.11) << endl;
  28. // 函数对象
  29. cout << useF(Functor(), 11.11) << endl;
  30. // lamber表达式
  31. cout << useF([](double d){ return d / 4; }, 11.11) << endl;
  32. return 0;
  33. }

 

  1. #include <functional>
  2. double func(double a)
  3. {
  4. return a / 2;
  5. }
  6. struct Functor
  7. {
  8. double operator()(double d)
  9. {
  10. return d / 3;
  11. }
  12. };
  13. template<class F, class T>
  14. T useF(F f, T x)
  15. {
  16. static int count = 0;
  17. cout << "count:" << ++count << endl;
  18. cout << "count:" << &count << endl;
  19. //f是什么?仿函数?函数名?lambda表达式?
  20. return f(x);
  21. }
  22. int main()
  23. {
  24. // 函数名
  25. std::function<double(double)> func1 = func;
  26. cout << useF(func1, 11.11) << endl;
  27. // 函数对象
  28. std::function<double(double)> func2 = Functor();
  29. cout << useF(func2, 11.11) << endl;
  30. // lamber表达式
  31. std::function<double(double)> func3 = [](double d)->double {return d /4;};
  32. cout << useF(func3, 11.11) << endl;
  33. return 0;
  34. }

我们发现function函数只实例化出一份,并且我们上面的这些只是针对于普通函数而言,成员函数我们还没有说过。

成员函数:
  1. #include <functional>
  2. int Plus(int a, int b)
  3. {
  4. return a + b;
  5. }
  6. class Sub
  7. {
  8. public:
  9. int sub(int a, int b)
  10. {
  11. return a - b;
  12. }
  13. };
  14. int main()
  15. {
  16. function<int(Sub, int, int)> f = &Sub::sub;
  17. cout << f(Sub(), 1, 2) << endl;
  18. return 0;
  19. }

注意:静态成员函数包装同普通函数。

bind

std::bind 函数定义在头文件中, 是一个函数模板,它就像一个函数包装器 ( 适配器 ) 接受一个可
调用对象( callable object ),生成一个新的可调用对象来 适应 原对象的参数列表 。一般而
言,我们用它可以把一个原本接收 N 个参数的函数 fn ,通过绑定一些参数,返回一个接收 M 个( M
可以大于 N ,但这么做没什么意义)参数的新函数。同时,使用 std::bind 函数还可以实现参数顺
序调整等操作。
可以将 bind 函数看作是一个通用的函数适配器,它接受一个可调用对象,生成一个新的可调用对
象来 适应 原对象的参数列表。
调用 bind 的一般形式: auto newCallable = bind(callable,arg_list);
其中, newCallable 本身是一个可调用对象, arg_list 是一个逗号分隔的参数列表,对应给定的
callable 的参数。 当我们调用 newCallable 时, newCallable 会调用 callable, 并传给它 arg_list
的参数
arg_list 中的参数可能包含形如 _n 的名字,其中 n 是一个整数,这些参数是 占位符 ,表示
newCallable 的参数,它们占据了传递给 newCallable 的参数的 位置 。数值 n 表示生成的可调用对
象中参数的位置: _1 newCallable 的第一个参数, _2 为第二个参数,以此类推
  1. #include <iostream>
  2. #include <functional>
  3. using namespace std;
  4. int Plus(int a, int b)
  5. {
  6. return a - b;
  7. }
  8. class Sub
  9. {
  10. public:
  11. int sub(int a, int b)
  12. {
  13. return a - b;
  14. }
  15. };
  16. int main()
  17. {
  18. /*
  19. 一般函数
  20. */
  21. function<int(int, int)> f1 = Plus;
  22. //调换参数顺序
  23. auto func = bind(Plus, placeholders::_2, placeholders::_1);
  24. cout << func(3, 4) << endl;
  25. //调整参数个数
  26. auto func1 = bind(Plus, 20, placeholders::_1);
  27. cout << func1(10) << endl;
  28. /*
  29. 成员函数:
  30. 1:静态(同一般)
  31. 2:非静态
  32. */
  33. function<int(Sub, int, int)> f2 = &Sub::sub;
  34. f2(Sub(), 2, 3);
  35. //改变参数数量
  36. auto func2 = bind(&Sub::sub, Sub(), placeholders::_1, placeholders::_2);
  37. function<int(int, int)> f3 = func2;
  38. cout << f3(3, 4) << endl;
  39. cout << func2(3, 4) << endl;
  40. return 0;
  41. }

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/花生_TL007/article/detail/378504?site
推荐阅读
相关标签
  

闽ICP备14008679号