c++11(下篇)

提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档

文章目录

  • 前言
  • 一、可变参数模板
    • 4.1 基本语法及原理
    • 4.2 包扩展
    • 4.3 empalce系列接⼝
  • 二、lambda
    • 2.1.仿函数
    • 2.2.lambda表达式语法
    • 2.3. 捕捉列表
    • 2.3lambda原理
  • 三. 新的类功能
    • 3.1 默认的移动构造和移动赋值
    • 3.2 defult和delete
    • 3.3 final和override
    • 3.4 STL中⼀些变化
  • 四 包装器
    • 4.1. function
    • bind
  • 总结


前言

承接上文c++11(上篇),上文主要讲解了初始化列表,右值引用和移动语义等相关知识,下面将会由此深入并扩展c++11中的重要概念
此外在了解一个知识点的时候,作者会先介绍其面对的问题,再讲解概念是如何解决这个问题的


一、可变参数模板

通俗的讲可变参数模板其实就是模板的模板,当我们使用单一的模板函数的时候,当我们面对参数数量不确定的时候,例如:

void Print()
{};

int main()
{
 double x = 2.2;
 Print(); // 包⾥有0个参数 
 Print(1); // 包⾥有1个参数 
 Print(1, string("xxxxx")); // 包⾥有2个参数 
 Print(1.1, string("xxxxx"), x); // 包⾥有3个参数 
 return 0;
}

难道我们要写四个函数模板去实现函数的进行吗?
此时可变参数模板应运而生

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…运算符去计算参数包中参数的个数。

template <class ...Args>
void Print(Args&&... args)
{
 cout << sizeof...(args) << endl;
}
int main()
{
 double x = 2.2;
 Print(); // 包⾥有0个参数 
 Print(1); // 包⾥有1个参数 
 Print(1, string("xxxxx")); // 包⾥有2个参数 
 Print(1.1, string("xxxxx"), x); // 包⾥有3个参数 
 return 0;
}

// 原理1:编译本质这⾥会结合引⽤折叠规则实例化出以下四个函数 
void Print();
void Print(int&& arg1);
void Print(int&& arg1, string&& arg2);
void Print(double&& arg1, string&& arg2, double& arg3);
// 原理2:更本质去看没有可变参数模板,我们实现出这样的多个函数模板才能⽀持 
// 这⾥的功能,有了可变参数模板,我们进⼀步被解放,他是类型泛化基础 
// 上叠加数量变化,让我们泛型编程更灵活。 
void Print();
template <class T1>
void Print(T1&& arg1);
template <class T1, class T2>
void Print(T1&& arg1, T2&& arg2);
template <class T1, class T2, class T3>
void Print(T1&& arg1, T2&& arg2, T3&& arg3);

• 可变参数模板的原理跟模板类似,本质还是去实例化对应类型和个数的多个函数。
• 这⾥我们可以使⽤sizeof…运算符去计算参数包中参数的个数。

4.2 包扩展

当我们看完4.1的内容时还并没有感觉到其语法上的难度,但是我们继续往下面想:函数内部如何使用这些可变参数模板实例化出的参数呢?

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

在这里插入图片描述
相当于再设定一个函数来递归获取每一个参数,将参数包扩展开来。

// 可变模板参数 
// 参数类型可变 
// 参数个数可变 
// 打印参数包内容 
//template <class ...Args>
//void Print(Args... args)
//{
// // 可变参数模板编译时解析 
// // 下⾯是运⾏获取和解析,所以不⽀持这样⽤ 
// cout << sizeof...(args) << endl;
// for (size_t i = 0; i < sizeof...(args); i++)
// {
// cout << args[i] << " ";
// }
// cout << endl;
//}
void ShowList()
{
 // 编译器时递归的终⽌条件,参数包是0个时,直接匹配这个函数 
 cout << endl;
}
template <class T, class ...Args>
void ShowList(T x, Args... args)
{
 cout << x << " ";
 // args是N个参数的参数包 
 // 调⽤ShowList,参数包的第⼀个传给x,剩下N-1传给第⼆个参数包 
 ShowList(args...);
}
// 编译时递归推导解析参数 
template <class ...Args>
void Print(Args... args)
{
 ShowList(args...);
}
int main()
{
 Print();
 Print(1);
 Print(1, string("xxxxx"));
 Print(1, string("xxxxx"), 2.2);
 return 0;
}

