IT评测·应用市场-qidao123.com技术社区

标题: C++11先容 [打印本页]

作者: 勿忘初心做自己    时间: 前天 02:59
标题: C++11先容
目录
一、C++11的两个小点
1.1、decltype
1.2、nullptr
二、列表初始化
2.1、C++98传统的{}
2.2、C++11中的{}
2.3、C++11中的std::initializer_list
三、右值引用和移动语义
3.1、左值和右值
3.2、左值引用和右值引用
3.3、引用延伸生命周期
3.4、左值和右值的参数匹配
3.5、右值引用和移动语义的使用场景
3.5.1、左值引用主要使用场景回顾
3.5.2、移动构造和移动赋值
3.5.3、右值引⽤和移动语义办理传值返回问题
3.5.4、右值引⽤和移动语义在传参中的提效
3.6类型分类
3.7、折叠引用
3.8、完美转发
四、可变参数模版
4.1、根本语法及原理
4.2、包扩展
4.3、emplace系列接口
五、新的类功能
5.1、默认的移动构造和移动赋值
5.2、defult和delete
六、lambda
6.1、lambda表达式语法
6.2、捕捉列表
6.3、lambda的应⽤
6.4、lambda的原理
七、包装器
7.1、function
7.2、bind


一、C++11的两个小点

1.1、decltype

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

1.2、nullptr

由于C++中NULL被定义为字面量0,这样大概会带来一些问题,因为0既能表现指针常量,又能表现整型常量,以是出于清晰和安全的角度考虑,C++11中新增了nullptr,用于表现空指针。
二、列表初始化

2.1、C++98传统的{}

C++98中⼀般数组和结构体可以⽤{}进⾏初始化。如图:

2.2、C++11中的{}


示例代码:
  1. class Date
  2. {
  3. public:
  4.         Date(int year = 1, int month = 1, int day = 1)
  5.                 :_year(year)
  6.                 , _month(month)
  7.                 , _day(day)
  8.         {
  9.                 cout << "Date(int year, int month, int day)" << endl;
  10.         }
  11.         Date(const Date& d)
  12.                 :_year(d._year)
  13.                 , _month(d._month)
  14.                 , _day(d._day)
  15.         {
  16.                 cout << "Date(const Date& d)" << endl;
  17.         }
  18. private:
  19.         int _year;
  20.         int _month;
  21.         int _day;
  22. };
  23. int main()
  24. {
  25.         // C++98⽀持的
  26.         int a1[] = { 1, 2, 3, 4, 5 };
  27.         int a2[5] = { 0 };
  28.         Point p = { 1, 2 };
  29.         // C++11支持的
  30.         // 内置类型支持
  31.         int x1 = { 2 };
  32.         // ⾃定义类型支持
  33.         // 这⾥本质是⽤{ 2025, 1, 1}构造⼀个Date临时对象
  34.         // 临时对象再去拷⻉构造d1,编译器优化后合⼆为⼀变成{ 2025, 1, 1}直接构造初始化
  35.         // 运⾏⼀下,我们可以验证上⾯的理论,发现是没调⽤拷⻉构造的
  36.         Date d1 = { 2025, 1, 1 };
  37.         // 这⾥d2引⽤的是{ 2024, 7, 25 }构造的临时对象,临时对象有常性
  38.         const Date& d2 = { 2024, 7, 25 };
  39.         // 需要注意的是C++98⽀持单参数时类型转换,也可以不⽤{}
  40.         Date d3 = { 2025 };
  41.         Date d4 = 2025;
  42.         // 可以省略掉=
  43.         Point p1{ 1, 2 };
  44.         int x2{ 2 };
  45.         Date d6{ 2024, 7, 25 };
  46.         const Date& d7{ 2024, 7, 25 };
  47.         // 下面语句不支持,只有{}初始化,才能省略=
  48.         // Date d8 2025;
  49.         vector<Date> v;
  50.         v.push_back(d1);
  51.         v.push_back(Date(2025, 1, 1));
  52.         // ⽐起有名对象和匿名对象传参,这⾥{}更有性价⽐
  53.         v.push_back({ 2025, 1, 1 });
  54.         return 0;
  55. }
复制代码
2.3、C++11中的std::initializer_list


如图:(这里只截取了vector和list)
vector:

list:

示例代码:

三、右值引用和移动语义

C++98的C++语法中就有引⽤的语法,⽽C++11中新增了的右值引⽤语法特性,C++11之后我们之前学习的引⽤就叫做左值引⽤。⽆论左值引⽤还是右值引⽤,都是给对象取别名。
3.1、左值和右值


注意:引用了右值的变量的属性是左值。例如:int&& r1 = 10;10是右值,但是r1是左值。
示例代码:
  1. int main()
  2. {
  3.         // 左值:可以取地址
  4.         // 以下的p、b、c、*p、s、s[0]就是常⻅的左值
  5.         int* p = new int(0);
  6.         int b = 1;
  7.         const int c = b;
  8.         *p = 10;
  9.         string s("111111");
  10.         s[0] = 'x';
  11.         cout << &c << endl;
  12.         cout << (void*)&s[0] << endl;
  13.         // 右值:不能取地址
  14.         double x = 1.1, y = 2.2;
  15.         // 以下⼏个10、x + y、fmin(x, y)、string("11111")都是常⻅的右值
  16.         10;
  17.         x + y;
  18.         fmin(x, y);
  19.         string("11111");
  20.         //右值取地址会报错
  21.         //cout << &10 << endl;
  22.         //cout << &(x+y) << endl;
  23.         //cout << &(fmin(x, y)) << endl;
  24.         //cout << &string("11111") << endl;
  25.         return 0;
  26. }
复制代码
3.2、左值引用和右值引用


move声明:

