Lambda表达式从用到底层原理-创新互联

文章目录
  • 前言
  • 一、lambda函数基本使用
      • 参数列表
      • 返回类型
      • 函数体
      • 捕获列表
            • 值捕获
            • 引用捕获
            • 隐式捕获
            • 混合方式捕获
            • 修改值捕获变量的值
            • 异常说明
  • 二、lambda表达式使用的注意事项
      • 避免默认捕获模式
  • 三、lambda表达式底层实现原理
      • 采用值捕获
      • 采用引用捕获

创新互联公司专业为企业提供阿拉山口网站建设、阿拉山口做网站、阿拉山口网站设计、阿拉山口网站制作等企业网站建设、网页设计与制作、阿拉山口企业网站模板建站服务,10余年阿拉山口做网站经验,不只是建网站,更提供有价值的思路和整体网络服务。
前言

lambda式作为一种创建函数对象的手段,实在太过方便,对c++日常软件开发产生极大影响,所以特来学习。


一、lambda函数基本使用

lambda函数是C++11标准新增的语法糖,也称为lambda表达式或匿名函数。
lambda函数的特点是:距离近、简洁、高效和功能强大。
示例:

[](const int& no) ->void {cout<< "亲爱的"<< no<< "号:我是一只傻傻鸟。\n"; };

语法:
在这里插入图片描述

参数列表

参数列表是可选的,类似普通函数的参数列表,如果没有参数列表,()可以省略不写。
与普通函数的不同:

  • lambda函数不能有默认参数。
  • 所有参数必须有参数名。
  • 不支持可变参数。
返回类型

用后置的方法书写返回类型,类似于普通函数的返回类型,如果不写返回类型,编译器会根据函数体中的代码推断出来。
如果有返回类型,建议显式的指定,自动推断可能与预期不一致。

auto f=[](const int& no) ->double{cout<< "亲爱的"<< no<< "号:我是一只傻傻鸟。\n";
};

此时auto判定f为double类型;

函数体

类似于普通函数的函数体。

捕获列表

通过捕获列表,lambda函数可以访问父作用域中的非静态局部变量(静态局部变量可以直接访问,不能访问全局变量)。
捕获列表书写在[]中,与函数参数的传递类似,捕获方式可以是值和引用。
以下列出了不同的捕获列表的方式。
在这里插入图片描述

值捕获

与传递参数类似,采用值捕获的前提是变量可以拷贝。
与传递参数不同,变量的值是在lambda函数创建时拷贝,而不是调用时拷贝。
例如:

size_t v1 = 42;
auto f = [ v1 ]  {return v1; };	// 使用了值捕获,将v1拷贝到名为f的可调用对象。
v1 = 0;
auto j = f();    // j为42,f保存了我们创建它是v1的拷贝。

由于被捕获的值是在lambda函数创建时拷贝,因此在随后对其修改不会影响到lambda内部的值。
默认情况下,如果以传值方式捕获变量,则在lambda函数中不能修改变量的值。

引用捕获

和函数引用参数一样,引用变量的值在lambda函数体中改变时,将影响被引用的对象。

size_t v1 = 42;
auto f = [ &v1 ]  {return v1; };	 // 引用捕获,将v1拷贝到名为f的可调用对象。
v1 = 0;
auto j = f();	   // j为0。

如果采用引用方式捕获变量,就必须保证被引用的对象在lambda执行的时候是存在的。

隐式捕获

除了显式列出我们希望使用的父作域的变量之外,还可以让编译器根据函数体中的代码来推断需要捕获哪些变量,这种方式称之为隐式捕获。
隐式捕获有两种方式,分别是[=]和[&]。[=]表示以值捕获的方式捕获外部变量,[&]表示以引用捕获的方式捕获外部变量。

int a = 123;
auto f = [ = ]  {cout<< a<< endl; };		//值捕获
f(); 	// 输出:123
auto f1 = [ & ] {cout<< a++<< endl; }; 		//引用捕获
f1();	//输出:123(采用了后++)
cout<< a<< endl; 		//输出 124
混合方式捕获

lambda函数还支持混合方式捕获,即同时使用显式捕获和隐式捕获。
混合捕获时,捕获列表中的第一个元素必须是 = 或 &,此符号指定了默认捕获的方式是值捕获或引用捕获。
需要注意的是:显式捕获的变量必须使用和默认捕获不同的方式捕获。例如:

int i = 10;
	int  j = 20;
	auto f1 = [ =, &i] () {return j + i; };		// 正确,默认值捕获,显式是引用捕获
	auto f2 = [ =, i] () {return i + j; };		// 编译出错,默认值捕获,显式值捕获,冲突了
	auto f3 = [ &, &i] () {return i +j; };		// 编译出错,默认引用捕获,显式引用捕获,冲突了
修改值捕获变量的值

在lambda函数中,如果以传值方式捕获变量,则函数体中不能修改该变量,否则会引发编译错误。
在lambda函数中,如果希望修改值捕获变量的值,可以加mutable选项,但是,在lambda函数的外部,变量的值不会被修改。