4.3 empalce系列接⼝

emplace系列的接口上的参数就使用到了可变参数模板概念。

• template <class… Args> void emplace_back (Args&&… args);
• template <class… Args> iterator emplace (const_iterator position, Args&&… args)

先来看一下与同功能的函数push_back()的对比

#include<list>
// emplace_back总体⽽⾔是更⾼效,推荐以后使⽤emplace系列替代insert和push系列 
int main()
{
 list<bit::string> lt;
 // 传左值,跟push_back⼀样,⾛拷⻉构造 
 bit::string s1("111111111111");
 lt.emplace_back(s1);
 cout << "*********************************" << endl;
 // 右值,跟push_back⼀样,⾛移动构造 
 lt.emplace_back(move(s1));
 cout << "*********************************" << endl;
// 直接把构造string参数包往下传,直接⽤string参数包构造string 
 // 这⾥达到的效果是push_back做不到的 ,因为push_back会发生一层隐式类型转换
 lt.emplace_back("111111111111");
 cout << "*********************************" << endl;
 list<pair<bit::string, int>> lt1;
 // 跟push_back⼀样 
 // 构造pair + 拷⻉/移动构造pair到list的节点中data上 
 pair<bit::string, int> kv("苹果", 1);
 lt1.emplace_back(kv);
 cout << "*********************************" << endl;
 // 跟push_back⼀样 
 lt1.emplace_back(move(kv));
 cout << "*********************************" << endl;
 
 // 直接把构造pair参数包往下传,直接⽤pair参数包构造pair 
 // 这⾥达到的效果是push_back做不到的 
 lt1.emplace_back("苹果", 1);
 cout << "*********************************" << endl;
 return 0;
}

总结:emplace系列基本包含insert和push_back的功能,并且在某些场景下更为优秀。

二、lambda

当我们在使用sort这种比较函数的时候,有时候会需要自己设计类里面的变量该怎么去比较,这时候我们可能需要引入仿函数的概念,但是还是会有一定的麻烦,这时候lambda就是来解决类似的问题的。

2.1.仿函数

仿函数(Functor)是一个重载了operator()的类或结构体实例,这使得它可以像函数一样被调用。仿函数的概念是C++函数对象(Function Object)的一种实现方式,它提供了比传统函数指针更丰富的功能,比如可以携带状态(通过成员变量)。

struct Add {  
    int operator()(int a, int b) const {  
        return a + b;  
    }  
};
//在这个例子中,Add是一个仿函数类型,它的实例可以像函数一样被调用:
Add add;  //定义类的对象
int result = add(3, 4); // 调用仿函数,result将是7

其实lambda的底层原理还是 仿函数,只不过当我们写了lambda之后会由编译器进行处理
C++11引入的lambda表达式进一步简化了仿函数的创建和使用,使得C++的函数式编程风格更加易于实现和理解。

struct Goods
{
 string _name; // 名字 
 double _price; // 价格 
 int _evaluate; // 评价 
 // ...
 Goods(const char* str, double price, int evaluate)
 :_name(str)
 , _price(price)
 , _evaluate(evaluate)
 {}
};
struct ComparePriceLess
{
 bool operator()(const Goods& gl, const Goods& gr)
 {
 return gl._price < gr._price;
 }
};
struct ComparePriceGreater
{
 bool operator()(const Goods& gl, const Goods& gr)
 {
 return gl._price > gr._price;
 }
};
int main()
{
 vector<Goods> v = { { "苹果", 2.1, 5 }, { "⾹蕉", 3, 4 }, { "橙⼦", 2.2, 3 
}, { "菠萝", 1.5, 4 } };
 // 类似这样的场景,我们实现仿函数对象或者函数指针⽀持商品中 
 // 不同项的⽐较,相对还是⽐较⿇烦的,那么这⾥lambda就很好⽤了 

//仿函数
 sort(v.begin(), v.end(), ComparePriceLess());
 sort(v.begin(), v.end(), ComparePriceGreater());
 //lambda
 sort(v.begin(), v.end(), [](const Goods& g1, const Goods& g2) {
 return g1._price < g2._price;
 });
 sort(v.begin(), v.end(), [](const Goods& g1, const Goods& g2) {
 return g1._price > g2._price; 
 });
 sort(v.begin(), v.end(), [](const Goods& g1, const Goods& g2) {
 return g1._evaluate < g2._evaluate;
 });
 sort(v.begin(), v.end(), [](const Goods& g1, const Goods& g2) {
 return g1._evaluate > g2._evaluate;
 });
 return 0;
}

