【C++】C++11(一)

[复制链接]
发表于 2025-11-11 08:38:33 | 显示全部楼层 |阅读模式

马上注册,结交更多好友,享用更多功能,让你轻松玩转社区。

您需要 登录 才可以下载或查看,没有账号?立即注册

×



C++11部门我将分为两个部门举行报告,本篇文章将要报告同一的列表初始化、声明、范围for、智能指针、STL中的一些变革、右值引用、移动语义以及新的类功能,下一篇文章将要报告可变参数模板、lambda表达式、包装器以及线程库。

一、C++11简介

在2003年C++标准委员会曾经提交了一份技能勘误表(简称TC1),使得C++03这个名字已经取代了C++98称为C++11 之前的最新C++标准名称。不外由于C++03(TC1)告急是对C++98标准中的毛病举行修复,语言的核心部门则没有改动,因此人们风俗性的把两个标准归并称为C++98/03标准。从C++0x到C++11,C++标准10年磨一剑,第二个真正意义上的标准珊珊来迟。相比于C++98/03,C++11则带来了数目可观的变革,此中包罗了约140个新特性,以及对C++03标准中约600个缺陷的修正,这使得C++11更像是从C++98/03中孕育出的一种新语言。相比力而言,C++11能更好地用于体系开辟和库开辟、语法更加泛华和简朴化、更加稳固和安全,不光功能更强盛,而且能提拔步调员的开辟服从,公司实际项目开辟中也用得比力多,以是我们要作为一个重点去学习。C++11增长的语法特性非常篇幅非常多,这里没办法一 一教学,以是本篇文章告急教学实际中比力实用的语法。

二、同一的列表初始化

2.1 {}初始化

在C++98中,标准答应利用花括号{}对数组大概结构体元素举行同一的列表初始值设定。比如:
  1. struct A
  2. {
  3. public:
  4.         A(int a1 , int a2)
  5.                 :_a1(a1)
  6.                 ,_a2(a2)
  7.         {}
  8. private:
  9.         int _a1 = 2;
  10.         int _a2 = 1;
  11. };
  12. int main()
  13. {
  14.         int arr[] = { 1,3,5,7,9 };
  15.         A aa1 = { 2,4 };
  16.         return 0;
  17. }
复制代码
C++11扩大了用大括号括起的列表(初始化列表)的利用范围,使其可用于全部的内置范例和用户自界说的范例,利用初始化列表时,可添加等号(=),也可不添加。
  1. struct A
  2. {
  3. public:
  4.         A(int a1, int a2)
  5.                 :_a1(a1)
  6.                 , _a2(a2)
  7.         {}
  8. private:
  9.         int _a1 = 2;
  10.         int _a2 = 1;
  11. };
  12. int main()
  13. {
  14.         int x = { 10 };
  15.         int y = 10;
  16.        
  17.         int arr[] = { 1,3,5,7,9 };
  18.         A aa1 = { 2,4 };
  19.         // C++11中列表初始化也可以适用于new表达式中
  20.         int* parr = new int[4]{ 0 };
  21.         return 0;
  22. }
复制代码
创建对象时也可以利用列表初始化方式调用构造函数初始化
  1. class Date
  2. {
  3. public:
  4.         Date(int year, int month, int day)
  5.                 :_year(year)
  6.                 , _month(month)
  7.                 , _day(day)
  8.         {
  9.                 cout << "Date(int year, int month, int day)" << endl;
  10.         }
  11. private:
  12.         int _year;
  13.         int _month;
  14.         int _day;
  15. };
  16. int main()
  17. {
  18.         Date d1(2024, 3, 1);
  19.         // C++11支持的列表初始化,这里会调用构造函数初始化
  20.         Date d2{ 2022, 3, 2 };
  21.         Date d3 = { 2022, 3, 3 };
  22.         return 0;
  23. }
复制代码

2.2 std::initializer_list

std::initializer_list的先容文档
std::initializer_list是什么范例:
  1. int main()
  2. {
  3.         auto il = { 10, 20, 30 };
  4.         cout << typeid(il).name() << endl;
  5.         return 0;
  6. }
复制代码

