C++11 -- lambda expression

Introduction of lamaba expression

  • Before C++11, if we want to sort the custom type Goods, we can sort them according to the name, price, and student number from big to small or from small to large. However, we need to write 6 additional related Functor.
  • And in the process of naming the custom functor, it is easy to cause code interpretation troubles due to irregular naming. It is impossible to understand the actual function only by the name of the functor, and we can only find the functor of the corresponding function name to understand. actual function.
struct Goods
{
    
    
	string _name;  //名字
	double _price; //价格
	int _num;      //数量
};

struct ComparePriceLess
{
    
    
	bool operator()(const Goods& g1, const Goods& g2)
	{
    
    
		return g1._price < g2._price;
	}
};
struct ComparePriceGreater
{
    
    
	bool operator()(const Goods& g1, const Goods& g2)
	{
    
    
		return g1._price > g2._price;
	}
};
struct CompareNumLess
{
    
    
	bool operator()(const Goods& g1, const Goods& g2)
	{
    
    
		return g1._num < g2._num;
	}
};
struct CompareNumGreater
{
    
    
	bool operator()(const Goods& g1, const Goods& g2)
	{
    
    
		return g1._num > g2._num;
	}
};
int main()
{
    
    
	vector<Goods> v = {
    
     {
    
     "苹果", 2, 20 }, {
    
     "香蕉", 3, 30}, {
    
     "橙子", 4,40 }, {
    
     "菠萝", 5,50 } };
	sort(v.begin(), v.end(), ComparePriceLess());    //按Goods价格升序排序
	sort(v.begin(), v.end(), ComparePriceGreater()); //按Goods价格降序排序
	sort(v.begin(), v.end(), CompareNumLess());      //按Goods升序排序
	sort(v.begin(), v.end(), CompareNumGreater());   //按Goods降序排序
	return 0;
}


After C++11, we can use the lamada expression to solve the problem. The lamada expression is actually an anonymous function, so that we can directly understand the comparison method of sort through the lamada expression, thereby improving the readability of the code. sex.

int main()
{
    
    
	vector<Goods> v = {
    
     {
    
     "苹果", 2.1, 300 }, {
    
     "香蕉", 3.3, 100 }, {
    
     "橙子", 2.2, 1000 }, {
    
     "菠萝", 1.5, 1 } };
	sort(v.begin(), v.end(), []( const Goods& g1,const Goods& g2) {
    
    return g1._price < g2._price; });
	sort(v.begin(), v.end(), [](const Goods& g1, const Goods& g2) {
    
    return g1._price < g2._price; });
	sort(v.begin(), v.end(), [](const Goods& g1, const Goods& g2) {
    
     return g1._num < g2._num; });
	sort(v.begin(), v.end(), [](const Goods& g1, const Goods& g2) {
    
     return g1._num < g2._num; });
	return 0;
}


lambda expression syntax

The writing format of lamada expression:

[capture-list] (parameters) mutable -> return-type 
{
    
      
  statement
}

Explanation of each part of lambda expression

  • [capture-list]: The capture list, which always appears at the beginning of the lambda expression, the compiler will judge whether the following code is a lambda expression according to [], the capture list can make the lambda expression according to the variables in the context expression used.
  • (parameters): The parameter list, which is consistent with the normal parameter list. If you do not need to pass parameters, you can omit them together with ().
  • mutable: By default, a lambda expression is always a const function (the function parameters cannot be modified), and mutable can cancel the constness. When using this modifier, the parameter list cannot be omitted (even if the parameter list is null.
  • ->returntype: Return value type. Generally, because the compiler deduces the return type, it can be omitted when the return value is clear.
  • {statement}: Function body. In this function body, in addition to the formal parameters in the function body, all captured variables can also be used.

Simple use of lambda expressions

int mian()
{
    
    
	//由于lambda表达式实际上就是一个匿名对象,没有函数名不好调用,但是我们可以通过auto自动获取.
	auto add1 = [](int a, int b) {
    
     return a + b; };    //省略返回值.

	cout << add1(1, 2) << endl;

}

capture list description

The capture list describes which data in the context can be used by the lambda, and whether it is used by value or by reference.

  • [var]: Indicates that the value transfer method captures the variable var.
  • [=]: Indicates that the value transfer method captures all variables in the parent scope (including this).
  • [&var]: Indicates that the capture variable var is passed by reference.
  • [&]: Indicates that reference transfer captures all variables in the parent scope (including this).
  • [this]: Indicates that the value transfer method captures the current this pointer.

The comparison between not using capture list and using capture list in lambda expression.

If we do not apply the capture list, we have to write additional function parameters, and the actual parameters must also be passed when calling, which is too troublesome.

int main()
{
    
    
	//之前做法
	int x = 0,y = 1;
	
	auto swap1 = [](int& x1, int& x2) {
    
     int tmp = x1; x1 = x2; x2 = tmp;};

	swap1(x, y);

	return 0;
}

Therefore, we can use the capture list, and because the x and y captured by value are usually not modifiable (the mutable modifier can be used), and the x and y captured at this time are only copies of the actual parameters. , we generally use reference capture, which makes the code more concise.


int main()
{
    
    
	auto swap2 = [&x, &y] {
    
    int tmp = x; x = y; x = tmp;}; //引用捕捉.
    
	swap2();             //不需要传递实参.
	
	cout << x << ":" << y << endl;
	
}

Simple use of other features of the capture list

int main()
{
    
    
	int a, b, c, d, e;
	
	auto f1 = [=] {
    
    cout << a << b << d << e; }; //a,b,c,d,e全部传值捕获.

	f1();

	auto f2 = [=, &a] {
    
     a++; cout << a << b << c << d << e; }; //b,c,d,e传值捕获,a传引用捕获.

	f2();
	
}

But note that the capture list does not allow variables to be passed repeatedly, otherwise it will cause compilation errors.

int main()
{
    
    
	int a, b, c, d, e;
	
	auto f = [=,a] {
    
    cout << a << b << d << e; }; //重复捕获.

}

Exploration of the underlying principle of lamaba expression

The compiler's treatment of lambdas is actually the same as that of functors .

In order to verify the underlying principles of lambda expressions, we wrote a functor and a lambda expression respectively, and their functions are the same.

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

When we call the functor and lambda expression separately, go to the disassembly view.
insert image description here

Summary :

  • From the point of view of usage, the functor is exactly the same as the lambda expression. The function object uses rate as its member variable, and the actual parameter can be passed when defining the object. The lambda expression can directly capture the variable to the actual parameter passing through the capture list. past.
  • From the perspective of the underlying implementation, it is completely processed in the way of functors. When we define a lambda expression, the compiler will actively generate a functor, and, for the convenience of processing, the compiler must use UUID Basically generate a unique functor name. Then call this functor through a lambda expression when calling, and the functor calls the operator to overload operator(). So in fact, the variables captured by the capture list are passed to operator() implemented in the body of the function.

Guess you like

Origin blog.csdn.net/m0_63300413/article/details/130877704