此外再了解lambda语法的前面我们先了解一下辅助知识:
函数对象:

函数对象(Function Object)在C++中是一个非常重要的概念,它指的是任何可以像函数一样被调用的对象。这个术语涵盖了多种实体,包括但不限于普通函数、函数指针、成员函数指针、以及重载了operator()的类或结构体实例(通常称为仿函数或functor)

匿名函数对象通常通过lambda表达式(lambda function)来创建。Lambda表达式提供了一种简洁的方式来定义和使用局部函数对象,而无需显式地定义一个类

2.2.lambda表达式语法

• lambda 表达式本质是⼀个匿名函数对象,跟普通函数不同的是他可以定义在函数内部。
lambda 表达式语法使⽤层⽽⾔没有类型,所以我们⼀般是⽤auto或者模板参数定义的对象去接
收 lambda 对象。

• lambda表达式的格式: [capture-list] (parameters)-> return type { function boby }

以下是语法格式的四个组成部分

• [capture-list] :捕捉列表,该列表总是出现在 lambda 函数的开始位置,编译器根据[]来
判断接下来的代码是否为 lambda 函数,捕捉列表能够捕捉上下⽂中的变量供 lambda 函数使
⽤,捕捉列表可以传值和传引⽤捕捉,具体细节7.2中我们再细讲。捕捉列表为空也不能省略。
• (parameters) :参数列表,与普通函数的参数列表功能类似,如果不需要参数传递,则可以连
同()⼀起省略
• ->return type :返回值类型,⽤追踪返回类型形式声明函数的返回值类型,没有返回值时此
部分可省略。⼀般返回值类型明确情况下,也可省略,由编译器对返回类型进⾏推导。
• {function boby} :函数体,函数体内的实现跟普通函数完全类似,在该函数体内,除了可以
使⽤其参数外,还可以使⽤所有捕获到的变量,函数体为空也不能省略。

int main()
{
 // ⼀个简单的lambda表达式 
 auto add1 = [](int x, int y)->int {return x + y; };
 cout << add1(1, 2) << endl;
 
 // 1、捕捉为空也不能省略 
 // 2、参数为空可以省略 
 // 3、返回值可以省略,可以通过返回对象⾃动推导 
 // 4、函数题不能省略 
 auto func1 = [] 
 {
 cout << "hello bit" << endl;
 return 0;
 };
 func1();
 int a = 0, b = 1; 
 auto swap1 = [](int& x, int& y)
 {
 int tmp = x;
 x = y;
 y = tmp;
 };
 swap1(a, b);
 cout << a << ":" << b << endl;
 return 0;
}

以下是对 auto add1 = [](int x, int y)->int {return x + y; };的解释

auto:这是C++11中引入的自动类型推导关键字。在这里,它用于告诉编译器自动推导add1的类型。由于add1是一个lambda表达式,编译器会将其推导为一个独特的、匿名的函数对象类型,所以add1并不是函数名而是一个对象!!!
[]:这是lambda表达式的捕获列表。捕获列表用于指定lambda表达式体内可以访问的外部变量。在这个例子中,捕获列表为空,意味着lambda表达式体内不能访问任何外部变量(除非它们是通过值捕获或引用捕获显式地添加的)。
(int x, int y):这是lambda表达式的参数列表。它指定了lambda表达式接受的参数类型和数量。在这个例子中,lambda表达式接受两个整数参数x和y。
->int:这是lambda表达式的返回类型。它指定了lambda表达式返回值的类型。在这个例子中,lambda表达式返回一个整数。注意,如果lambda表达式的体非常简单,并且其返回类型可以从返回语句中直接推导出来,那么可以省略返回类型(但前提是编译器能够正确推导)。然而,在更复杂的lambda表达式中,或者当您希望明确指定返回类型以避免潜在的二义性时,提供返回类型是一个好习惯。
{return x + y; }:这是lambda表达式的体。它包含了lambda表达式要执行的代码。在这个例子中,lambda表达式的体只是简单地返回了两个参数的和。

2.3. 捕捉列表

