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; } ```

相关推荐
mghio2 小时前
Dubbo 中的集群容错
java·微服务·dubbo
咖啡教室7 小时前
java日常开发笔记和开发问题记录
java
咖啡教室7 小时前
java练习项目记录笔记
java
鱼樱前端8 小时前
maven的基础安装和使用--mac/window版本
java·后端
RainbowSea8 小时前
6. RabbitMQ 死信队列的详细操作编写
java·消息队列·rabbitmq
RainbowSea9 小时前
5. RabbitMQ 消息队列中 Exchanges(交换机) 的详细说明
java·消息队列·rabbitmq
我不会编程55510 小时前
Python Cookbook-5.1 对字典排序
开发语言·数据结构·python
李少兄10 小时前
Unirest:优雅的Java HTTP客户端库
java·开发语言·http
此木|西贝10 小时前
【设计模式】原型模式
java·设计模式·原型模式