当前位置: 首页 > article >正文

C++11新增内容详解(三)

一、lambda表达式

1.1基本使用格式

①捕捉列表:编译器通过[]来判断接下来的代码是否为lambda表达式;

可以捕捉上下文变量供lambda使用

②参数列表:函数参数存放地

③mutable:默认lambda表达式核心函数operetor()为const成员函数且捕捉列表中的参数全部属于const修饰,加上这个标识可以取消常量性 

使用此修饰符时,参数列表不可省略,返回值的省略不受影响

④返回值类型:没有返回值,返回值类型明确这两种情况可以省略

1.2使用时的问题

①可将lambda表达式看作一个局部匿名函数对象定义时需要加;

②表达式返回值类型不明确,现阶段只能使用auto来接收 

1.3使用举例

1.3.1两数相加

auto add1 = [](int a, int b)->int {return a + b; };
cout << add1(3, 4) << endl;

可以实现将两个参数3,4相加的目的,

之所以可以进行打印,是因为函数的返回值为int类型,可以直接传给operator<< 

1.3.2省略参数列表和返回值类型

auto func2 = []
	{
		cout << "hello world" << endl;
		cout << "hello lambda" << endl;
	};
func2();

 当无参数需要传,无返回值,且没有使用mutable时可以对参数列表和返回值类型进行省略

1.3.3特定情况下代替仿函数,提高代码可读性

假设有一个结构体为Object,两个成员变量name和price表示商品名称和价格

有一个vector对象v,其中存储了o1~o9多个Object类型对象

现在要对v进行按价格排序

sort(v.begin(),v.end(),[](const Object& o1,const Object& o2)->bool
{
    return o1.price < o2.price;//排价格升序
});

在函数参数列表中就可以写上lambda表达式

1.4捕捉列表相关问题

1.4.1捕捉列表的特点

默认使用传值捕捉,即将要捕捉的对象进行一次拷贝;如果不希望造成这一消耗可以传引用捕捉

1.4.2结合例子理解

假设此时的需求是:交换int类型变量a,b的值

已有a=0,b=1;

①不用捕捉列表,参数列表直接给int&

//由于返回值类型是确定的,所以省略
auto swap1 = [](int& a1, int& b1)
	{
		int tmp = a1;
		a1 = b1;
		b1 = tmp;
	};
swap(a, b);

在没有进行捕捉的时候,只能用当前lambda局部域对象和全局域对象 

②捕捉列表用传值捕捉(错误示例)

auto swap2 = [a,b]()mutable
	{
		int tmp = a;
		a = b;
		b = tmp;
	};
swap2();

 这是无法完成交换的,传值捕捉的本质是一种拷贝,并且被const修饰了

(mutable则为去掉const属性来实现将两个变量的值进行互换)

③引用捕捉进行修改

auto swap3 = [&a, &b]()
	{
		int tmp = a;
		a = b;
		b = tmp;
	};
swap3();

其中 “[&a, &b]”的写法不是取地址,而是进行引用捕捉

1.4.3引用列表中的特定符号

①[=] 可以实现所有父作用域的值全部传值捕捉

②[&]可以实现所有父作用域的值全部传引用捕捉

③混合捕捉:

[=,&d] d传引用捕捉,其他传值捕捉

[&,d] d传值捕捉,其他传引用捕捉

1.4补 

①  mutable默认传值为const修饰,就是防止误用导致的错误

②全局变量无需捕捉

1.5lambda表达式的底层实现

1.5.1实际类型

lambda表达式的实际类型为:名为lambda_(uuid)的类模板

1.5.1补:什么是uuid

uuid:唯一通用识别码,是一串不重复的字符串

即使是相同的实现过程,两个不同lambda表达式的uuid也是不同的,

因此,我们不能明确lambda表达式的类型,只能通过auto推导

1.5.2定义的过程和捕捉的本质

定义的过程实际上就是调用构造捕获的对象作为类成员变量,而捕捉的本质就是构造函数的初始化参数

类比于调用类的成员函数:实现一个AD类,调用其成员函数add

而lambda表达式

综上,lambda表达式本质上也是在调用函数 ,都经历开空间->压栈->计算->返回的过程

那么lambda表达式本质上在调用的是什么函数呢?

正是operator()这一仿函数

1.5.3全部捕捉时的性能改善

全部捕捉[=]时,编译器会自动推导接下来会用到的变量,然后只对这些进行捕获,从而加速你好拷贝消耗

⭐1.5.4调用过程的本质

根据1.5.3中介绍,调用过程的本质就是调用lambda_(uuid)类的operator()成员函数

二、包装器

2.1基础模板与作用

包装器最常用的是function,用来封装任意类型的可调用对象,因此,function的主要作用便是:

统一可调用对象

(常见的如①函数指针②仿函数③lambda表达式)

模板:

template <class T> function;     // undefined
template <class Ret, class... Args> class function<Ret(Args...)>;

其中的Ret模板参数表示返回值类型,这也造就了其独特的使用方式

2.2使用举例

使用前需要包头文件

#include<functional>

提前准备一个函数与一个仿函数

//函数指针
int func1(int a, int b) { return a + b; }
//仿函数
struct FUNC2 {
     int operator()(int a1,int b1)
	{
		return a1 + b1;
	}
};

 之后在main函数中调用

//函数指针
function<int(int, int)> f1 = func1;
//仿函数
function<int(int, int)> f2 = FUNC2();
//lambda表达式
function<int(int, int)> f3 = [](int a, int b)->int {return a + b; };

cout << f1(2,5) << endl;
cout << f2(3,5) << endl;
cout << f3(4,5) << endl;