示例代码:
  1. int main()
  2. {
  3.         // 左值:可以取地址
  4.         // 以下的p、b、c、*p、s、s[0]就是常⻅的左值
  5.         int* p = new int(0);
  6.         int b = 1;
  7.         const int c = b;
  8.         *p = 10;
  9.         string s("111111");
  10.         s[0] = 'x';
  11.         double x = 1.1, y = 2.2;
  12.         // 左值引⽤给左值取别名
  13.         int& r1 = b;
  14.         int*& r2 = p;
  15.         int& r3 = *p;
  16.         string& r4 = s;
  17.         char& r5 = s[0];
  18.         // 右值引⽤给右值取别名
  19.         int&& rr1 = 10;
  20.         double&& rr2 = x + y;
  21.         double&& rr3 = fmin(x, y);
  22.         string&& rr4 = string("11111");
  23.         // 左值引⽤不能直接引⽤右值,但是const左值引⽤可以引⽤右值
  24.         const int& rx1 = 10;
  25.         const double& rx2 = x + y;
  26.         const double& rx3 = fmin(x, y);
  27.         const string& rx4 = string("11111");
  28.         // 右值引⽤不能直接引⽤左值,但是右值引⽤可以引⽤move(左值)
  29.         int&& rrx1 = move(b);
  30.         int*&& rrx2 = move(p);
  31.         int&& rrx3 = move(*p);
  32.         string&& rrx4 = move(s);
  33.         //底层(汇编层面)并不分左值引用和右值引用,在底层它们都是一样的
  34.         //语法上通不过是因为上层封装时将他们封装为不同类型
  35.         //编译器检测语法时,因为类型不同报错
  36.         //所以只要强转成相同类型就可以了,move的本质也是在强转
  37.         string&& rrx5 = (string&&)s;
  38.         // b、r1、rr1都是变量表达式,都是左值
  39.         cout << &b << endl;
  40.         cout << &r1 << endl;
  41.         cout << &rr1 << endl;
  42.         // 这⾥要注意的是,rr1的属性是左值,所以不能再被右值引⽤绑定,除非move⼀下
  43.         int& r6 = r1;
  44.         // int&& rrx6 = rr1;
  45.         int&& rrx6 = move(rr1);
  46.         return 0;
  47. }
复制代码
注意:右值底层实在是有地点的,但是在语法层取不到。
3.3、引用延伸生命周期

右值引⽤可⽤于为临时对象延⻓⽣命周期,const 的左值引⽤也能延⻓临时对象⽣存期,但这些对象⽆法被修改。

上图中,r3引用的是右值,但是r3自己的属性是左值,以是可以延伸临时对象的生命周期。
3.4、左值和右值的参数匹配


示例代码:
  1. void f(int& x)
  2. {
  3.         std::cout << "左值引用重载 f(" << x << ")\n";
  4. }
  5. void f(const int& x)
  6. {
  7.         std::cout << "const 的左值引用重载 f(" << x << ")\n";
  8. }
  9. void f(int&& x)
  10. {
  11.         std::cout << "右值引用重载 f(" << x << ")\n";
  12. }
  13. int main()
  14. {
  15.         int i = 1;
  16.         const int ci = 2;
  17.         f(i); // 调用 f(int&)
  18.         f(ci); // 调用 f(const int&)
  19.         f(3); // 调用 f(int&&),如果没有 f(int&&) 重载则会调用 f(const int&)
  20.         f(std::move(i)); // 调用 f(int&&)
  21.         // 右值引用变量在用于表达式时是左值
  22.         int&& x = 1;
  23.         f(x); // 调用 f(int& x)
  24.         f(std::move(x)); // 调用 f(int&& x)
  25.         return 0;
  26. }
复制代码
结果:

3.5、右值引用和移动语义的使用场景

3.5.1、左值引用主要使用场景回顾

左值引⽤主要使⽤场景是在函数中左值引⽤传参和左值引⽤传返回值时减少拷⻉,同时还可以修改实参和修改返回对象的代价。左值引⽤已经办理⼤多数场景的拷⻉效率问题,但是有些场景不能使⽤传左值引⽤返回,如下面addStrings和generate函数,C++98中的办理⽅案只能是被迫使⽤输出型参数办理。那么C++11以后这⾥可以使⽤右值引⽤做返回值办理吗?显然是不大概的,因为这⾥的本质是返回对象是⼀个局部对象,函数结束这个对象就析构销毁了,右值引⽤返回也⽆法改变对象已经析构销毁的究竟。
代码:
  1. class Solution
  2. {
  3. public:
  4.         // 传值返回需要拷贝
  5.         string addStrings(string num1, string num2)
  6.         {
  7.                 string str;
  8.                 int end1 = num1.size() - 1, end2 = num2.size() - 1;
  9.                 // 进位
  10.                 int next = 0;
  11.                 while (end1 >= 0 || end2 >= 0)
  12.                 {
  13.                         int val1 = end1 >= 0 ? num1[end1--] - '0' : 0;
  14.                         int val2 = end2 >= 0 ? num2[end2--] - '0' : 0;
  15.                         int ret = val1 + val2 + next;
  16.                         next = ret / 10;
  17.                         ret = ret % 10;
  18.                         str += ('0' + ret);
  19.                 }
  20.                 if (next == 1)
  21.                         str += '1';
  22.                 reverse(str.begin(), str.end());
  23.                 return str;
  24.         }
  25. };
  26. class Solution
  27. {
  28. public:
  29.         // 这里的传值返回拷⻉代价就太大了
  30.         vector<vector<int>> generate(int numRows)
  31.         {
  32.                 vector<vector<int>> vv(numRows);
  33.                 for (int i = 0; i < numRows; ++i)
  34.                 {
  35.                         vv[i].resize(i + 1, 1);
  36.                 }
  37.                 for (int i = 2; i < numRows; ++i)
  38.                 {
  39.                         for (int j = 1; j < i; ++j)
  40.                         {
  41.                                 vv[i][j] = vv[i - 1][j] + vv[i - 1][j - 1];
  42.                         }
  43.                 }
  44.                 return vv;
  45.         }
  46. };