std::initializer_list利用场景:
std::initializer_list一样寻常是作为构造函数的参数,C++11对STL中的不少容器就增长std::initializer_list作为参数的构造函数,如许初始化容器对象就更方便了。也可以作为operator=的参数,如许就可以用大括号赋值。
下面是C++标准库中的能利用initializer_list作为参数调用operator=的部门容器。
https://legacy.cplusplus.com/reference/vector/vector/operator=/
https://legacy.cplusplus.com/reference/list/list/operator=/
https://legacy.cplusplus.com/reference/set/set/operator=/
https://legacy.cplusplus.com/reference/map/map/operator=/

  1. int main()
  2. {
  3.         vector<int> v;
  4.         // 使用vector调用initializer_list为参数的operator=函数
  5.         v = { 2024,3,1 };
  6.         // 使用list调用initializer_list为参数的operator=函数
  7.         list<pair<string, string>> l;
  8.         l = { {"love","爱"},{"want","想要"} };
  9.         return 0;
  10. }
复制代码

模仿实现的vector也支持{}初始化和赋值
  1. #pragma once
  2. #include <iostream>
  3. #include <assert.h>
  4. using namespace std;
  5. namespace aj
  6. {
  7.     template<class T>
  8.     class vector
  9.     {
  10.     public:
  11.         vector(initializer_list<T> l)
  12.         {
  13.             _start = new T[l.size()];
  14.             _finish = _start + l.size();
  15.             _endOfStorage = _start + l.size();
  16.             size_t i = 0;
  17.             for (auto ch : l)
  18.             {
  19.                 *(_start + i) = ch;
  20.                 i++;
  21.             }
  22.         }
  23.         vector<T>& operator= (initializer_list<T>& l)
  24.         {
  25.             _start = new T[l.capacity()];
  26.             _finish = _start + l.size();
  27.             _endOfStorage = _start + l.capacity();
  28.             for (size_t i = 0; i < l.size(); i++)
  29.             {
  30.                 *(_start + i) = l[i];
  31.             }
  32.             return *this;
  33.         }
  34.     private:
  35.         iterator _start = nullptr; // 指向数据块的开始
  36.         iterator _finish = nullptr; // 指向有效数据的尾
  37.         iterator _endOfStorage = nullptr; // 指向存储容量的尾
  38.     };
  39. }
复制代码

三、声明

c++11提供了多种简化声明的方式,尤其是在利用模板时。
3.1 auto

在C++98中auto是一个存储范例的分析符,表明变量是局部自动存储范例,但是局部域中界说局部的变量默认就是自动存储范例,以是auto就没什么代价了。C++11中废弃auto原来的用法,将其用于实现自动范例推断。如许要求必须举行体现初始化,让编译器将界说对象的范例设置为初始化值的范例。
  1. namespace aj
  2. {
  3.         void swap(int& x, int& y)
  4.         {
  5.                 int tmp = x;
  6.                 x = y;
  7.                 y = tmp;
  8.         }
  9. }
  10. int main()
  11. {
  12.         int x = 0;
  13.         auto pa = &x;
  14.         auto ps = aj::swap;
  15.         cout << typeid(pa).name() << endl << endl;
  16.         cout << typeid(ps).name() << endl << endl;
  17.         map<string, string> m({ {"love","爱"},{"want","想要"} });
  18.         auto it = m.begin();
  19.         cout << typeid(it).name() << endl;
  20.         return 0;
  21. }
复制代码

3.2 decltype

关键字decltype将变量的范例声明为表达式指定的范例。
  1. int main()
  2. {
  3.         int x = 2;
  4.         double y = 2.2;
  5.         // 如果这里我们想将 x 与 y乘积类型作为容器的模版参数
  6.         // 会发现,auto不能完成这一任务
  7.         // 那么这里使用 decltype
  8.         vector<decltype(x* y)> v;
  9.         cout << typeid(decltype(x * y)).name() << endl;
  10.         return 0;
  11. }
复制代码

3.3 nullptr

由于C++中NULL被界说成字面量0,如许就大概回带来一些题目,由于0既能指针常量,又能表现整形常量。以是出于清晰和安全的角度思量,C++11中新增了nullptr,用于表现空指针。
  1. #ifndef NULL
  2. #ifdef __cplusplus
  3. #define NULL 0
  4. #else
  5. #define NULL ((void *)0)
  6. #endif
  7. #endif
复制代码

四、范围for

范围for在【C++】C++入门这篇文章中的第九点中有详细教学,那么这里不再做教学,不太认识的可以跳转到这篇文章看一下。

五、智能指针