• lambda 表达式中默认只能⽤ lambda 函数体和参数中的变量,如果想⽤外层作⽤域中的变量就
需要进⾏捕捉
• 第⼀种捕捉⽅式是在捕捉列表中显⽰的传值捕捉和传引⽤捕捉,捕捉的多个变量⽤逗号分割。[x,
y,&z]表⽰x和y值捕捉,z引⽤捕捉。
• 第⼆种捕捉⽅式是在捕捉列表中隐式捕捉,我们在捕捉列表写⼀个=表⽰隐式值捕捉,在捕捉列表
写⼀个&表⽰隐式引⽤捕捉,这样我们 lambda 表达式中⽤了那些变量,编译器就会⾃动捕捉那些
变量。
• 第三种捕捉⽅式是在捕捉列表中混合使⽤隐式捕捉和显⽰捕捉。[=,&x]表⽰其他变量隐式值捕捉,x引⽤捕捉;[&,x,y]表⽰其他变量引⽤捕捉,x和y值捕捉。当使⽤混合捕捉时,第⼀个元素必须是&或=,并且&混合捕捉时,后⾯的捕捉变量必须是值捕捉,同理=混合捕捉时,后⾯的捕捉变量必须是引⽤捕捉。• lambda 表达式如果在函数局部域中,他可以捕捉 lambda 位置之前定义的变量,不能捕捉静态局部变量和全局变量,静态局部变量和全局变量也不需要捕捉, lambda 表达式中可以直接使⽤。这也意味着 lambda 表达式如果定义在全局位置,捕捉列表必须为空。
默认情况下, lambda 捕捉列表是被const修饰的,也就是说传值捕捉的过来的对象不能修改,mutable加在参数列表的后⾯可以取消其常量性,也就说使⽤该修饰符后,传值捕捉的对象就可以修改了,但是修改还是形参对象,不会影响实参。使⽤该修饰符后,参数列表不可省略(即使参数为空)。

int x = 0;
// 捕捉列表必须为空,因为全局变量不⽤捕捉就可以⽤,没有可被捕捉的变量 
auto func1 = []()
{
 x++;
};
int main()
{
 // 只能⽤当前lambda局部域和捕捉的对象和全局对象 
 int a = 0, b = 1, c = 2, d = 3;
 auto func1 = [a, &b]
 {
 // 值捕捉的变量不能修改,引⽤捕捉的变量可以修改 
 //a++;
 b++;
 int ret = a + b;
 return ret;
 };
 cout << func1() << endl;
 // 隐式值捕捉 
 // ⽤了哪些变量就捕捉哪些变量 
 auto func2 = [=]
 {
 int ret = a + b + c;
 return ret;
 };
 cout << func2() << endl;
 // 隐式引⽤捕捉 
 // ⽤了哪些变量就捕捉哪些变量 
 auto func3 = [&]
 {
 a++;
 c++;
 d++;
 };
 func3();
 cout << a <<" "<< b <<" "<< c <<" "<< d <<endl;
 // 混合捕捉1 
 auto func4 = [&, a, b]
 {
 //a++;
 //b++;
 c++;
 d++;
 return a + b + c + d;
 };
 func4();
 cout << a << " " << b << " " << c << " " << d << endl;
 // 混合捕捉1 
 auto func5 = [=, &a, &b]
 {
 a++;
 b++;
 /*c++;
 d++;*/
 return a + b + c + d;
 };
 func5();
 cout << a << " " << b << " " << c << " " << d << endl;
 // 局部的静态和全局变量不能捕捉,也不需要捕捉 
 static int m = 0;
 auto func6 = []
 {
 int ret = x + m;
 return ret;
 };
 // 传值捕捉本质是⼀种拷⻉,并且被const修饰了 
 // mutable相当于去掉const属性,可以修改了 
 // 但是修改了不会影响外⾯被捕捉的值,因为是⼀种拷⻉ 
 auto func7 = [=]()mutable
 {
 a++;
 b++;
 c++;
 d++;
 return a + b + c + d;
 };
 cout << func7() << endl;
 cout << a << " " << b << " " << c << " " << d << endl;
 return 0;
}

2.3lambda原理

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

class Rate
{
public:
 Rate(double rate) 
 : _rate(rate)
 {}
 double operator()(double money, int year)
 {
 return money * _rate * year;
 }
private:
 double _rate;
};
int main()
{
 double rate =
上一篇:Linux 宝塔安装(各操作系统命令合集)


下一篇:我一口气记录下整个接口自动化测试过程!