复制代码
3.5.2、移动构造和移动赋值


示例代码:
  1. namespace bit
  2. {
  3.         class string
  4.         {
  5.         public:
  6.                 typedef char* iterator;
  7.                 typedef const char* const_iterator;
  8.                 iterator begin()
  9.                 {
  10.                         return _str;
  11.                 }
  12.                 iterator end()
  13.                 {
  14.                         return _str + _size;
  15.                 }
  16.                 const_iterator begin() const
  17.                 {
  18.                         return _str;
  19.                 }
  20.                 const_iterator end() const
  21.                 {
  22.                         return _str + _size;
  23.                 }
  24.                 string(const char* str = "")
  25.                         :_size(strlen(str))
  26.                         , _capacity(_size)
  27.                 {
  28.                         cout << "string(char* str)-构造" << endl;
  29.                         _str = new char[_capacity + 1];
  30.                         strcpy(_str, str);
  31.                 }
  32.                 void swap(string& s)
  33.                 {
  34.                         ::swap(_str, s._str);
  35.                         ::swap(_size, s._size);
  36.                         ::swap(_capacity, s._capacity);
  37.                 }
  38.                 //拷贝构造
  39.                 string(const string& s)
  40.                         :_str(nullptr)
  41.                 {
  42.                         cout << "string(const string& s) -- 拷⻉构造" << endl;
  43.                         reserve(s._capacity);
  44.                         for (auto ch : s)
  45.                         {
  46.                                 push_back(ch);
  47.                         }
  48.                 }
  49.                 // 移动构造
  50.                 //传入的是临时创建的对象,即将亡值,不能取地址,用完就会消亡
  51.                 //所以直接交换资源
  52.                 string(string&& s)
  53.                 {
  54.                         cout << "string(string&& s) -- 移动构造" << endl;
  55.                         swap(s);
  56.                 }
  57.                 string& operator=(const string& s)
  58.                 {
  59.                         cout << "string& operator=(const string& s) -- 拷⻉赋值" <<
  60.                                 endl;
  61.                         if (this != &s)
  62.                         {
  63.                                 _str[0] = '\0';
  64.                                 _size = 0;
  65.                                 reserve(s._capacity);
  66.                                 for (auto ch : s)
  67.                                 {
  68.                                         push_back(ch);
  69.                                 }
  70.                         }
  71.                         return *this;
  72.                 }
  73.                 // 移动赋值
  74.                 string& operator=(string&& s)
  75.                 {
  76.                         cout << "string& operator=(string&& s) -- 移动赋值" << endl;
  77.                         swap(s);
  78.                         return *this;
  79.                 }
  80.                 ~string()
  81.                 {
  82.                         cout << "~string() -- 析构" << endl;
  83.                         delete[] _str;
  84.                         _str = nullptr;
  85.                 }
  86.                 char& operator[](size_t pos)
  87.                 {
  88.                         assert(pos < _size);
  89.                         return _str[pos];
  90.                 }
  91.                 void reserve(size_t n)
  92.                 {
  93.                         if (n > _capacity)
  94.                         {
  95.                                 char* tmp = new char[n + 1];
  96.                                 if (_str)
  97.                                 {
  98.                                         strcpy(tmp, _str);
  99.                                         delete[] _str;
  100.                                 }
  101.                                 _str = tmp;
  102.                                 _capacity = n;
  103.                         }
  104.                 }
  105.                 void push_back(char ch)
  106.                 {
  107.                         if (_size >= _capacity)
  108.                         {
  109.                                 size_t newcapacity = _capacity == 0 ? 4 : _capacity *
  110.                                         2;
  111.                                 reserve(newcapacity);
  112.                         }
  113.                         _str[_size] = ch;
  114.                         ++_size;
  115.                         _str[_size] = '\0';
  116.                 }
  117.                 string& operator+=(char ch)
  118.                 {
  119.                         push_back(ch);
  120.                         return *this;
  121.                 }
  122.                 const char* c_str() const
  123.                 {
  124.                         return _str;
  125.                 }
  126.                 size_t size() const
  127.                 {
  128.                         return _size;
  129.                 }
  130.         private:
  131.                  char* _str = nullptr;
  132.                  size_t _size = 0;
  133.                  size_t _capacity = 0;
  134.         };
  135. }
复制代码
3.5.3、右值引⽤和移动语义办理传值返回问题

注意:下面所有测试都基于上面实现的string类。
右值对象构造,只有拷⻉构造,没有移动构造的场景:
图1展⽰了vs2019 debug环境下编译器对拷⻉的优化,左边为不优化的情况下,两次拷⻉构造,右 边为编译器优化的场景下一连步骤中的拷⻉合⼆为⼀变为⼀次拷⻉构造。
需要注意的是在vs2019的release和vs2022的debug和release,下⾯代码优化为⾮常可怕,会直接 将str对象的构造,str拷⻉构造临时对象,临时对象拷⻉构造ret对象,合三为⼀,变为直接构造。 变为直接构造。要理解这个优化要联合局部对象⽣命周期和栈帧的⻆度理解,如图3所⽰。
linux下可以将下⾯代码拷⻉到test.cpp⽂件,编译时⽤ g++ test.cpp -fno-elide-constructors 的⽅式关闭构造优化,运⾏结果可以看到图1左边没有优化的两次拷⻉。
图一:

