一、lambda表达式

1.lambda表达式语法

        lambda表达式本质是一个匿名函数对象,和普通函数不同的是他可以定义在函数内部。lambda 表达式语法使用层而言没有类型,所以我们一般是用 auto 或者模板参数定义的对象去接 收 lambda对象。

        lambda表达式的格式:

 [ capture-list ] (parameters)-> return type { function boby }

        [ capture-list] :捕捉列表,该列表总是出现在 lambda 函数的开始位置,编译器根据[]来判断接下来的代码是否为 lambda 函数,捕捉列表能够捕捉上下文中的变量供 lambda 函数使用,捕捉列表可以传值和传引用捕捉,具体细节下面细讲。捕捉列表为空也不能省略。

        ( parameters) :参数列表,与普通函数的参数列表功能类似,如果不需要参数传递,则可以连 同()一起省略

        ->return type :返回值类型,用追踪返回类型形式声明函数的返回值类型,没有返回值时此部分可省略。一般返回值类型明确情况下,也可省略,由编译器对返回类型进行推导。

        { function boby} :函数体,函数体内的实现跟普通函数完全类似,在该函数体内,除了可以使用其参数外,还可以使用所有捕获到的变量,函数体为空也不能省略。

int main()
{
	auto add1 = [](int x, int y)->int {return x + y;};
	cout << add1(1, 2) << endl;
	// 1.捕捉为空也不能省略
	// 2.参数为空可以省略
	// 3.返回值可以省略,可以通过返回对象自动推导
	// 4.函数体不能省略
	auto func1 = []
		{
			cout << "hello word" << endl;
			return 0;
		};
	func1();
	return 0;
}

2.捕捉列表

        • lambda 表达式中默认只能用 lambda 函数体和参数中的变量,如果想用外层作用域中的变量就需要进行捕捉

        • 第一种捕捉方式是在捕捉列表中显示的传值捕捉和传引用捕捉,捕捉的多个变量用逗号分割。[x, y,&z]表示 x 和 y 传值捕捉,z 引捕捉。

        • 第二种捕捉方式是在捕捉列表中隐式捕捉,我们在捕捉列表写一个 = 表示隐式值捕捉,在捕捉列表写一个 & 表示隐式引用捕捉,这样我们 lambda 表达式中用了哪些变量,编译器就会自动捕捉哪些变量。

        • 第三种捕捉方式是在捕捉列表中混合使用隐式捕捉和显示捕捉。[=,&x]表示其他变量隐式值捕捉, x引用捕捉;[&,x,y]表示其他变量引用捕捉,x和y 传值捕捉。当使用混合捕捉时,第一个元素必须是 & 或 =,并且 & 混合捕捉时,后面的捕捉变量必须是值捕捉,同理 = 混合捕捉时,后面的捕捉变量必须是引用捕捉。

        • lambda 表达式如果在函数局部域中,他可以捕捉 lambda 位置之前定义的变量,不能捕捉静态局部变量和全局变量,静态局部变量和全局变量也不需要捕捉, lambda 表达式中可以直接使用。这也意味着 lambda 表达式如果定义在全局位置,捕捉列表必须为空。

        • 默认情况下, lambda 捕捉列表是被 const 修饰的,也就是说传值捕捉的过来的对象不能修改, mutable 加在参数列表的后面可以取消其常量性,也就是说使用该修饰符后,传值捕捉的对象就可以修改了,但是修改还是形参对象,不会影响实参。使用该修饰符后,参数列表不可省略(即使参数为空)。

int x = 0;

int main()
{
	// 只能用当前lambda局部域和捕捉的对象和全局对象
	int a = 0, b = 1, c = 2, d = 3;

	auto func1 = [a, &b]() 
	{
		// 值捕捉的变量不能修改,引用捕捉的变量可以修改
		// a++;
		b++;
		x++;
		int ret = a + b;
		return ret;
	};

	cout << func1() << endl;

	// 隐式值捕捉
	// 用了哪些变量就捕捉哪些变量
	auto func2 = [=]
	{
		int ret = a + b + c;
		return ret;
	};
	cout << func2() << endl;

	// 隐式引用捕捉
	// 用了哪些变量就捕捉哪些变量
	auto func3 = [&]
	{
		a++;
		c++;
		d++;
	};
	func3();
	cout << a << " " << b << " " << c << " " << d << endl;

	// 混合捕捉
	auto func4 = [&, a, b]
	{
		//a++;
		//b++;
		c++;
		d++;
		return a + b + c + d;
	};
	func4();
	cout << a << " " << b << " " << c << " " << d << endl;

	// 混合捕捉
	auto func5 = [=, &a, &b]
	{
		a++;
		b++;
		/*c++;
		d++;*/
		return a + b + c + d;
	};
	func5();
	cout << a << " " << b << " " << c << " " << d << endl;

	return 0;
}

