113 C++ lambda 表达式,for_each, find_if

本文详细介绍了C++11中的lambda表达式,包括其基本形式、返回类型、参数列表、捕获列表的使用,以及与普通函数的差异。还探讨了lambda表达式的延迟调用问题及其解决方案,以及lambda表达式的类型存储和语法糖概念。

一  lambda用法简介

C++ 11引入

也是一种可调用对象

lambda表达式,它定义了一个匿名函数,并且可以捕获一定范围内的变量

基本形式 以及解释

auto 返回类型后置 语法。

void main() {
	//lambda表达式的基本形式
	auto f = [](double dou)->int {
		cout << "dou*2 = " << dou*2 << endl;
		return dou * 2;
	};
	//说明:lambda表达式是一个可调用对象
	//f是返回值
	//[]是捕获符,可以从外界捕获想要的值,例如想要使用外界的变量,需要在[]里面写东西
	//()里面是参数,表示该lambda表达式需要的参数
	//->int,表示该lambda表达式的返回值
	//{}里面是函数体
	//;记得最后加上这个分号


	//调用
	int res = f(69.9);
	cout << "res = " <<res << endl;

	//结果
	//  dou * 2 = 139.8
	//	res = 139
}

其中:-> int 可以省略,lambda表达式会根据 真正的返回值决定返回啥类型

	auto f1 = [](double dou) {
		cout << "dou*2 = " << dou * 2 << endl;
		return dou * 2;
	};
	cout << f1(3.14) << endl;

//结果

//dou*2 = 6.28
//6.28

特点:

a 是一个匿名函数,也可以理解为:可调用的代码单元。

b 它有一个返回类型,一个参数列表,一个函数体。

c 与函数不同的是:lambda表达式可以在函数内部定义,常规函数不行。例如上述代码中,我们是在main函数中写的lambda表达式。

d.一般形式说明

    //f是返回值
    //[]是捕获列表,可以从外界捕获想要的值,例如想要使用外界的变量,需要在[]里面写东西
    //()里面是参数,表示该lambda表达式需要的参数
    //->int,表示该lambda表达式的返回值
    //{}里面是函数体
    //;记得最后加上这个分号

f.因为很多时候,lambda表达式返回值特别明显,所以允许lambda表达式返回类型 可以省略。编译器可以自动推导。

g.()中的参数可以给默认值,如果给了默认值,则调用的时候可以省略参数

	auto f2 = [](double dou = 90.8) {
		cout << "dou*2 = " << dou * 2 << endl;
		return dou * 2;
	};
	cout << f2() << endl;

h 编译器并不是总能推断出 返回值类型,如果编译器推断不出来返回值类型的时候,必须显示的给出返回值类型。

i 在没有参数的的时候,小括号也可以省略。

	auto f3 = []() {
		cout << "f3 called" << endl;
	};
	f3();

	auto f4 = [] {
		cout << "f4 called " << endl;
	};
	f4();

j 捕获列表[] 和 函数体{} 必须包含。

k lambda 表达式调用和 普通函数相同

m lambda表达式 可以没有返回值

二。捕获列表

就是[]里面的东西。

通过捕获列表来捕获外边一定范围内的变量。

1. []  ---空的中括号 表示不捕获任何变量

2. 静态变量不需要捕获。

注意的是:如下的代码,局部变量在没有捕获的情况下,是不能使用的,会有build error

但是,如果是静态局部变量 或者 全局的静态变量,则可以访问,注意mage 和mage2的区别。


static int magequanju = 980;
void main(){

    int mage = 9;
	static int mage2 = 10;
	auto f5 = [] {
		mage2 = 100;//ok
        magequanju =1101010;//ok
		mage = 90;//如果不在[]中捕获外边的变量,则这一行会有build error
		cout << "f5 called " << endl;
	};
	f5();
}	

3.[&] 捕获外部作用域中所有的变量,并 作为引用 在函数体内使用

	int mage = 10;
    auto f6 = [&] {
		mage2 = 100;//ok
		mage3 = 890;
		mage = 90;//中括号中的& 表示 捕获外部作用域的所有变量,并作为引用在函数体内使用。
		cout << "f5 called " << endl;
	};
	f6();
	cout << "mage = " << mage << endl;//结果为90