右值对象构造,有拷⻉构造,也有移动构造的场景:
图2展⽰了vs2019 debug环境下编译器对拷⻉的优化,左边为不优化的情况下,两次移动构造,右 边为编译器优化的场景下一连步骤中的拷⻉合⼆为⼀变为⼀次移动构造。
需要注意的是在vs2019的release和vs2022的debug和release,下⾯代码优化为⾮常可怕,会直接 将str对象的构造,str拷⻉构造临时对象,临时对象拷⻉构造ret对象,合三为⼀,变为直接构造。 要理解这个优化要联合局部对象⽣命周期和栈帧的⻆度理解,如图3所⽰。
linux下可以将下⾯代码拷⻉到test.cpp⽂件,编译时⽤ g++ test.cpp -fno-elide-constructors 的⽅式关闭构造优化,运⾏结果可以看到图1左边没有优化的两次移动。
图二:

图三:

右值对象赋值,只有拷⻉构造和拷⻉赋值,没有移动构造和移动赋值的场景:
图4左边展⽰了vs2019 debug和 g++ test.cpp -fno-elide-constructors 关闭优化环境下编译器的处理,⼀次拷⻉构造,⼀次拷⻉赋值。
需要注意的是在vs2019的release和vs2022的debug和release,下⾯代码会进⼀步优化,直接构造 要返回的临时对象,str本质是临时对象的引⽤,底层⻆度⽤指针实现。运⾏结果的⻆度,我们可以 看到str的析构是在赋值以后,阐明str就是临时对象的别名。
图四:

右值对象赋值,既有拷⻉构造和拷⻉赋值,也有移动构造和移动赋值的场景:
图5左边展⽰了vs2019 debug和 g++ test.cpp -fno-elide-constructors 关闭优化环境下编译器的处理,⼀次移动构造,⼀次移动赋值。
需要注意的是在vs2019的release和vs2022的debug和release,下⾯代码会进⼀步优化,直接构造 要返回的临时对象,str本质是临时对象的引⽤,底层⻆度⽤指针实现。运⾏结果的⻆度,我们可以 看到str的析构是在赋值以后,阐明str就是临时对象的别名。
图五:

总结:如果传入的是右值,对于自定义类型而言,右值又叫将亡值,使用之后就会死亡,以是没有必要举行拷贝浪费效率,直接转移资源即可,即直接互换数据(例如通过swap),如果是左值大概就需要举行深拷贝了。
3.5.4、右值引⽤和移动语义在传参中的提效

   检察STL⽂档我们发现C++11以后容器的push和insert系列的接⼝否增加的右值引⽤版本。
  例如:


   
  实当代码:
  1.                 //右值版本的insert
  2.                 iterator insert(iterator pos, T&& x)
  3.                 {
  4.                         Node* cur = pos._node;
  5.                         Node* newnode = new Node(move(x));
  6.                         Node* prev = cur->_prev;
  7.                         // prev  newnode  cur
  8.                         prev->_next = newnode;
  9.                         newnode->_prev = prev;
  10.                         newnode->_next = cur;
  11.                         cur->_prev = newnode;
  12.                         return iterator(newnode);
  13.                 }
  14.                 //右值版本的push_back
  15.                 void push_back(T&& x)
  16.                 {
  17.                         insert(end(), move(x));
  18.                 }
复制代码
这里有一个注意点:引用了右值的变量的属性实在是左值,也就是说,对于一个变量,固然它引用的是右值,但是他自己是左值,以是这里只是将这两个方法的形参改为吸收右值的情势是不敷的,例如:push_back的右值版本的方法中,如果只将形参改为能够吸收右值,其他不变,那么形参x引用的是右值,但x自己实在是左值,那么在方法内部调用insert方法时就会因为x是左值而调用左值对应的insert方法,而不是右值对应的insert方法,对于这种情况,我们有两种方法办理,第一种是完美转发,下面会先容,这里使用第二种,因为这个方法内部我们是知道我们吸收的是右值的,以是向下传递时将该变量move一下,将其转换为右值,当然,这样做所有对应调到的方法都要有右值版本才行,而且每次向下传递时都要move一下。
代码美满:(仅右值相干部门)
  1.                 //节点的构造方法-右值版本
  2.                 ListNode(T&& data)
  3.                         :_next(nullptr)
  4.                         , _prev(nullptr)
  5.                         , _data(move(data))
  6.                 {}
复制代码
解释:需要这个方法是因为在insert方法中通过右值直接构造节点。
   实在这⾥另有⼀个emplace系列的接⼝,但是这个涉及可变参数模板,我们需要把可变参数模板讲解以后再讲解emplace系列的接⼝。
  3.6类型分类

C++11以后,进⼀步对类型进⾏了分别,右值被分别纯右值(pure value,简称prvalue)和将亡值 (expiring value,简称xvalue)。
纯右值是指那些字⾯值常量或求值结果相当于字⾯值或是⼀个不具名的临时对象。如:42、true、nullptr 大概类似 str.substr(1, 2)、str1 + str2 传值返回函数调⽤,大概整形a、b,a++,a+b等。纯右值和将亡值C++11中提出的,C++11中的纯右值概念分别等价于 C++98中的右值。(即内置类型的右值叫做纯右值)
将亡值是指返回右值引⽤的函数的调⽤表达式和转换为右值引⽤的转换函数的调⽤表达,如 move(x)、static_cast(x)。(即自定义类型的右值叫做将亡值)
泛左值(generalized value,简称glvalue),泛左值包罗将亡值和左值。
两个关于值类型的中⽂和英⽂的官⽅⽂档,有兴 趣可以相识细节:
https://zh.cppreference.com/w/cpp/language/value_categoryhttps://zh.cppreference.com/w/cpp/language/value_category

3.7、折叠引用

   C++中不能直接定义引⽤的引⽤如 int& && r = i;,这样写会直接报错,通过模板或 typedef 中的类型操纵可以构成引⽤的引⽤。 通过模板或 typedef 中的类型操纵构成引⽤的引⽤时,这时C++11给出了⼀个引⽤折叠的规则:右值引⽤的右值引⽤折叠成右值引⽤,所有其他组合均折叠成左值引⽤。
  下⾯的步伐中很好的展⽰了模板和typedef时构成引⽤的引⽤时的引⽤折叠规则。