3.lambda的应用

        在学习 lambda 表达式之前,我们使用的可调用对象只有函数指针和仿函数对象,函数指针的类型定义起来比较麻烦,仿函数要定义一个类,相对会比较麻烦。使用 lambda 去定义可调用对象,既方便又简单

        lambda 在很多其他地方用起来也很好用。比如线程中定义线程的执行函数逻辑,智能指针中定制删除器等, lambda 的应用还是很广泛的。

#include<vector>
#include<algorithm>
struct Goods
{
	string _name; // 名字
	double _price; // 价格
	int _evaluate; // 评价
	// ...

	Goods(const char* str, double price, int evaluate)
		:_name(str)
		, _price(price)
		, _evaluate(evaluate)
	{
	}
};

struct Compare1
{
	bool operator()(const Goods& gl, const Goods& gr)
	{
		return gl._price < gr._price;
	}
	/*bool operator()(const Goods& gl, const Goods& gr)
	{
		return gl._price < gr._price;
	}*/
};

struct Compare2
{
	bool operator()(const Goods& gl, const Goods& gr)
	{
		return gl._price > gr._price;
	}
};

struct Compare3
{
	bool operator()(const Goods& gl, const Goods& gr)
	{
		return gl._evaluate > gr._evaluate;
	}
};

int main()
{
	vector<Goods> v = { { "苹果", 2.1, 5 }, { "香蕉", 3, 4 }, { "橙子", 2.2, 3}, { "菠萝", 1.5, 4 } };
	sort(v.begin(), v.end(), Compare1());
	sort(v.begin(), v.end(), Compare2());
	sort(v.begin(), v.end(), Compare3());

	auto priceLess = [](const Goods& gl, const Goods& gr)
		{
			return gl._price < gr._price;
		};

	sort(v.begin(), v.end(), priceLess);

	sort(v.begin(), v.end(), [](const Goods& gl, const Goods& gr){
			return gl._price > gr._price;
		});

	sort(v.begin(), v.end(), [](const Goods& gl, const Goods& gr) {
		return gl._evaluate > gr._evaluate;
		});

	sort(v.begin(), v.end(), [](const Goods& gl, const Goods& gr) {
		return gl._evaluate < gr._evaluate;
		});
}

4.lambda的原理

        lambda 的原理和范围for很像,编译后从汇编指令层的角度看,压根就没有 lambda 和范围for这样的东西。范围for底层是迭代器,而 lambda 底层是仿函数对象,也就说我们写了一个lambda 以后,编译器会生成一个对应的仿函数的类。

        仿函数的类名是编译按一定规则生成的,保证不同的 lambda 生成的类名不同,lambda参数/返回类型/函数体就是仿函数operator()的参数/返回类型/函数体, lambda 的捕捉列表本质是生成的仿函数类的成员变量,也就是说捕捉列表的变量都是 lambda 类构造函数的实参,当然隐式捕捉,编译器要看使用哪些对象就传哪些对象。

• 上面的原理,我们可以透过汇编层了解一下,下面汇编层代码印证了上面的原理。

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;

	// lambda
	auto r2 = [rate](double money, int year) {
		return money * rate * year;
	};

	// 函数对象
	Rate r1(rate);
	r1(10000, 2);
	r2(10000, 2);
}

// 汇编代码:
	// lambda
	auto r2 = [rate](double money, int year) {
		return money * rate * year;
	};
00007FF70CE51A2B  lea         rdx,[rate]  
00007FF70CE51A2F  lea         rcx,[r2]  
00007FF70CE51A33  call        `main'::`2'::<lambda_1>::<lambda_1> (07FF70CE517B0h)  
00007FF70CE51A38  nop  

	// 函数对象
	Rate r1(rate);
00007FF70CE51A39  movsd       xmm1,mmword ptr [rate]  
00007FF70CE51A3E  lea         rcx,[r1]  
00007FF70CE51A42  call        Rate::Rate (07FF70CE510C8h)  
00007FF70CE51A47  nop  
	r1(10000, 2);
00007FF70CE51A48  mov         r8d,2  
00007FF70CE51A4E  movsd       xmm1,mmword ptr [__real@40c3880000000000 (07FF70CE5ACD0h)]  
00007FF70CE51A56  lea         rcx,[r1]  
00007FF70CE51A5A  call        Rate::operator() (07FF70CE51136h)  
00007FF70CE51A5F  nop  
	r2(10000, 2);
