C++11中的新特性(2)

C++11

  • [1 可变参数模板](#1 可变参数模板)
  • [2 emplace_back函数](#2 emplace_back函数)
  • [3 lambda表达式](#3 lambda表达式)
    • [3.1 捕捉列表的作用](#3.1 捕捉列表的作用)
    • [3.2 lambda表达式底层原理](#3.2 lambda表达式底层原理)
  • [4 包装器](#4 包装器)
  • [5 bind函数的使用](#5 bind函数的使用)

1 可变参数模板

在C++11之前,模板利用class关键字定义了几个参数,那么我们在编译推演中,我们就必须传入对应的参数,如下图函数模板所示(类模板也是一样的,这里就以函数模版为例)

出现上述错误的原因就是,缺少了T3这个参数,隐式实例化模版推演不出来!我们可以采用显式实例化!

我们可以发现这样的模版就存在一定的局限性,难道不可以根据我所传的参数,自动的匹配出对应有几个模版参数吗?所以在C++11中,我们就引入了可变的模版参数包来解决这一难题!

那么我们是如何获取到参数包中传过来的参数的呢?我们就以递归函数方式展开函数包来理解!

cpp 复制代码
//递归的终止条件
void test()
{
	cout << endl;
}
template<class T, class ...Args>
void test(T t, Args ...arg)
{
	cout << t << endl;
	test(arg...);
}
//声明Args这是一个模版参数包 可以传过来0到任意个模版参数
template<class ...Args>
//arg就是函数形参参数包
void test(Args ...arg)
{
	//开始递归
	test(arg...);
}
int main()
{
	test(1, 'a',"ggg");
	return 0;
}

也就是说通过递归函数,我们可以一个一个的获得函数包中的参数!其实函数参数包是我们在语法层上的理解,事实上,对于所要传过来的参数,编译器就会实例化好对应的模版!如下图所示,在编译器的眼里其实只存在对应的类型参数!

所以在C++11之后,STL中的容器利用可变参数包通常结合我们的万能引用,以及完美转发从而保持我们要传入的是左值还是右值,比如下面所要介绍的emplace_back函数就是这样做的!

2 emplace_back函数

有些人常常说emplace_back函数效率更高,那么到底更高在哪里呢?其实在引用右值引用之后,对于深拷贝的有移动构造的对象,emplace_back与push_back函数效率其实都是相差不大的!但是对于那些浅拷贝的对象来说,利用emplace_back插入只需要直接调用构造函数,而利用push_back需要调用构造+拷贝构造!所以综合以上所述,emplace_back效率更高,我们更推荐使用emplace_back进行插入!

3 lambda表达式

在某些场景下,我们需要对对象进行比较与排序,但是对于我们选择不同的方式去进行比较!就需要写多个仿函数,为了解决这个问题,就引入了lambda表达式来解决这一问题!

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

capture-list\]指的是捕捉列表! (parameters)是参数列表与普通函数的参数列表一样,如果不需要参数传递,那么可以省略! mutable:lambda函数总是一个const函数,mutable可以取消其常量性。 -\>return-type指的就是一个返回值,没有返回值,可以不写,如果有明确的返回值,可以由编译器自行推导! {statement}指的就是一个函数体,在该函数体内,除了可以使用其参数外,还可以使用所有捕获到的变量。 也就是说在C++11中最简单的lambda表达式就是\[\]{},但是该表达式不能处理任何事情! 以下面的代码为示例: ```cpp int main() { int a = 6, b = 4; // 拷贝x到捕捉列表中,利用mutable取消拷贝后x的常性,可以改变x的拷贝值 int x = 10; auto add_x = [x](int a)mutable{x *= 2;return a + x;}; cout << add_x(10) << endl; return 0; } ``` 可以发现,lambda表达式实质上就是一个匿名函数!我们需要通过auto将其赋值给一个变量,然后才可以显式的进行调用! ### 3.1 捕捉列表的作用 捕捉列表描述了上下文中那些数据可以被lambda使用,以及使用的方式传值还是传引用。 > 1️⃣ \[var\]:表示值传递方式捕捉变量var > > 2️⃣\[=\]:表示值传递方式捕获所有父作用域中的变量(包括this) > > 3️⃣\[\&var\]:表示引用传递捕捉变量var > > 4️⃣\[\&\]:表示引用传递捕捉所有父作用域中的变量(包括this) > > 5️⃣\[this\]:表示值传递方式捕捉当前的this指针 需要注意的是: 1 语法上捕捉列表可由多个捕捉项组成,并以逗号分割。比如:\[=, \&a, \&b\]:以引用传递的方式捕捉变量a和b,值传递方式捕捉其他所有变量 2 捕捉列表不允许变量重复传递,否则就会导致编译错误。比如:\[=, a\]:=已经以值传递方式捕捉了所有变量,捕捉a重复 3 在块作用域以外的lambda函数捕捉列表必须为空,即全局lambda函数的捕捉列表必须为空。在块作用域中的lambda函数仅能捕捉父作用域中的局部变量,捕捉任何非此作用域或者非局部变量都会导致编译报错,一对{}就组成了块作用域! 4 lambda表达式之间不能相互赋值,即使看起来类型相同 ### 3.2 lambda表达式底层原理 实际上,lambda底层所用到就是去调用operator()函数,也就是说是仿函数!以下面中的代码为例! ```cpp 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 = 0.49; Rate r1(rate); r1(10000, 2); // lamber表达式 auto r2 = [=](double monty, int year)->double {return monty * rate * year;}; r2(10000, 2); return 0; } ``` ![在这里插入图片描述](https://file.jishuzhan.net/article/1796745616357855233/edb5c2f1325d77907953ca458775d432.webp) 了解完底层是啥样的之后,我们再来解释以下为什么一样的lambda表达式不能相互赋值呢?原因就是他们其实不是同样的类型! ```cpp int main() { int x = 10; int y = 20; auto add1 = [=] {return x + y; }; auto add2 = [=] {return x + y; }; cout << typeid(add1).name() << endl; cout << typeid(add2).name() << endl; return 0; } ``` ![在这里插入图片描述](https://file.jishuzhan.net/article/1796745616357855233/fcac27235091436814cd0e81325e31e1.webp) 我们可以发现,类型都不是一样的了,所以表达式一样的lambda不能相互赋值的!关于lambda可以参考这篇文章中所讲述的[lambda详解](https://blog.csdn.net/weixin_53306029/article/details/131785343) ## 4 包装器 也就是我们所说的function包装器,它有什么作用呢?我们先来看这样一句代码: ```cpp ret = func(x); ``` 在结合我们所学过的仿函数,以及lambda表达式,你觉得func是函数指针,还是仿函数对象,还是lambda表达式!所以如果有下面这样类似的代码,就会导致模版的效率低下! ```cpp template T useF(F f, T x) { static int count = 0; cout << "count:" << ++count << endl; cout << "count:" << &count << endl; return f(x); } double f(double i) { return i / 2; } struct Functor { double operator()(double d) { return d / 3; } }; int main() { // 函数名 cout << useF(f, 11.11) << endl; // 函数对象 cout << useF(Functor(), 11.11) << endl; // lamber表达式 cout << useF([](double d)->double{ return d/4; }, 11.11) << endl; return 0; } ``` 这样子我们的useF模版就会实例化成3个,那么如何解决这样一个问题,就是利用一个包装器,将上述的三种类型都变成包装器的对象!生成一个模版就行了!改进如下: > std::function在头文件functional中 > > 类模板原型如下 template function; // undefined > > template \ class > > function\; > > 模板参数说明: > > Ret: 被调用函数的返回类型 > > Args...:被调用函数的形参 ```cpp #include template T useF(F f, T x) { static int count = 0; cout << "count:" << ++count << endl; cout << "count:" << &count << endl; return f(x); } double f(double i) { return i / 2; } struct Functor { double operator()(double d) { return d / 3; } }; int main() { // 函数名 std::function f1 = f; cout << useF(f1, 11.11) << endl; // 函数对象 std::function f2 = Functor(); cout << useF(f2, 11.11) << endl; // lamber表达式 std::function f3 = [](double d)->double { return d / 4; }; cout << useF(f3, 11.11) << endl; return 0; } ``` 所以此时模版就只会实例化成一份包装器的模版参数! ## 5 bind函数的使用 bind函数作用一般就是绑定函数,然后交换参数的顺序!使用方法如下所示: ```cpp #include int Plus(int a, int b) { return a + b; } class Sub { public: int sub(int a, int b) { return a - b; } }; int main() { //表示绑定函数plus 参数分别由调用 func1 的第一,二个参数指定 std::function func1 = std::bind(Plus, placeholders::_1, placeholders::_2); //auto func1 = std::bind(Plus, placeholders::_1, placeholders::_2); // placeholders::_1就表示是函数中的第一个参数,依次类推 //表示绑定函数 plus 的第一,二为: 1, 2 cout << func1(1, 2) << endl; Sub s; // 绑定成员函数,需要取类中成员函数的地址 std::function func3 = std::bind(&Sub::sub, s, placeholders::_1, placeholders::_2); // 参数调换顺序 std::function func4 = std::bind(&Sub::sub, s, placeholders::_2, placeholders::_1); cout << func3(1, 2) << endl; cout << func4(1, 2) << endl; return 0; } ```

相关推荐
慌糖4 分钟前
RabbitMQ:消息队列的轻量级王者
开发语言·javascript·ecmascript
风象南11 分钟前
SpringBoot 控制器的动态注册与卸载
java·spring boot·后端
醇醛酸醚酮酯29 分钟前
Qt项目锻炼——TODO清单(二)
开发语言·数据库·qt
jioulongzi35 分钟前
记录一次莫名奇妙的跨域502(badgateway)错误
开发语言·python
我是一只代码狗37 分钟前
springboot中使用线程池
java·spring boot·后端
hello早上好1 小时前
JDK 代理原理
java·spring boot·spring
PanZonghui1 小时前
Centos项目部署之Java安装与配置
java·linux
向阳@向远方1 小时前
第二章 简单程序设计
开发语言·c++·算法
沉着的码农1 小时前
【设计模式】基于责任链模式的参数校验
java·spring boot·分布式
Mr_Xuhhh2 小时前
信号与槽的总结
java·开发语言·数据库·c++·qt·系统架构