从上面代码可以看到,无论是右值还是左值都可以调用该模版函数,且实际使用中,传入左值就会天生左值版本的函数,传入右值就会天生右值版本的函数,这就是引用折叠。对于函数而言,平常函数和使用了类的模版的成员函数都无法实现引用折叠,想要实现引用折叠,函数必须有自己的模版参数。例如:

上图是稍加改动后的从前模拟实现的list,这个成员函数就只是右值版本的函数,不是引用折叠,因为它的模版是 list 这个类的,不是自己的,当类实例化时,这个模版参数就定下来了,他就没法根据传入的参数在去实例化差别的左值和右值的版本。
3.8、完美转发

PerfectForward(T&& t)函数模板步伐中,传左值实例化以后是左值引⽤的PerfectForward函数,传右值实例化以后是右值引⽤的PerfectForward函数。
但是联合我们在前面的讲解,变量表达式都是左值属性,也就意味着⼀个右值被右值引⽤绑定 后,右值引⽤变量表达式的属性是左值,也就是说PerfectForward函数中t的属性是左值,那么我们把t传递给下⼀层函数Fun,那么匹配的都是左值引⽤版本的Fun函数。这⾥我们想要保持t对象的属性, 就需要使⽤完美转发实现。
   完美转发声明:
  template <class T> T&& forward (typename remove_reference<T>::type& arg);
  template <class T> T&& forward (typename remove_reference<T>::type&& arg);
  完美转发forward本质是⼀个函数模板,他主要还是通过引⽤折叠的⽅式实现,下⾯⽰例中传递给 PerfectForward的实参是右值,T被推导为int,没有折叠,forward内部t被强转为右值引⽤返回;传递给 PerfectForward的实参是左值,T被推导为int&,引⽤折叠为左值引⽤,forward内部t被强转为左值引⽤返回。

四、可变参数模版

4.1、根本语法及原理

C++11⽀持可变参数模板,也就是说⽀持可变数量参数的函数模板和类模板,可变数⽬的参数被称 为参数包,存在两种参数包:模板参数包,表⽰零或多个模板参数;函数参数包:表⽰零或多个函 数参数。
   template <class  ...Args> void Func(Args... args) {}
  template <class  ...Args> void Func(Args&... args) {}
  template <class  ...Args> void Func(Args&&... args) {}
  我们⽤省略号来指出⼀个模板参数或函数参数的表⽰⼀个包,在模板参数列表中,class...或 typename...指出接下来的参数表⽰零或多个类型列表;在函数参数列表中,类型名后⾯跟...指出 接下来表⽰零或多个形参对象列表;函数参数包可以⽤左值引⽤或右值引⽤表⽰,跟前⾯平常模板 ⼀样,每个参数实例化时遵照引⽤折叠规则。
可变参数模板的原理跟模板类似,本质还是去实例化对应类型和个数的多个函数。
这⾥我们可以使⽤sizeof...(参数包)方式去盘算参数包中参数的个数。
示例代码一:
  1. void Print()
  2. {
  3.         cout << endl;
  4. }
  5. //参数包传入时第一个参数解析到x中
  6. //然后再递归依次进行解析
  7. template <class T, class ...Args>
  8. void Print(T&& x, Args&&... args)
  9. {
  10.         //不可以这样结束递归
  11.         //因为这样是运行时结束递归的方式
  12.         //但是解析参数包时编译时解析的,所以需要提供一个空参的函数来结束编译时递归
  13.         //if (sizeof...(args) == 0)
  14.         //        return;
  15.         cout << x << " ";
  16.         Print(args...);
  17. }
  18. // 可变模版参数
  19. // 参数类型可变
  20. // 参数个数可变
  21. template <class ...Args>
  22. void ShowList(Args... args)
  23. {
  24.         //计算参数包的参数个数
  25.         cout << sizeof...(args) << endl;
  26. // 可变参数模版在编译时解析
  27. // 下面是运行获取和解析,所以不支持这样用
  28. //        /*cout << sizeof...(args) << endl;
  29. //        for (size_t i = 0; i < sizeof...(args); i++)
  30. //        {
  31. //                cout << args[i] << " ";
  32. //        }
  33.         //正确解析方式
  34.         Print(args...);
  35. }
  36. int main()
  37. {
  38.         //下面这些调用方式都可以
  39.         ShowList();
  40.         ShowList(1);
  41.         ShowList(1, "xxxxx");
  42.         ShowList(1, "xxxxx", 2.2);
  43.         return 0;
  44. }
复制代码
示例代码二:
  1. template <class T>
  2. int PrintArg(T t)
  3. {
  4.         cout << t << " ";
  5.         //借助数组解析,但是返回值会被放入数组中
  6.         //所以返回int类型的0,符合数组存储数据的类型
  7.         //当然也可以不返回该类型的数据,那就需要用到逗号表达式
  8.         return 0;
  9. }
  10. template <class ...Args>
  11. void ShowList(Args... args)
  12. {
  13.         //借助数组来帮助我们解析参数包
  14.         //返回值会放到数组中
  15.         int arr[] = { PrintArg(args)... };
  16.         //如果函数返回值和数组类型不相符可以这样
  17.         // 通过逗号表达式,最终放入数组中的是0
  18.         //int arr[] = { (PrintArg(args),0)...};
  19.        
  20.         cout << endl;
  21. }
  22. //编译推演生成下面的函数
  23. //void ShowList(int x, char y, std::string z)
  24. //{
  25. //        int arr[] = { PrintArg(x),PrintArg(y),PrintArg(z) };
  26. //        cout << endl;
  27. //}
  28. //这样也可以
  29. //template <class ...Args>
  30. //void ShowList(Args... args)
  31. //{
  32. // //这里必须用逗号表达式,因为cout返回的类型是ostream的
  33. //        int arr[] = { (cout<<(args)<<" ", 0)...};
  34. //
  35. //        cout << endl;
  36. //}
  37. int main()
  38. {
  39.         ShowList(1, 'A', std::string("sort"));
  40.         return 0;
  41. }