这里有两个问题需要注意:

1.改动的是引用,因此在lambda表达式中的改动该值后,外边也会看到这个值变化了

2.要注意作用域,mage的作用域是在main函数内部,但是我们这个lambda表达式的调用,也就是f6(),实际上有可能是什么时候调用的,因此要特别注意在调用的时候mage的作用域

4. [=] 捕获外部作用域中的所有变量,在函数中使用,也就是可以用它的值,但是不能给赋值。

auto f7 = [=] {

		//mage = 11190;//build error 中括号中的= 表示 只能用,不能改。
		int res = mage + 80;
		cout << "f7 called res = " << res <<  endl;

	};
	f7();
	cout << "mage = " << mage << endl;//结果为90

	//f7 called res = 170
	//	mage = 90

5. [this],一般用于类中,捕获当前类中this指针,让lambda表达式有和当前类成员函数同样的访问权限,如果[]中已经使用了 & 或者 =,那么默认就已经使用了this。说白了,捕获this的目的就是为了在lambda中使用当前类的成员函数和成员变量。

class Teacher130 {
public:
	int mage = 90;

	void myfuncpt(int x, int y) {
		//这里不管是this ,还是 & 还是 = 都可以达到访问类成员mage
		auto mylambda = [this]() {
			return mage;
		};
		cout << mylambda() << endl;
	}
};

void main() {
	Teacher130 tea;
	tea.myfuncpt(10,20); //结果:90
}

6. []里面是变量名,如果是多个变量名,彼此之间用,分隔。代表的含义是按值捕获 中括号里面的变量名,不捕获不在中括号中的变量。只能访问,不能改动。

那么在 []中写= 号不是更好吗?为啥我们还要一个一个写。

这是因为如果是=号,lambda表达式会将所有的变量都存储一份,这个资源消耗较大,如果程序很大,那消耗的就更大了,而使用在[]中写变量名的方式更加合理。消耗更少。

void main() {

	int mage = 80;
	string str("nihao");
	auto f1 = [mage,str](double dou) {
		cout << "mage = " << mage << endl;
		cout << "str = " << str << endl;
		//mage = 8;//build error ,只能访问,不能修改
		cout << "dou*2 = " << dou * 2 << endl;
		return dou * 2;
	};
	f1(89);
}

7 []里面是&变量名,如果是多个变量名,彼此之间用,分隔。代表的含义是按引用捕获 中括号里面的变量名,不捕获不在中括号中的变量。可以访问,可以改动。

	auto f2 = [&mage, &str](double dou) {
		
		mage = 8;//可以访问,可以修改
		str = "nishuo";
		cout << "dou*2 = " << dou * 2 << endl;
		cout << "mage = " << mage << endl;
		cout << "str = " << str << endl;
		return dou * 2;
	};
	f2(189);

8.  [=,&变量名],按值捕获所有的变量,按引用 捕获变量名,打头的=表示默认捕获方式

	auto f3 = [=, &str](double dou) {

		//mage = 8;//不可修改
		str = "nishuo";
		cout << "dou*2 = " << dou * 2 << endl;
		cout << "mage = " << mage << endl;
		cout << "str = " << str << endl;
		return dou * 2;
	};
	f3(1189);

9.[&,变量名],按引用捕获所有的变量,按值 捕获变量名,打头的&表示默认捕获方式

	auto f4 = [&, str](double dou) {

		mage = 8;//可以修改
		//str = "nishuo"; // 不可修改
		cout << "dou*2 = " << dou * 2 << endl;
		cout << "mage = " << mage << endl;
		cout << "str = " << str << endl;
		return dou * 2;
	};
	f4(11189);

三。lambda表达式延迟调用易出错细节分析

如果是用 = 捕获的,或者用变量名捕获的,也就是值捕获,那么在lambda表达式赋值的时刻,就会将所有的变量复制一份。因此会有如下问题:

问题代码

	int xx = 10;
	auto f5 = [=]() {
		return xx;
	};
	xx = 8080;
	cout << f5() << endl; //结果是10



	int xxxx = 1000;
	auto f7 = [xxxx]() {
		return xxxx;
	};
	xxxx = 808090;
	cout << f7() << endl; //结果是1000