由于智能指针的内容的篇幅不少,加在这里会使这篇文章的内容太长,那么智能指针的内容会在背面的文章报告到。

六、STL中一些变革


  • 新容器
    下面圈起来是C++11中的一些几个新容器,但是实际最有用的是unordered_map和unordered_set。这两个我们前面已经举行了非常详细的教学,其他的各人相识一下即可。

  • 新的构造: initializer_list的构造
    关于initializer_list的内容已经在上面第二点中报告到了。
  • 移动构造和移动赋值
    移动构造和移动赋值的相干内容在下面第八点中讲到。
  • 右值引用的引入
    右值引用的相干内容在下面第七点中讲到。
  • 容器中的一些新方法
    比如提供了cbegin和cend方法返回const迭代器等等,但是实际意义不大,由于begin和end也是可以返回const迭代器的,而且各人也已经风俗了利用begin和end,以是cbegin和cend的出实际践意义不大。

七、右值引用和移动语义

7.1 左值引用和右值引用

而C++11中新增了的右值引用语法特性,之前学习的引用叫做左值引用。无论左值引用还是右值引用,都是给对象取别名。
什么是左值?什么是左值引用?
左值是一个表现数据的表达式(如变量名或解引用的指针),**我们可以获取它的所在+可以对它赋值,左值可以出现赋值符号的左边,右值不能出现在赋值符号左边。**界说时const修饰符后的左值,不能给他赋值,但是可以取它的所在。左值引用就是给左值的引用,给左值取别名。
  1. int main()
  2. {
  3.         // 左值
  4.         int x = 0;
  5.         int* p = new int(x);
  6.         const int y = 1;
  7.         // 左值引用
  8.         int& rx = x;
  9.         int*& rp = p;
  10.         const int& ry = y;
  11.         return 0;
  12. }
复制代码
什么是右值?什么是右值引用?
右值也是一个表现数据的表达式,如:字面常量、表达式返回值,函数返回值(这个不能是左值引用返回)等等,右值可以出现在赋值符号的右边,但是不能出现出现在赋值符号的左边,右值不能取所在。右值引用就是对右值的引用,给右值取别名。
  1. int add(int x, int y)
  2. {
  3.         return x + y;
  4. }
  5. int main()
  6. {
  7.         // 右值有下面几种情况
  8.        
  9.         // 常数
  10.         int&& x = 0;
  11.         int m = 1, n = 2;
  12.         // 表达式返回值
  13.         int&& y = m + n;
  14.         // 函数的返回值
  15.         int&& z = add(m , n);
  16.         return 0;
  17. }
复制代码
总结:左值引用和右值引用的区别就是能不能取所在,左值引用的对象可以取所在,而右值引用的对象不可以取所在。

7.2 左值引用与右值引用比力

左值引用总结:

  • 左值引用只能引用左值,不能引用右值。
  • 但是const左值引用既可引用左值,也可引用右值。
  1. int main()
  2. {
  3.         int x = 0;
  4.         const int y = 1;
  5.         // 左值引用左值
  6.         int& ra = x;
  7.         // 左值引用右值报错
  8.         // int& ry = y;
  9.        
  10.         // const左值引用左值
  11.         const int& ry2 = x;
  12.         // const左值引用右值
  13.         const int& rsum = x + y;
  14.         return 0;
  15. }
复制代码
右值引用总结:

  • 右值引用只能右值,不能引用左值。
  • 但是右值引用可以move以后的左值。
move函数文档先容
move函数可以或许将对象的属性从左值转化为右值返回。
  1. int main()
  2. {
  3.         int x = 0;
  4.         const int y = 1;
  5.         // 右值引用右值
  6.         int&& ry = 520;
  7.         // 右值引用左值报错
  8.         // 无法将右值引用绑定到左值
  9.         // C2440 初始化 : 无法从int转换为int &&
  10.         // int&& rx = x;
  11.         // 右值引用move后的左值
  12.         int&& rx = move(x);
  13.         // const右值引用左值报错
  14.         // const int&& rx = x;
  15.         // const左值引用右值
  16.         const int&& rsum = x + y;
  17.         return 0;
  18. }
复制代码

7.3 右值引用利用场景和意义