复制代码
4.2、包扩展

对于⼀个参数包,我们除了能盘算他的参数个数,我们能做的唯⼀的事情就是扩展它,当扩展⼀个 包时,我们还要提供⽤于每个扩展元素的模式,扩展⼀个包就是将它分解为构成的元素,对每个元 素应⽤模式,获得扩展后的列表。我们通过在模式的右边放⼀个省略号(...)来触发扩展操纵。底层 的实现细节如图1所⽰。
C++还⽀持更复杂的包扩展,直接将参数包依次展开依次作为实参给⼀个函数行止理。

4.3、emplace系列接口

   template <class...  Args> void emplace_back (Args&&...  args);
  template <class...  Args> iterator emplace (const_iterator position, Args&&...  args);
  
示例代码一:(使用)
  1. // emplace_back总体而言是更高效,推荐使用
  2. int main()
  3. {
  4.         //基本使用和push_xxx/insert方法类似
  5.         //这里使用的是前面我们自己实现的string,方便我们看效果
  6.         list<bit::string> lt;
  7.         // 左值
  8.         bit::string s1("111111111111");
  9.         lt.emplace_back(s1);
  10.         // 右值
  11.         lt.emplace_back(move(s1));
  12.         list<pair<bit::string, int>> lt1;
  13.         // 构造pair + 拷贝/移动构造pair到list的节点中data上
  14.         pair<bit::string, int> kv("苹果", 1);
  15.         lt1.emplace_back(kv);
  16.         lt1.emplace_back(move(kv));
  17.         cout << endl << endl;
  18.         // 不同点
  19.         // 直接把构造pair参数包往下传,直接用pair参数包构造pair
  20.         lt1.emplace_back("苹果", 1);
  21.         cout << endl;
  22.         // 直接把构造string参数包往下传,直接用string参数包构造string
  23.         lt.emplace_back("111111111111");
  24.         //使用的构造函数:string(int n, const char ch)
  25.         lt.emplace_back(10, 'x');
  26.         cout << endl << endl;
  27.         return 0;
  28. }
复制代码
结果:

示例代码二:(emplace_back实现)
  1.                 //节点的构造函数  ListNode
  2.                 template<class... Args>
  3.                 ListNode(Args... args)
  4.                         :_next(nullptr)
  5.                         , _prev(nullptr)
  6.                         , _data(forward<Args>(args)...)
  7.                 {}
  8.         //list
  9.                 template<class... Args>
  10.                 iterator insert(iterator pos, Args&&... args)
  11.                 {
  12.                         Node* cur = pos._node;
  13.                         Node* newnode = new Node(forward<Args>(args)...);
  14.                         Node* prev = cur->_prev;
  15.                         // prev  newnode  cur
  16.                         prev->_next = newnode;
  17.                         newnode->_prev = prev;
  18.                         newnode->_next = cur;
  19.                         cur->_prev = newnode;
  20.                         return iterator(newnode);
  21.                 }
  22.                 template<class... Args>
  23.                 void emplace_back(Args&&... args)
  24.                 {
  25.                         insert(end(), forward<Args>(args)...);
  26.                 }
复制代码
五、新的类功能

5.1、默认的移动构造和移动赋值


5.2、defult和delete



六、lambda

6.1、lambda表达式语法

   lambda 表达式本质是⼀个匿名函数对象,跟平常函数差别的是他可以定义在函数内部。
  lambda 表达式语法使⽤层⽽⾔没有类型,以是我们⼀般是⽤auto大概模板参数定义的对象去吸收 lambda 对象。
  lambda表达式的格式: [capture-list] (parameters)-> return type { function boby }:

6.2、捕捉列表

lambda 表达式中默认只能⽤ lambda 函数体和参数中的变量,如果想⽤外层作⽤域中的变量就需要进⾏捕捉。捕获的三种方式如下:

   捕获列表详细阐明:
  捕获列表形貌了上下文中哪些数据可以被lambda使用,以及使用的方式是传值还是传引用。
  [var]:表现值传递方式捕获变量var。默认会被const修饰
  [=]:表现值传递方式捕获所有父作用域中的变量(包括this)。
  [&var]:表现引用传递捕获变量var。
  [&]:表现引用传递捕获所有父作用域中的变量(包括this)。
   lambda 表达式如果在函数局部域中,他可以捕捉 lambda 位置之前定义的变量,不能捕捉静态局部变量和全局变量,静态局部变量和全局变量也不需要捕捉, lambda 表达式中可以直接使⽤。这也意味着 lambda 表达式如果定义在全局位置,捕捉列表必须为空。
默认情况下, lambda 捕捉列表是被const修饰的,也就是说传值捕捉的过来的对象不能修改, mutable加在参数列表的后⾯可以取消其常量性,也就说使⽤该修饰符后,传值捕捉的对象就可以 修改了,但是修改还是形参对象,不会影响实参。使⽤该修饰符后,参数列表不可省略(纵然参数为空)。
6.3、lambda的应⽤