00007FF70CE51A60  mov         r8d,2  
00007FF70CE51A66  movsd       xmm1,mmword ptr [__real@40c3880000000000 (07FF70CE5ACD0h)]  
00007FF70CE51A6E  lea         rcx,[r2]  
00007FF70CE51A72  call        `main'::`2'::<lambda_1>::operator() (07FF70CE51870h)  
00007FF70CE51A77  nop  
}

        汇编层可以看到 r2 lambda 对象调用本质还是调用 operator() ,类型是 lambda_1, 这个类型名的规则是编译器自己定制的,保证不同的 lambda 不冲突

二、包装器

1.function

// 官方文档:
template <class T>
class function;   //undefined
    
template <class Ret, class... Args>
class function<Ret(Args...)>;

        • std::function 是一个类模板,也是一个包装器。 std::function 的实例对象可以包装存储其他的可以调用的对象,包括函数指针、仿函数、 lambda 、bind 表达式等,存储的可调用对象被称为 std::function 的目标。若 std::function不含目标,则称它为空。调用空 std::function 的目标导致抛出 std::bad_function_call(std::bad_function_call - cppreference.com) 异常。

        • 以上是 function 的原型,他被定义<functional>头件中。std::function - cppreference.com是function的官方文件链接。

        • 函数指针、仿函数、 lambda 等可调用对象的类型各不相同, std::function 的优势就是统一类型,对他们都可以进行包装,这样在很多地方就方便声明可调用对象的类型,下面的第二个代码样例展示了 std::function 作为map的参数,实现字符串和可调用对象的映射表功能。

#include <functional>
#include <vector>
using namespace std;

int f(int a, int b)
{
	return a + b;
}

struct Functor
{
public:
	int operator() (int a, int b)
	{
		return a + b;
	}
};

class Plus
{
public:
	Plus(int n = 10)
		:_n(n)
	{}

	static int plusi(int a, int b)
	{
		return a + b;
	}

	double plusd(double a, double b) const
	{
		return (a + b) * _n;
	}
private:
	int _n;
};

int main()
{
	// void(*pf)(int) = nullptr;
	// 
	// 包装各种可调用对象
	function<int(int, int)> f1 = f;
	function<int(int, int)> f2 = Functor();
	function<int(int, int)> f3 = [](int a, int b) {return a + b; };

	cout << f1(1, 1) << endl;
	cout << f2(1, 1) << endl;
	cout << f3(1, 1) << endl;

	vector<function<int(int, int)>> vf = { f , Functor(),[](int a, int b) {return a + b; } };
	for (auto& f : vf)
	{
		cout << f(1, 1) << endl;
	}

	// 包装静态成员函数
	// 成员函数要指定类域并且前面加&才能获取地址,一般取地址
	function<int(int, int)> f4 = &Plus::plusi;
	cout << f4(1, 1) << endl;

	function<double(Plus*, double, double)> f5 = &Plus::plusd;
	Plus ps;
	cout << f5(&ps, 1.1, 1.1) << endl;

	//function<double(Plus, double, double)> f6 = &Plus::plusd;
	function<double(const Plus&, double, double)> f6 = &Plus::plusd;
	cout << f6(Plus(), 1.1, 1.1) << endl;
	cout << f6(ps, 1.1, 1.1) << endl;

	function<double(Plus&&, double, double)> f7 = &Plus::plusd;
	cout << f7(Plus(), 1.1, 1.1) << endl;
	cout << f7(move(ps), 1.1, 1.1) << endl;

	return 0;
}

150. 逆波兰表达式求值 - 力扣(LeetCode)

// 常规代码:
class Solution {
public:
    int evalRPN(vector<string>& tokens) {
        stack<int> st;
        for(int i = 0; i < tokens.size(); ++i)
        {
            const string& s = tokens[i];

            if(s != "+" && s != "-" && s != "*" && s != "/")
            {
                st.push(stoi(s));
            }
            else
            {
                int right = st.top();
                st.pop();
                int left = st.top();
                st.pop();

                switch(s[0])
                {
                    case '+':
                    st.push(left + right);
                    break;
                    case '-':
                    st.push(left - right);
                    break;
                    case '*':
                    st.push(left * right);
                    break;
                    case '/':
                    st.push(left / right);
                    break;
                }
            }
        }
        return st.top();
    }
};

下面进行优化:

        使用 map 映射 string 和 function 的方式实现,这种方式的最大优势之一是方便扩展,假设还有其他运算,我们增加  map 中的映射即可

class Solution {
public:
    int evalRPN(vector<string>& tokens) {
        stack<int> st;
        unordered_map<string, function<int(int, int)>> opFuncMap = 
        {
            {"+",[](int x, int y){return x + y;}},
            {"-",[](int x, int y){return x - y;}},
            {"*",[](int x, int y){return x * y;}},
            {"/",[](int x, int y){return x / y;}}
        };
        for(int i = 0; i < tokens.size(); ++i)
        {
            const string& s = tokens[i];
            if(opFuncMap.count(s))
            {
                int right = st.top(); st.pop();
                int left = st.top(); st.pop();
                int ret = opFuncMap[s](left, right);
                st.push(ret);
            }
            else
            {
                st.push(stoi(s));
            }
        }
        return st.top();
    }
};