前面我们可以看到左值引用既可以引用左值和又可以引用右值,那为什么C++11还要提出右值引用呢?是不是化蛇添足呢?下面我们来看看左值引用的短板,右值引用是怎样补齐这个短板的!
  1. #pragma once
  2. #include <iostream>
  3. #include <assert.h>
  4. using namespace std;
  5. #include <string>
  6. namespace aj
  7. {
  8.     class string
  9.     {
  10.     public:
  11.         typedef char* iterator;
  12.     public:
  13.         // 这里缺省值给""的原因是空字符串本身就带一个'\0'
  14.         // 而不是初始化的时候_str为nullptr
  15.         // 初始化列表的顺序应该与声明相同
  16.         string(const char* str = "")
  17.             :_capacity(strlen(str))
  18.             , _size(_capacity)
  19.         {
  20.             // 多开一个空间用来存放'\0'
  21.             _str = new char[_capacity + 1];
  22.             strcpy(_str, str);
  23.         }
  24.         // 拷贝构造函数现代写法
  25.         string(const string& s)
  26.             :_str(nullptr)
  27.             , _capacity(0)
  28.             ,_size(0)
  29.         {
  30.             cout << "string(const string& s)---深拷贝" << endl;
  31.             string tmp(s._str);
  32.             swap(tmp);
  33.         }
  34.         // 移动构造
  35.         string(string&& s)
  36.             :_str(nullptr)
  37.             , _capacity(0)
  38.             , _size(0)
  39.         {
  40.             cout << "string(string&& s)---移动语义" << endl;
  41.             swap(s);
  42.         }
  43.         string& operator=(string tmp)
  44.         {
  45.             cout << "string& operator=(string tmp)---深拷贝" << endl;
  46.             swap(tmp);
  47.             return *this;
  48.         }
  49.         // 移动赋值
  50.         string& operator=(string&& tmp)
  51.         {
  52.             cout << "string& operator=(string&& tmp)---移动语义" << endl;
  53.             swap(tmp);
  54.             return *this;
  55.         }
  56.         ~string()
  57.         {
  58.             delete[] _str;
  59.             _str = nullptr;
  60.             _size = _capacity = 0;
  61.         }
  62.         iterator begin()
  63.         {
  64.             return _str;
  65.         }
  66.         iterator end()
  67.         {
  68.             return _str + _size;
  69.         }
  70.         void push_back(char c)
  71.         {
  72.             if (_size + 1 > _capacity)
  73.             {
  74.                 // 这里不能盲目的开二倍,因为string可能是空字符串,
  75.                 // _capacity = 0 , 那么这里的二倍就没有意义,继续下面的操作会报错
  76.                 reserve(_capacity == 0 ? 4 : 2 * _capacity);
  77.             }
  78.             _str[_size] = c;
  79.             _size++;
  80.             _str[_size] = '\0';
  81.         }
  82.         string& operator+=(char c)
  83.         {
  84.             push_back(c);
  85.             return *this;
  86.         }
  87.         void append(const char* str)
  88.         {
  89.             int len = strlen(str);
  90.             if (_size + len > _capacity)
  91.             {
  92.                 reserve(_size + len);
  93.             }
  94.             strcpy(_str + _size, str);
  95.             _size += len;
  96.         }
  97.         string& operator+=(const char* str)
  98.         {
  99.             append(str);
  100.             return *this;
  101.         }
  102.         void clear()
  103.         {
  104.             _str[0] = '\0';
  105.             _size = 0;
  106.         }
  107.         void swap(string& s)
  108.         {
  109.             std::swap(_str, s._str);
  110.             std::swap(_capacity, s._capacity);
  111.             std::swap(_size, s._size);
  112.         }
  113.         const char* c_str()const
  114.         {
  115.             return _str;
  116.         }
  117.         size_t size()const
  118.         {
  119.             return _size;
  120.         }
  121.         size_t capacity() const
  122.         {
  123.             return _capacity;
  124.         }
  125.         bool empty()const
  126.         {
  127.             return _size == 0;
  128.         }
  129.         void resize(size_t n, char c = '\0')
  130.         {
  131.             if (n <= _size)
  132.             {
  133.                 _str[n] = '\0';
  134.                 _size = n;
  135.             }
  136.             else
  137.             {
  138.                 reserve(n);
  139.                 _capacity = n;
  140.                 while (_size < n)
  141.                 {
  142.                     _str[_size] = c;
  143.                     _size++;
  144.                 }
  145.                 // 到这里 _size = n
  146.                 _str[_size] = '\0';
  147.             }
  148.         }
  149.         void reserve(size_t n)
  150.         {
  151.             if (n > _capacity)
  152.             {
  153.                 // _capacity 记录的是需要存储有效数据的个数
  154.                 // 所以我们这里要多开一个空间用来记录'\0'
  155.                 char* tmp = new char[n + 1];
  156.                 strcpy(tmp, _str);
  157.                 delete[] _str;
  158.                 _str = tmp;
  159.                 _capacity = n;
  160.             }
  161.         }
  162.     private:
  163.         char* _str;
  164.         size_t _capacity;
  165.         size_t _size;
  166.         const static size_t npos;
  167.     };
  168.     const size_t string::npos = -1;
  169. };