在学习 lambda 表达式之前,我们的使⽤的可调⽤对象只有函数指针和仿函数对象,函数指针的类型定义起来⽐较麻烦,仿函数要定义⼀个类,相对会⽐较麻烦。使⽤ lambda 去定义可调⽤对象,既简朴⼜⽅便。
示例代码一:
  1. // lambda 可以理解为定义了一个匿名函数的对象
  2. int main()
  3. {
  4.         //lambda表达式没有类型,需要使用auto推导
  5.         auto add1 = [](int x, int y)->int {return x + y; };
  6.         cout << add1(1, 2) << endl;
  7.         //多行时可以这样写
  8.         auto func1 = []()->int
  9.         {
  10.                 cout << "hello bit" << endl;
  11.                 cout << "hello world" << endl;
  12.                 return 0;
  13.         };
  14.         func1();
  15.         // 返回值类型可自动推导类型,所以可以省略
  16.         auto func2 = []
  17.         {
  18.                 cout << "hello bit" << endl;
  19.                 cout << "hello world" << endl;
  20.                 return 0;
  21.         };
  22.         cout << func2() << endl;
  23.         //无形参是也可以省略
  24.         auto func3 = []
  25.         {
  26.                 cout << "hello bit" << endl;
  27.                 cout << "hello world" << endl;
  28.         };
  29.         func3();
  30.         return 0;
  31. }
复制代码
示例代码二:
  1. int main()
  2. {
  3.         int a = 0, b = 1;
  4.         auto swap1 = [](int& x, int& y)
  5.         {
  6.                 // 只能用当前lambda局部域(即形参)和捕捉的对象(捕获列表)
  7.                 int tmp = x;
  8.                 x = y;
  9.                 y = tmp;
  10.         };
  11.         swap1(a, b);
  12.         // 传值捕捉本质是一种拷贝,并且const修饰了
  13.         // mutable相当于去掉const属性,可以修改了
  14.         // 但是修改了不会影响外面被捕捉的值,因为是一种拷贝
  15.         auto swap2 = [a, b]()mutable
  16.         {
  17.                 int tmp = a;
  18.                 a = b;
  19.                 b = tmp;
  20.         };
  21.         swap2();
  22.         //如果想在lambda表达式中影响外部的值,可以传引用捕获
  23.         //传引用捕获的值不会被const修饰,在表达式内可以直接修改
  24.         auto swap3 = [&a, &b]()
  25.         {
  26.                 int tmp = a;
  27.                 a = b;
  28.                 b = tmp;
  29.         };
  30.         swap3();
  31.         return 0;
  32. }
复制代码
示例代码三:
  1. int x = 0;
  2. int main()
  3. {
  4.         // 只能用当前lambda局部域和捕捉的对象
  5.         // 全局对象和静态变量不需要捕获,直接就可以使用
  6.         int a = 0, b = 1, c = 2, d = 3;
  7.         // 所有值传值捕捉
  8.         auto func1 = [=]
  9.         {
  10.                 int ret = a + b + c + d + x;
  11.                 return ret;
  12.         };
  13.        
  14.         // 所有值传引用捕捉
  15.         auto func2 = [&]
  16.         {
  17.                 a++;
  18.                 b++;
  19.                 c++;
  20.                 d++;
  21.                 int ret = a + b + c + d;
  22.                 return ret;
  23.         };
  24.         // 混合捕捉
  25.         auto func3 = [&a, b]
  26.         {
  27.                 a++;
  28.                 // b++;
  29.                 int ret = a + b;
  30.                 return ret;
  31.         };
  32.         // 混合捕捉
  33.         // 所有值以引用方式捕捉,d用传值捕捉
  34.         auto func4 = [&, d]
  35.         {
  36.                 a++;
  37.                 b++;
  38.                 c++;
  39.                 //d++;
  40.                 int ret = a + b + c + d;
  41.         };
  42.         //所有值以传值方式捕获,d传引用捕获
  43.         auto func5 = [=, &d]() mutable
  44.         {
  45.                 a++;
  46.                 b++;
  47.                 c++;
  48.                 d++;
  49.                 int ret = a + b + c + d;
  50.         };
  51.         return 0;
  52. }
复制代码
  lambda 在许多其他地⽅⽤起来也很好⽤。⽐如线程中定义线程的执⾏函数逻辑,智能指针中定制删除器等, lambda 的应⽤还是很⼴泛的,以后我们会不停接触到。
  6.4、lambda的原理

lambda 的原理和范围for很像,编译后从汇编指令层的⻆度看,压根就没有 lambda 和范围for 这样的东西。范围for底层是迭代器,⽽lambda底层是仿函数对象,也就说我们写了⼀个lambda 以后,编译器会⽣成⼀个对应的仿函数的类。
仿函数的类名是编译按⼀定规则⽣成的,保证差别的 lambda ⽣成的类名差别,lambda参数/返回类型/函数体就是仿函数operator()的参数/返回类型/函数体, lambda 的捕捉列表本质是⽣成的仿函数类的成员变量,也就是说捕捉列表的变量都是 lambda 类构造函数的实参,当然隐式捕捉,编译器要看使⽤哪些就传那些对象。
七、包装器

7.1、function


下面是 function 的原型,他被定义<functional>头⽂件中:

函数指针、仿函数、 lambda 等可调⽤对象的类型各不相同, std::function 的优势就是统⼀类型,对他们都可以进⾏包装,这样在许多地⽅就⽅便声明可调⽤对象的类型。
   语法: function<返回值类型(形参类型)> 变量名 = 封装的可调用对象
  示例代码:
  1. #include<functional>
  2. int f(int a, int b)
  3. {
  4.         return a + b;
  5. }
  6. struct Functor
  7. {
  8. public:
  9.         int operator() (int a, int b)
  10.         {
  11.                 return a + b;
  12.         }
  13. };
  14. class Plus
  15. {
  16. public:
  17.         static int plusi(int a, int b)
  18.         {
  19.                 return a + b;
  20.         }
  21.         double plusd(double a, double b)
  22.         {
  23.                 return a + b;
  24.         }
  25. };
  26. int main()
  27. {
  28.         // 包装可调用对象
  29.         //函数
  30.         function<int(int, int)> f1 = f;
  31.         //仿函数
  32.         function<int(int, int)> f2 = Functor();
  33.         //lambda表达式
  34.         function<int(int, int)> f3 = [](int a, int b) {return a + b; };
  35.         cout << f1(1, 1) << endl;
  36.         cout << f2(1, 1) << endl;
  37.         cout << f3(1, 1) << endl;
  38.         // 包装静态成员函数
  39.         //静态成员函数不用取地址符也可以取到地址,所以这里不写&也可以
  40.         function<int(int, int)> f4 = &Plus::plusi;
  41.         cout << f4(1, 1) << endl;
  42.         // 包装非静态成员函数
  43.         //类的非静态成员函数必须有取地址符才能取到地址
  44.         //因为非静态成员函数有隐含的this指针,所以需要多传一个参数
  45.         //这个参数可以是类类型的指针,也可以是类对象
  46.         //指针版
  47.         function<double(Plus*, double, double)> f5 = &Plus::plusd;
  48.         Plus pd;
  49.         cout << f5(&pd, 1.1, 1.1) << endl;
  50.         // 类对象版
  51.         function<double(Plus, double, double)> f6 = &Plus::plusd;
  52.         cout << f6(pd, 1.1, 1.1) << endl;
  53.         cout << f6(Plus(), 1.1, 1.1) << endl;
  54.         return 0;
  55. }