根据例子可以得出结论:在通过function进行封装以后,不论这一个可调用对象一开始是什么,都可以用唯一一种调用方式来进行使用

(...................................................35.....................7.31...................................................................) 

2.3特殊情况:包装类的成员函数指针

假设有这样一个类

class Func_3 {
public:
	static int Func_3_1(int a, int b)
	{
		return a + b;
	}

	double Func_3_2(double c, double d)
	{
		return c + d;
	}
};

其中有两种成员函数,我们分别进行讨论 

2.3.1包装静态成员函数指针

成员函数按照要求必须要在函数名前加上“&”来表示使用的是成员函数的地址,其次使用时需要指明类域

除这些最基本的之外与普通函数没有任何区别

function<int(int, int)> f4 = &Func_3::Func_3_1;
cout << f4(9, 7) << endl;

2.3.2包装非静态成员函数指针

要包装非静态成员函数有一个很关键的问题:隐含参数this指针

要解决这一问题有两种方式

①方式一:定义新对象,该对象仅用于提供this指针充位

Func_3 nf3;
function<double(Func_3*,double, double)> f5 = &Func_3::Func_3_2;
cout << f5(&nf3, 9.1, 7.6) << endl;

 其实在底层上,“(&nf3, 9.1, 7.6)”这一参数列表并不是直接提供给成员函数Func_3_2的,

而是通过nf3对象来调用它的成员函数Func_3_2,所以我们实际上无需传入类指针,只要传入类编译器就可以自动推导

function<double(Func_3, double, double)> f6 = &Func_3::Func_3_2;
cout << f6(nf3, 9.2, 7.6) << endl;

②方式二:直接使用匿名对象

 其实是对①方式在效率上的改进

在f6定义后,直接使用

cout << f6(Func_3(), 9.5, 7.6) << endl;

2.4除了function外,还有第二种包装器bind

2.4.1bind是什么

bind是一个函数模板,属于std命名空间

无返回值:

template <class Fn, class... Args>
  /* unspecified */ bind (Fn&& fn, Args&&... args);

有返回值:

template <class Ret, class Fn, class... Args>
  /* unspecified */ bind (Fn&& fn, Args&&... args);

传入的是一个可调用对象和一个特定的可变模板参数 返回值为可调用对象(可能性很多所以并不明确)

 其中的特定的可变模板参数为命名空间placeholders 中的变量

namespace placeholders {
  extern /* unspecified */ _1;
  extern /* unspecified */ _2;
  extern /* unspecified */ _3;
  // ...
}

对应的_1,_2,_3...为调用bind返回值对象时,传入的参数顺序 

2.4.2结合例子理解使用方法

bind主要有两个用途:

①调整参数顺序

②调整参数个数

基础使用:

using placeholders::_1;
using placeholders::_2;
int sub(int a, int b)
{
	return (a - b) * 120;
}

int main()
{
	auto sub1 = bind(sub, _1, _2);
	cout << sub1(10, 5) << endl;
    return 0;
}

代码解析:

 

 功能一:

//作用1:调整参数顺序
auto sub2 = bind(sub, _2, _1);
cout << sub2(10, 5) << endl;//输出-600

功能二:

//作用2:调整参数个数
auto sub3 = bind(sub, 100, _1);
cout << sub3(6) << endl;//输出11280

auto sub4 = bind(sub, _1, 100);
cout << sub4(6) << endl;//输出-11280

 不难发现:函数指针之后的几个位置分别对应函数的参数列表中对应的位置

如果调用bind对象只传一个参数,那么_2无论如何也用不上

2.4.3实用场景

2.3.2中的包装可以进行简化了

可以用bind来绑死this指针位置的参数 

//原先:
function<double(Func_3, double, double)> f6 = &Func_3::Func_3_2;
cout << f6(nf3, 9.2, 7.6) << endl;


cout << f6(Func_3(), 9.5, 7.6) << endl;
//用了bind以后:
function<double(double, double)> f7 = bind(&Func_3::Func_3_2, Func_3(),_1,_2) ;
cout << f7(9.2, 7.6) << endl;

利息的计算:bind绑定lambda表达式

function<double(double)> interest = bind([](double rate,
	double money, int year)->double
	{
		return rate * money * year;
	}, 0.015, _1, 10);
cout << interest(10000) << endl;

目的是我们只需要传入money的数量,就可以计算1.5%利率下存十年的利息,

function的类型因此只需要返回值类型double和传入值类型double


http://www.kler.cn/a/593771.html

相关文章:

  • 软件测试--黑马程序员
  • ABC395题解
  • 浅谈canal实例 在docker里面安装canal镜像 Canal监听MySQL数据库变更并同步更新Redis和Elasticsearch 示例
  • 共筑智慧城市新生态!YashanDB与荣科科技完成兼容互认证
  • Hive高频SQL及典型应用场景总结
  • 【Pandas】pandas Series plot
  • STC89C52单片机学习——第28节: [12-2] AT24C02数据存储秒表(定时器扫描按键数码管)
  • 外星人入侵-Python-三
  • C++学习之nginx+fastDFS
  • 深入解析 Redis 原理:架构、数据结构与高效存储
  • 基于开源模型的微调训练及瘦身打造随身扫描仪方案__用AI把手机变成文字识别小能手
  • 【Vue3】01-vue3的基础 + ref reactive
  • Pygame实现记忆拼图游戏14
  • 实时数仓和离线数仓
  • subprocess执行系统命令简明用法
  • 「低延迟+快速集成:Amazon IVS如何重塑实时互动视频体验?」
  • Linux与HTTP中的Cookie和Session
  • 头歌实训--数据预处理Pandas--共三关
  • 黄金屋 #2 我应该将产品开源吗?
  • 雅可比行列式