int a = 123;
    auto f = [a]()mutable {cout<< ++a<< endl; }; // 不会报错
    cout<< a<< endl; 	// 输出:123
    f(); 					// 输出:124
    cout<< a<< endl; 	// 输出:123
异常说明

lambda可以抛出异常,用throw(…)指示异常的类型,用noexcept指示不抛出任何异常。

二、lambda表达式使用的注意事项 避免默认捕获模式

按引用的默认捕获模式可能导致空悬引用,一旦由lambda式所创建的闭包越过了局部变量或形参的生命周期,那么闭包内的引用就会空悬(即必须保证被引用的对象在lambda执行的时候是存在的)
(有没有空悬引用其实就是看的生命周期,那个长)

既然引用有导致空悬引用的风险,那是不是可以用按值捕获呢。按值的默认捕获也有可能存在空悬的风险。如按值捕获了一个指针以后,在lambda式创建的闭包中持有的是这个指针的副本,但并无办法阻止lambda式之外的代码去针对该指针实施delete操作所导致的指针副本空悬。

对于类的方法中使用lambda,如果使用到了类的成员变量,则会出现无法被捕获的错误。如下:

void Widget::addFilter() const
{filters.emplace_back(
		[divisor](int value)		//错误
		{return value % divisor==0;}	//局部没有可捕获的divisor(divisor既不是局部变量,也不是形参)
	);
}

解决这一问题,关键在于一个裸指针隐式应用,这就是this。每一个非静态成员函数都持有一个this指针,然后每当提及该类的成员变量时都会用到这个指针。
所以此上的代码的lambda函数被捕获的实际上是Widget的this指针,而不是divisor。
代码如下 :

void Widget::addFilter() const
{auto currentObjectPtr=this;
	filters.emplace_back(
		[currentObjectPtr](int value)
		{return value%currentObjectPtr->divisor==0;}
	);
}

这就相当于lambda闭包的存活与它含有其this指针副本的Widget对象的生命期是绑在一起的

对于以static声明的静态变量,可以在lambda内使用,但是它们不能被捕获

三、lambda表达式底层实现原理
class Add{public:
	Add(int n):_a(n){}

	int operator()(int n){return _a + n;
	}
private:
	int _a;
};

int main(){int n = 2;
	Add a(n);
	a(4);

	auto a2 = [=](int m)->int{return n + m; };
	a2(4);
	return 0;
} 

从上面的代码中可以看到,仿函数与lambda表达式完全一样
在这里插入图片描述
实际当我们编写了一个lambda表达式之后,编译器将该表达式翻译成一个未命名类的未命名对象。该类含有一个operator()。
整个lamda表达式,编译的时候,

  1. 编译器给你自动生成一个形如的类
  2. 然后把捕获列表中的参数,都按照你的要求(值捕获, 引用捕获)包装到这个类的成员里面
  3. 编译器生成一个 operator() 重载函数, 最后你对lamda的调用就是对函数对象的调用了, 捕获的参数早给你准备好了
采用值捕获

采用值捕获时,lambda函数生成的类用捕获变量的值初始化自己的成员变量。
例如:

int a =10;
int b = 20;
auto addfun = [=] (const int c ) ->int {return a+c; };
int c = addfun(b);    
cout<< c<< endl;

等同于:

class Myclass
{int m_a;		// 该成员变量对应通过值捕获的变量。
public:
	Myclass( int a ) : m_a(a){};	// 该形参对应捕获的变量。
	// 重载了()运算符的函数,返回类型、形参和函数体都与lambda函数一致。
	int operator()(const int c) const
	{return a + c;
	}
};

默认情况下,由lambda函数生成的类是const成员函数,所以变量的值不能修改。如果加上mutable,相当于去掉const。这样上面的限制就能讲通了。

采用引用捕获

如果lambda函数采用引用捕获的方式,编译器直接引用就行了。
唯一需要注意的是,lambda函数执行时,程序必须保证引用的对象有效。

你是否还在寻找稳定的海外服务器提供商?创新互联www.cdcxhl.cn海外机房具备T级流量清洗系统配攻击溯源,准确流量调度确保服务器高可用性,企业级服务器适合批量采购,新人活动首月15元起,快前往官网查看详情吧

分享题目:Lambda表达式从用到底层原理-创新互联
当前链接:https://www.cdcxhl.com/article38/dsppsp.html

成都网站建设公司_创新互联,为您提供网站设计公司网站改版品牌网站制作网站导航域名注册商城网站

广告

声明:本网站发布的内容(图片、视频和文字)以用户投稿、用户转载内容为主,如果涉及侵权请尽快告知,我们将会在第一时间删除。文章观点不代表本网站立场,如需处理请联系客服。电话:028-86922220;邮箱:631063699@qq.com。内容未经允许不得转载,或转载时需注明来源: 创新互联

h5响应式网站建设