复制代码
左值引用的利用场景:做参数和做返回值都可以进步服从。
  1. void func1(aj::string s)
  2. {}
  3. void func2(const aj::string& s)
  4. {}
  5. int main()
  6. {
  7.         aj::string s1("hello world");
  8.         // func1和func2的调用我们可以看到左值引用做参数减少了拷贝,提高效率的使用场景和价值
  9.         func1(s1);
  10.         func2(s1);
  11.         // string operator+=(char ch) 传值返回存在深拷贝
  12.         // string& operator+=(char ch) 传左值引用没有拷贝提高了效率
  13.         s1 += '!';
  14.         return 0;
  15. }
复制代码
左值引用的短板:
但是当函数返回对象是一个局部变量,出了函数作用域就不存在了,就不能利用左值引用返回,只能传值返回。比方:aj::string to_string(int value)函数中可以看到,这里只能利用传值返回,传值返回会导致至少1次拷贝构造(假如是一些旧一点的编译器大概是两次拷贝构造)。

  1. namespace aj
  2. {
  3.         aj::string to_string(int value)
  4.         {
  5.                 bool flag = true;
  6.                 if (value < 0)
  7.                 {
  8.                         flag = false;
  9.                         value = 0 - value;
  10.                 }
  11.                 aj::string ret;
  12.                 while (value > 0)
  13.                 {
  14.                         int x = value % 10;
  15.                         value /= 10;
  16.                         ret+= ('0' + x);
  17.                 }
  18.                 if (flag == false)
  19.                 {
  20.                         ret+= '-';
  21.                 }
  22.                 aj::reverse(ret.begin(), ret.end());
  23.                 return ret;
  24.         }
  25. }
  26. int main()
  27. {
  28.         // 在aj::string to_string(int num)函数中可以看到,这里
  29.         // 只能使用传值返回,传值返回会导致至少1次拷贝构造
  30.         // (如果是一些旧一点的编译器可能是两次拷贝构造)。
  31.         aj::string ret1 = aj::to_string(1234);
  32.         aj::string ret2 = aj::to_string(-1234);
  33.         return 0;
  34. }
复制代码
  1. namespace aj
  2. {
  3.         string(const string& s)
  4.                 :_str(nullptr)
  5.                 , _capacity(0)
  6.                 , _size(0)
  7.         {
  8.                 cout << "string(const string& s)---深拷贝" << endl;
  9.                 string tmp(s._str);
  10.                 swap(tmp);
  11.         }
  12.         string to_string(int num)
  13.         {
  14.                 string ret;
  15.                 // ...
  16.                 return ret;
  17.         }
  18. }
复制代码

右值引用和移动语义办理上述题目:
在aj::string中增长移动构造,移动构造本质是将参数右值的资源盗取过来,占位已有,那么就不消做深拷贝了,以是它叫做移动构造,就是盗取别人的资源来构造本身。
  1. #pragma once
  2. #include <iostream>
  3. using namespace std;
  4. #include <string>
  5. namespace aj
  6. {
  7.     class string
  8.     {  
  9.         // 移动构造
  10.         string(string&& s)
  11.             :_str(nullptr)
  12.             , _capacity(0)
  13.             , _size(0)
  14.         {
  15.             cout << "string(string&& s)---移动语义" << endl;
  16.             swap(s);
  17.         }
  18.     private:
  19.         char* _str;
  20.         size_t _capacity;
  21.         size_t _size;
  22.         const static size_t npos;
  23.     };
  24.     const size_t string::npos = -1;
  25.     aj::string to_string(int num)
  26.     {
  27.         aj::string ret;
  28.         // ...
  29.         return ret;
  30.     }
  31. };