2.bind

        • bind 是一个函数模板,它也是一个可调用对象的包装器,可以把他看做一个函数适配器,对接收的 fn 可调用对象进行处理后返回一个可调用对象。 bind 可以用来调整参数个数和参数顺序。bind 也在<function>这个头文件中。

 • 调用 bind 的一般形式:

auto newCallable = bind(callable,arg_list);

其中 newCallable本身是一个可调用对象,arg_list 是一个逗号分隔的参数列表,对应给定的 callable的参数。当我们调用newCallable时,newCallable会调用callable,并传给它arg_list中的参数。

        arg_list中的参数可能包含形如 _n 的名字,其中n是一个整数,这些参数是占位符,表示 newCallable 的参数,它们占据了传递给 newCallable 的参数的位置。数值 n 表示生成的可调用对象中参数的位置:_1 为newCallable的第一个参数,_2为第二个参数,以此类推。_1 / _ 2 / _3 ....这些占位符放到placeholders的一个命名空间中。

看下面代码来理解吧,其实用起来是很简单的。

#include<functional>

using placeholders::_1;
using placeholders::_2;
using placeholders::_3;

int Sub(int a, int b)
{
	return (a - b) * 10;
}

int SubX(int a, int b, int c)
{
	return (a - b - c) * 10;
}

class Plus
{
public:
	static int plusi(int a, int b)
	{
		return a + b;
	}
	double plusd(double a, double b)
	{
		return a + b;
	}
};

int main()
{
	cout << Sub(10, 5) << endl;

	// bind 本质返回的一个仿函数对象
	// 调整参数顺序(不常用)
	// _1代表第一个实参
	// _2代表第二个实参
	// ...
	auto newSub1 = bind(Sub, _1, _2);
	auto newSub1 = bind(Sub, _2, _1);
	cout << newSub1(10, 5) << endl;

	// 调整参数个数
	auto newSub2 = bind(Sub, _1, 10);
	cout << newSub2(20) << endl;

	auto newSub3 = bind(Sub, 5, _1);
	cout << newSub3(20) << endl;

	auto newSub4 = bind(SubX, 5, _1, _2);
	cout << newSub4(10, 20) << endl;

	auto newSub5 = bind(SubX, _1, 5, _2);
	cout << newSub5(10, 20) << endl;

	auto newSub6 = bind(SubX, _1, _2, 5);
	cout << newSub6(10, 20) << endl;

	// 成员函数对象进行绑死,就不需要每次都传递了
	//function<double(Plus&&, double, double)> f6 = &Plus::plusd;
	//Plus pd;
	//cout << f6(move(pd), 1.1, 1.1) << endl;
	//cout << f6(Plus(), 1.1, 1.1) << endl;

	//// auto f7 = bind(&Plus::plusd, Plus(), _1, _2);
	//function<double(double, double)> f7 = bind(&Plus::plusd, Plus(), _1, _2);
	//cout << f7(1.1, 1.1) << endl;
	function<double(Plus&&, double, double)> f6 = &Plus::plusd;
	Plus pd;
	cout << f6(move(pd), 1.1, 1.1) << endl;
	cout << f6(Plus(), 1.1, 1.1) << endl;
	//auto f7 = bind(&Plus::plusd, _1, _2);
	function<double(double, double)> f7 = bind(&Plus::plusd,Plus(), _1, _2);
	cout << f7(1.1, 1.1) << endl;
	// 100w
	// 计算复利的lambda
	auto func1 = [](double rate, double money, int year)->double {
		double ret = money;
		for (int i = 0; i < year; i++)
		{
			ret += ret * rate;
		}
		return ret - money;
		};

	//cout << func1(0.2, 1000000, 3) << endl;
	//cout << func1(0.2, 1000000, 10) << endl;
	//cout << func1(0.2, 1000000, 30) << endl;

	// 绑死一些参数,实现出支持不同年华利率,不同金额和不同年份计算出复利的结算利息
	function<double(double)> func3_1_5 = bind(func1, 0.015, _1, 3);
	function<double(double)> func5_2_0 = bind(func1, 0.02, _1, 5);
	function<double(double)> func10_2_5 = bind(func1, 0.025, _1, 10);
	function<double(double)> func20_3_5 = bind(func1, 0.035, _1, 30);

	cout << func3_1_5(1000000) << endl;
	cout << func5_2_0(1000000) << endl;
	cout << func10_2_5(1000000) << endl;
	cout << func20_3_5(1000000) << endl;

	return 0;
}

更多推荐