复制代码
下⾯的代码样例展⽰了 std::function 作为map的参数,实现字符串和可调⽤对象的映射表功能。
  1. // 这种⽅式的最⼤优势之⼀是⽅便扩展,假设还有其他运算,我们增加map中的映射即可
  2. class Solution
  3. {
  4. public:
  5.         int evalRPN(vector<string>& tokens)
  6.         {
  7.                 stack<int> st;
  8.                 // function作为map的映射可调⽤对象的类型
  9.                 map<string, function<int(int, int)>> opFuncMap =
  10.                 {
  11.                         {"+", [](int x, int y) {return x + y; }},
  12.                         {"-", [](int x, int y) {return x - y; }},
  13.                         {"*", [](int x, int y) {return x * y; }},
  14.                         {"/", [](int x, int y) {return x / y; }}
  15.                 };
  16.                 for (auto& str : tokens)
  17.                 {
  18.                         if (opFuncMap.count(str)) // 操作符
  19.                         {
  20.                                 int right = st.top();
  21.                                 st.pop();
  22.                                 int left = st.top();
  23.                                 st.pop();
  24.                                 int ret = opFuncMap[str](left, right);
  25.                                 st.push(ret);
  26.                         }
  27.                         else
  28.                         {
  29.                                 st.push(stoi(str));
  30.                         }
  31.                 }
  32.                 return st.top();
  33.         }
  34. };
复制代码
7.2、bind


bind声明:

​​​​​​​placeholders声明:

示例代码:
  1. //这里它们从命名空间中放开
  2. //否则指定命名空间的方式写起来太长了
  3. using placeholders::_1;
  4. using placeholders::_2;
  5. using placeholders::_3;
  6. int Sub(int a, int b)
  7. {
  8.         return (a - b) * 10;
  9. }
  10. int SubX(int a, int b, int c)
  11. {
  12.         return (a - b - c) * 10;
  13. }
  14. int main()
  15. {
  16.         auto sub1 = bind(Sub, _1, _2);
  17.         cout << sub1(10, 5) << endl;
  18.         // bind 本质返回的一个仿函数对象
  19.         // 调整参数顺序(不常用)
  20.         // _1代表第一个实参
  21.         // _2代表第二个实参
  22.         // ...以此类推
  23.         auto sub2 = bind(Sub, _2, _1);
  24.         cout << sub2(10, 5) << endl;
  25.         // 调整参数个数 (常用)
  26.         //直接将第一个参数固定了
  27.         auto sub3 = bind(Sub, 100, _1);
  28.         cout << sub3(5) << endl;
  29.         auto sub4 = bind(Sub, _1, 100);
  30.         cout << sub4(5) << endl;
  31.         // 分别绑死第123个参数
  32.         auto sub5 = bind(SubX, 100, _1, _2);
  33.         cout << sub5(5, 1) << endl;
  34.         auto sub6 = bind(SubX, _1, 100, _2);
  35.         cout << sub6(5, 1) << endl;
  36.         auto sub7 = bind(SubX, _1, _2, 100);
  37.         cout << sub7(5, 1) << endl;
  38.         function<double(Plus, double, double)> f6 = &Plus::plusd;
  39.         Plus pd;
  40.         cout << f6(pd, 1.1, 1.1) << endl;
  41.         cout << f6(Plus(), 1.1, 1.1) << endl;
  42.         // bind一般用于,绑死一些固定参数
  43.         function<double(double, double)> f7 = bind(&Plus::plusd, Plus(), _1, _2);
  44.         cout << f7(1.1, 1.1) << endl;
  45.         //auto func1 = [](double rate, double monty, int year)->double {return monty * rate * year;};
  46.         auto func1 = [](double rate, double monty, int year)->double {
  47.                 double ret = monty;
  48.                 for (int i = 0; i < year; i++)
  49.                 {
  50.                         ret += ret * rate;
  51.                 }
  52.                 return ret - monty;
  53.                 };
  54.         function<double(double)> func3_1_5 = bind(func1, 0.015, _1, 3);
  55.         function<double(double)> func5_1_5 = bind(func1, 0.015, _1, 5);
  56.         function<double(double)> func10_2_5 = bind(func1, 0.025, _1, 10);
  57.         function<double(double)> func20_3_5 = bind(func1, 0.035, _1, 30);
  58.         cout << func3_1_5(1000000) << endl;
  59.         cout << func5_1_5(1000000) << endl;
  60.         cout << func10_2_5(1000000) << endl;
  61.         cout << func20_3_5(1000000) << endl;
  62.         return 0;
  63. }
复制代码
图解:


免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。




欢迎光临 IT评测·应用市场-qidao123.com技术社区 (https://dis.qidao123.com/) Powered by Discuz! X3.4