复制代码

不光仅有移动构造,尚有移动赋值:
在aj::string类中增长移动赋值函数,再去调用aj::to_string(1234),不外这次是将aj::to_string(1234)返回的右值对象赋值给ret对象,这时调用的是移动构造。

这里运行后,我们看到调用了一次移动构造和一次移动赋值。由于假如是用一个已经存在的对象吸取,编译器就没办法优化了。aj::to_string函数中会先用str天生构造天生一个临时对象,但是我们可以看到,编译器很聪明的在这里把str辨认成了右值,调用了移动构造。然后在把这个临时对象做为aj::to_string函数调用的返回值赋值给ret,这里调用的移动赋值。
STL中的容器都是增长了移动构造和移动赋值:
https://legacy.cplusplus.com/reference/vector/vector/
https://legacy.cplusplus.com/reference/list/list/
https://legacy.cplusplus.com/reference/set/set/
https://legacy.cplusplus.com/reference/map/map/

7.4 右值引用引用左值及其一些更深入的利用场景分析

按照语法,右值引用只能引用右值,但右值引用肯定不能引用左值吗?由于:有些场景下,大概真的须要用右值去引用左值实现移动语义。当须要用右值引用引用一个左值时,可以通过move函数将左值转化为右值。C++11中,std::move()函数位于 头文件中,该函数名字具有疑惑性,它并不搬移任何东西,唯一的功能就是将一个左值逼迫转化为右值引用,然后实现移动语义。
  1. // move底层的实现
  2. template<class _Ty>
  3. inline typename remove_reference<_Ty>::type&& move(_Ty&& _Arg) _NOEXCEPT
  4. {
  5. // forward _Arg as movable
  6. return ((typename remove_reference<_Ty>::type&&)_Arg);
  7. }
复制代码
  1. int main()
  2. {
  3.         aj::string s1("hello world");
  4.         // 这里s1是左值,调用的是拷贝构造
  5.         aj::string s2(s1);
  6.         // 这里我们把s1 move处理以后, 会被当成右值,调用移动构造
  7.         // 但是这里要注意,一般是不要这样用的,因为我们会发现s1的
  8.         // 资源被转移给了s3,s1被置空了。
  9.         aj::string s3(std::move(s1));
  10.         cout << "s1: " << s1 << endl;
  11.         cout << "s2: " << s2 << endl;
  12.         cout << "s3: " << s3 << endl;
  13.         return 0;
  14. }
复制代码

STL容器插入接口函数也增长了右值引用版本:
https://legacy.cplusplus.com/reference/list/list/push_back/
  1. // list --> void push_back(value_type&& val);
  2. int main()
  3. {
  4.         list<aj::string> lt;
  5.         aj::string s1("1111");
  6.         // 这里调用的是拷贝构造
  7.         lt.push_back(s1);
  8.         // 下面调用都是移动构造
  9.         lt.push_back("2222");
  10.         lt.push_back(std::move(s1));
  11.         return 0;
  12. }
复制代码


7.5 美满转发

模板中的&& 全能引用
若对象的属性为右值被右值引用后,对象的属性会退化为左值。
  1. void Fun(int& x) { cout << "左值引用" << endl; }
  2. void Fun(const int& x) { cout << "const 左值引用" << endl; }
  3. void Fun(int&& x) { cout << "右值引用" << endl; }
  4. void Fun(const int&& x) { cout << "const 右值引用" << endl; }
  5. // 模板中的&&不代表右值引用,而是万能引用,其既能接收左值又能接收右值。
  6. // 模板的万能引用只是提供了能够接收同时接收左值引用和右值引用的能力,
  7. // 但是引用类型的唯一作用就是限制了接收的类型,后续使用中都退化成了左值,
  8. // 我们希望能够在传递过程中保持它的左值或者右值的属性, 就需要用我们下面学习的完美转发
  9. template<typename T>
  10. void PerfectForward(T&& t)
  11. {
  12.         Fun(t);
  13. }
  14. int main()
  15. {
  16.         PerfectForward(10); // 右值
  17.         int a;
  18.         PerfectForward(a); // 左值
  19.         PerfectForward(std::move(a)); // 右值
  20.         const int b = 8;
  21.         PerfectForward(b); // const 左值
  22.         PerfectForward(std::move(b)); // const 右值
  23.         return 0;
  24. }
复制代码