fix 方案:使用引用解决

	int xxx = 10;
	auto f6 = [&]() {
		return xx;
	};
	xxx = 8080;
	cout << f6() << endl; //结果是8080

四,lambda表达式中的,mutable

    //回忆一下 mutable (不容易,不稳定)修饰成员变量,用于const 的反义词。
//如果该成员变量被 mutable 修饰,表示即使函数是const ,也可以改动该值

    //假设我们这里是 值传递的,但是又想在内部改动该值,也可以使用修饰符mutable来达到这个目的。

注意的是:这个改动不会真正的改动 y 的值,改动的只是 y 的副本

	//回忆一下 mutable (不容易,不稳定)修饰成员变量,用于const 的反义词。
//如果该成员变量被 mutable 修饰,表示即使函数是const ,也可以改动该值

	//假设我们这里是 值传递的,但是又想改动该值,也可以使用修饰符mutable来达到这个目的。

	int y = 1000;
	auto f8 = [y]()mutable {
		y = 9000;
		return y*2;
	};

	cout << f8() << endl; //结果是18000
	cout << y << endl; //结果是1000

五 lambda表达式对的类型以及 存储

在C++11中,lambda表达式的类型被称呼为 “闭包类型”。

闭包:可以理解为函数内的函数,也是可调用对象,本质上就是lambda表达式创建的运行时期的对象。

lambda表达式是一种比较特殊的,匿名的,类类型【闭包类】的对象(也就是定义一个类型,又生成一个匿名的该类 类型的对象)。

我们可以认为 它是一个带有 operator()的类类型对象。也就是仿函数。

所以,我们也可以用 std::function 和 std::bind来保存和调用 lambda表达式。

每个lambda表达式都会触发编译器生成一个 独一无二的类类型,以及返回给我们一个这个类类型的对象。

lambda表达式这种语法,让我们可以就地定义匿名函数(就地封装短小的功能闭包)

void main() {
	//使用function 接受 lambda的返回值,并调用
	std::function<int(int)> f1 = [](int vt) {
		return vt * 2;
	};
	cout << f1(8880) << endl;

	//使用bind 将 lambda表达式和参数组合。bind 的作用是1.能够将对象以及相关的参数绑定在一起,绑定完后可以直接调用。
	auto b2 = bind([](int vt) {return vt * 7; }, std::placeholders::_1);
	std::function<int(int)> f2 = b2;
	cout << f2(888888) << endl;
	//结果:17760
	//	6222216

}

        语法糖概念     

int a [5];

a[1] = 90;// 这个就是语法糖。

实际写法应该是: *(a+1) = 90;

语法糖的意思是:让程序员写代码更加方便。

所以 lambda表达式,可以看成是顶一个仿函数闭包(函数中的函数)的语法糖。

六 不捕获任何变量的lambda表达式,也就是捕获列表为空,可以转换成一个普通的函数指针。

	using functype = int(*)(int);
	functype fp = [](int ve) {return ve * 2; };
	cout << fp(890) << endl;

  

七  lambda 表达式再演示和优点聪姐

for_each简介

是一个函数模版,

需要添加  #include <algorithm>

一般是用来配合函数对象使用的

void func132(int value) {
	cout << "value = "  << value << endl;
}
void main() {
	//for_each的使用
	vector<int> vec = {10,20,30,40,50,60};
	for_each(vec.begin(),vec.end(),func132);

	//for_each 结合 lambda表达式的使用
	int sum = 0;
	for_each(vec.begin(), vec.end(), [&sum](int value) {
		sum = sum + value;
	});

	cout << "sum = " << sum << endl;
}

find_if简介

函数模版,用来查找一个东西,查找什么,取决于第三个参数

find_if的第三个参数只要返回false,就会不停的遍历 vector,直到查找到返回 true为止。或者遍历完毕后没有找到。

	//find_if 返回值:范围[first, last) 中首个满足以下条件的迭代器 it,或者在没有满足条件的迭代器时返回 last
	auto result = find_if(vec.begin(), vec.end(), [](int value) {
		if (value %2 ==0 ) {
			return true;
		}
		return false;
	});
	cout << "result = " << *result << endl;

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值