std::forward 美满转发在传参的过程中生存对象原生范例属性

美满转发实际中的利用场景:
  1. namespace aj
  2. {
  3.     // List的节点类
  4.     template<class T>
  5.     struct ListNode
  6.     {
  7.         ListNode(const T& val = T())
  8.             :_val(val)
  9.         {}
  10.         ListNode<T>* _prev = nullptr;
  11.         ListNode<T>* _next = nullptr;
  12.         T _val;
  13.     };
  14.     //list类
  15.     template<class T>
  16.     class list
  17.     {
  18.     public:
  19.         typedef ListNode<T> Node;
  20.         typedef Node* PNode;
  21.     public:
  22.         // typedef ListIterator<T> iterator;
  23.         typedef ListIterator<T, T&, T*> iterator;
  24.         typedef ListIterator<T, const T&, const T&> const_iterator;
  25.     public:
  26.         // List的构造
  27.         list()
  28.         {
  29.             CreateHead();
  30.         }
  31.         iterator begin()
  32.         {
  33.             // return iterator(_head->_next);
  34.             return _head->_next;
  35.         }
  36.         iterator end()
  37.         {
  38.             // return iterator(_head);
  39.             return _head;
  40.         }
  41.         const_iterator begin()const
  42.         {
  43.             return _head->_next;
  44.         }
  45.         const_iterator end()const
  46.         {
  47.             return _head;
  48.         }
  49.         ////////////////////////////////////////////////////////////
  50.         // List Modify
  51.         void push_back(const T&& val) { insert(end(), forward<T>(val)); }
  52.         void pop_back() { erase(--end()); }
  53.         void push_front(const T&& val) { insert(begin(), forward<T>(val); }
  54.         void pop_front() { erase(begin()); }
  55.         // 在pos位置前插入值为val的节点,返回插入新节点的位置
  56.         iterator insert(iterator pos, const T& val)
  57.         {
  58.             // 通过迭代器找到所需的节点指针
  59.             Node* cur = pos._pNode;
  60.             Node* prev = cur->_prev;
  61.             // 创建新的节点
  62.             Node* newnode = new Node(val);
  63.             // 节点间相互连接
  64.             newnode->_prev = prev;
  65.             prev->_next = newnode;
  66.             cur->_prev = newnode;
  67.             newnode->_next = cur;
  68.             // 节点数量++
  69.             _size++;
  70.             //return iterator(newnode);
  71.             return newnode;
  72.         }
  73.         iterator insert(iterator pos, const T&& val)
  74.         {
  75.             // 通过迭代器找到所需的节点指针
  76.             Node* cur = pos._pNode;
  77.             Node* prev = cur->_prev;
  78.             // 创建新的节点
  79.             Node* newnode = new Node(forward<T>(val));
  80.             // 节点间相互连接
  81.             newnode->_prev = prev;
  82.             prev->_next = newnode;
  83.             cur->_prev = newnode;
  84.             newnode->_next = cur;
  85.             // 节点数量++
  86.             _size++;
  87.             //return iterator(newnode);
  88.             return newnode;
  89.         }
  90.     private:
  91.         void CreateHead()
  92.         {
  93.             _head = new Node();
  94.             _head->_next = _head;
  95.              _head->_prev = _head;
  96.             _size = 0;
  97.         }
  98.         PNode _head;    // 头结点
  99.         int _size;      // 记录链表中节点的个数
  100.     };
  101. };
复制代码

八、新的类功能

8.1 默认成员函数

原来C++类中,有6个默认成员函数:

  • 构造函数
  • 析构函数
  • 拷贝构造函数
  • 拷贝赋值重载
  • 取所在重载
  • const 取所在重载
末了告急的是前4个,后两个用处不大。默认成员函数就是我们不写编译器会天生一个默认的。C++11 新增了两个:移动构造函数和移动赋值运算符重载
针对移动构造函数和移动赋值运算符重载有一些须要注意的点如下:


  • 假如你没有本身实现移动构造函数,且没有实现析构函数 、拷贝构造、拷贝赋值重载中的恣意一个。那么编译器会自动天生一个默认移动构造。默认天生的移动构造函数,对于内置范例成员会实行逐成员按字节拷贝,自界说范例成员,则须要看这个成员是否实现移动构造,假如实现了就调用移动构造,没有实现就调用拷贝构造。
  • 假如你没有本身实现移动赋值重载函数,且没有实现析构函数 、拷贝构造、拷贝赋值重载中的恣意一个,那么编译器会自动天生一个默认移动赋值。默认天生的移动构造函数,对于内置范例成员会实行逐成员按字节拷贝,自界说范例成员,则须要看这个成员是否实现移动赋值,假如实现了就调用移动赋值,没有实现就调用拷贝赋值。(默认移动赋值跟上面移动构造完全雷同)
  • 假如你提供了移动构造大概移动赋值,编译器不会自动提供拷贝构造和拷贝赋值。
  1. class Person
  2. {
  3. public:
  4.         /*Person(const char* name = "", int age = 0)
  5.                 :_name(name)
  6.                 , _age(age)
  7.         {}
  8.         Person(const Person& p)
  9.                 :_name(p._name)
  10.                 ,_age(p._age)
  11.         {}
  12.         Person& operator=(const Person& p)
  13.         {
  14.                 if(this != &p)
  15.                 {
  16.                         _name = p._name;
  17.                         _age = p._age;
  18.                 }
  19.                 return *this;
  20.         }
  21.         ~Person()
  22.         {}*/
  23. private:
  24.         aj::string _name;
  25.         int _age;
  26. };
  27. int main()
  28. {
  29.         Person p1;
  30.         Person p2 = s1;
  31.         Person p3 = std::move(p1);
  32.         Person p4;
  33.         p4 = std::move(p2);
  34.         return 0;
  35. }
复制代码

8.2 类成员变量初始化

C++11答应在类界说时给成员变量初始缺省值,默认天生构造函数会利用这些缺省值初始化,类和对象(中)中的构造函数和类和对象(下)中的初始化列表讲了,这里就不再细讲了。

8.3 逼迫天生默认函数的关键字default

C++11可以让你更好的控制要利用的默认函数。假设你要利用某个默认的函数,但是由于一些缘故起因这个函数没有默认天生。比如:我们提供了拷贝构造,就不会天生移动构造了,那么我们可以利用default关键字体现指定移动构造天生。
  1. class Person
  2. {
  3. public:
  4.         Person(const char* name = "", int age = 0)
  5.                 :_name(name)
  6.                 , _age(age)
  7.         {}
  8.         Person(const Person& p)
  9.                 :_name(p._name)
  10.                 ,_age(p._age)
  11.         {}
  12.         Person(Person&& p) = default;
  13.         Person& operator=(const Person& p)
  14.         {
  15.                 if(this != &p)
  16.                 {
  17.                         _name = p._name;
  18.                         _age = p._age;
  19.                 }
  20.                 return *this;
  21.         }
  22.         ~Person()
  23.         {}
  24. private:
  25.         aj::string _name;
  26.         int _age;
  27. };
  28. int main()
  29. {
  30.         Person p1;
  31.         Person p2 = s1;
  32.         Person p3 = std::move(p1);
  33.        
  34.         return 0;
  35. }
复制代码

8.4 克制天生默认函数的关键字delete

假如能想要限定某些默认函数的天生,在C++98中,是该函数设置成private,而且只声明补丁已,如许只要其他人想要调用就会报错。在C++11中更简朴,只需在该函数声明加上=delete即可,该语法指示编译器不天生对应函数的默认版本,称=delete修饰的函数为删除函数。
  1. class Person
  2. {
  3. public:
  4.         Person(const char* name = "", int age = 0)
  5.                 :_name(name)
  6.                 , _age(age)
  7.         {}
  8.         ~Person() = delete;
  9. private:
  10.         aj::string _name;
  11.         int _age;
  12. };
  13. int main()
  14. {
  15.         // 无法引用 函数 "Person::~Person()" (已声明 所在行数 : 447)
  16.         // --它是已删除的函数        C++11
  17.         // Person s1;
  18.         return 0;
  19. }
复制代码

8.5 继续和多态中的final与override关键字

这个我们在多态章节已经举行了详细教学这里就不再举行教学。

末了

假如有什么发起和疑问,或是有什么错误,各人可以在品评区中提出。
渴望各人以后也能和我一起进步!!🌹🌹
假如这篇文章对你有用的话,渴望各人给一个三连支持一下!!🌹🌹

回复

使用道具 举报

登录后关闭弹窗

登录参与点评抽奖  加入IT实名职场社区
去登录
快速回复 返回顶部 返回列表