<C++> C++11 Lambda expression

C++11 Lambda expressions

1. An example in C++98

In C++98, if you want to sort the elements in a data set, you can use the std::sort method.

#include <algorithm>
#include <functional>
int main() {
    
    
    int array[] = {
    
    4, 1, 8, 5, 3, 7, 0, 9, 2, 6};
    // 默认按照小于比较,排出来结果是升序
    std::sort(array, array + sizeof(array) / sizeof(array[0]));
    // 如果需要降序,需要改变元素的比较规则
    std::sort(array, array + sizeof(array) / sizeof(array[0]), greater<int>());
    return 0;
}

If the element to be sorted is a custom type, the user needs to define the comparison rules when sorting:

#include <algorithm>
#include <string>
#include <vector>
using namespace std;

struct Goods {
    
    
    string _name; // 名字
    double _price;// 价格
    int _evaluate;// 评价
    Goods(const char *str, double price, int evaluate)
        : _name(str), _price(price), _evaluate(evaluate) {
    
    }
};
//价格降序
struct ComparePriceLess {
    
    
    bool operator()(const Goods &gl, const Goods &gr) {
    
    
        return gl._price < gr._price;
    }
};
//价格升序
struct ComparePriceGreater {
    
    
    bool operator()(const Goods &gl, const Goods &gr) {
    
    
        return gl._price > gr._price;
    }
};
int main() {
    
    
    vector<Goods> v = {
    
    {
    
    "苹果", 2.1, 5}, {
    
    "香蕉", 3, 4}, {
    
    "橙子", 2.2, 3}, {
    
    "菠萝", 1.5, 4}};
    sort(v.begin(), v.end(), ComparePriceLess());
    sort(v.begin(), v.end(), ComparePriceGreater());
}

With the development of C++ grammar, people began to feel that the above writing method is too complicated. Every time in order to realize an algorithmalgorithm, it is necessary to write a new class. If the logic of each comparison is different, it is necessary to implement multiple classes, especially It is the naming of the same class, which brings great inconvenience to programmers . Thus, expressions appear in the C++11 syntax Lambda.

2. Lambda expressions

Lambda expressions are a way of anonymous functions introduced by the C++11 standard. It provides a concise, flexible syntax for defining and using functions where function objects are expected.

The basic syntax of a Lambda expression is as follows:

[capture-list](parameter-list) -> return-type {
    
     
    // 函数体
}

Among them, the meaning of each part is as follows:

  • capture-list: Specifies the variable to be captured in the lambda expression. Captures can be value captures (capturing by copying the value of a variable) or reference captures (binding to a variable by reference). The capture list is optional and can be omitted if no variables are captured.
  • parameter-list: Specify the parameter list of the lambda function, which is similar to the parameter list of ordinary functions. The parameter list is optional and can be omitted if the function does not require parameters.
  • return-type: Specifies the return type of the lambda function. The return type can be omitted, and the compiler will automatically deduce the return type.
  • {}: The start and end markers for the body of the lambda function.

The above code can be solved using lambda expressions in C++11

struct Goods {
    
    
    string _name; // 名字
    double _price;// 价格
    int _evaluate;// 评价
    Goods(const char *str, double price, int evaluate)
        : _name(str), _price(price), _evaluate(evaluate) {
    
    }
};
struct ComparePriceLess {
    
    
    bool operator()(const Goods &gl, const Goods &gr) {
    
    
        return gl._price < gr._price;
    }
};
struct ComparePriceGreater {
    
    
    bool operator()(const Goods &gl, const Goods &gr) {
    
    
        return gl._price > gr._price;
    }
};


int main() {
    
    
    vector<Goods> v = {
    
    {
    
    "苹果", 2.1, 5}, {
    
    "香蕉", 3, 4}, {
    
    "橙子", 2.2, 3}, {
    
    "菠萝", 1.5, 4}};
    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._evaluate < g2._evaluate;
    });
    sort(v.begin(), v.end(), [](const Goods &g1, const Goods &g2) {
    
    
        return g1._evaluate > g2._evaluate;
    });
}

It can be seen that the lambda expression is actually an anonymous function.

Note :

In a Lambda expression definition, the parameter list and return type are optional parts, while the capture list and function body can be empty . So the simplest Lambda expression in C++11 is: []{}; This Lambda expression cannot do anything.

int main() {
    
    
    // 最简单的lambda表达式, 该lambda表达式没有任何意义
    [] {
    
    };

    // 省略参数列表和返回值类型,返回值类型由编译器推导为int
    int a = 3, b = 4;
    [=] {
    
     return a + 3; };

    // 省略了返回值类型,无返回值类型
    auto fun1 = [&](int c) {
    
     b = a + c; };
    fun1(10);
    cout << a << " " << b << endl;

    // 各部分都很完善的lambda函数
    auto fun2 = [=, &b](int c) -> int {
    
     return b += a + c; };

    //cout << [=, &b](int c) -> int { return b += a + c; }(10);
    cout << fun2(10) << endl;

    // 复制捕捉x
    int x = 10;
    auto add_x = [x](int a) mutable {
    
     x *= 2; return a + x; };
    cout << add_x(10) << endl;
    return 0;
}

From the above examples, we can see that Lambda expression can actually be understood as an unnamed function, which cannot be called directly. If you want to call it directly, you can use auto to assign it to a variable.

capture list description

The capture list describes which data in the context can be used by 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 the reference transfer captures all variables in the parent scope (including this)
  • [this]: Indicates that the value transfer method captures the current this pointer
  1. [var]: Indicates that the value transfer method captures variables var. The Lambda function copies varthe value of the variable for use in the function body. Modifying variables inside a Lambda function varwill not affect variables outside.
int x = 5;

auto Lambda = [x]() {
    
    
    // 使用值传递方式捕获的变量x
    std::cout << "Value of x: " << x << std::endl;
};

x = 10; // 修改外部变量x的值

Lambda(); // 输出: Value of x: 5

In the preceding example, the Lambda function captures xthe value of the variable by passing it by value. Even if the value of the variable is modified outside the Lambda function x, the copy inside the Lambda function is used at the time of capture, so the output result is still the value at the time of capture.

  1. [=]: Indicates that the pass-by-value method captures variables in all parent scopes (including this). Lambda functions copy the values ​​of all external variables for use in the function body. This way, the Lambda function can access all external variables without side effects.
int x = 5;
int y = 10;

auto lambda = [=]() {
    
    
    // 使用值传递方式捕获的变量x和y
    std::cout << "Value of x: " << x << std::endl;
    std::cout << "Value of y: " << y << std::endl;
};

x = 20;
y = 30;

lambda(); // 输出: Value of x: 5  Value of y: 10

In the above example, the Lambda function captures the values ​​of the variables xand by passing by value y. Even if the value of the variable is modified outside the Lambda function, the Lambda function uses the copy at the time of capture, so the output result is still the value at the time of capture.

​ 3. [&var]: Indicates that the capture variable is passed by reference var. Lambda functions are bound to variables by reference varfor use within the function body. Modifications to variables varaffect external variables.

int x = 5;

auto lambda = [&x]() {
    
    
    // 使用引用传递方式捕获的变量x
    std::cout << "Value of x: " << x << std::endl;
};

x = 10; // 修改外部变量x的值

lambda(); // 输出: Value of x: 10

In the above example, the Lambda function captures the variable by passing it by reference x. Because it is bound to the variable by reference, the same variable is accessed inside the Lambda function, so the output will reflect the modified value of the variable.

  1. [&]: Indicates that pass-by-reference captures variables in all parent scopes (inclusive this). Lambda functions are bound by reference to all external variables for use within the function body. Modifications to variables affect external variables.
int x = 5;
int y = 10;

auto lambda = [&]() {
    
    
    // 使用引用传递方式捕获的变量x和y
    std::cout << "Value of x: " << x << std::endl;
    std::cout << "Value of y: " << y << std::endl;
};

x = 20;
y = 30;

lambda(); // 输出: Value of x: 20  Value of y: 30

In the above example, the Lambda function captures the variables xand by pass-by-reference y. Because it is bound to the variable by reference, the same variable is accessed inside the Lambda function, so the output will reflect the modified value of the variable.

  1. [this]: Indicates that the value transfer method captures the current thispointer. The Lambda function copies thisthe pointer of the current object for use in the function body. Through capture this, Lambda functions can access member variables and member functions of the current object.
class MyClass {
    
    
public:
    void foo() {
    
    
        auto lambda = [this]() {
    
    
            // 使用值传递方式捕获的当前对象的this指针
            std::cout << "Value of member: " << member << std::endl;
            bar(); // 调用成员函数
        };

        lambda();
    }

private:
    int member = 5;

    void bar() {
    
    
        std::cout << "Inside bar()" << std::endl;
    }
};

MyClass obj;
obj.foo(); // 输出: Value of member: 5  Inside bar()

In the above example, the Lambda function captures thisthe pointer of the current object through value passing, and uses the pointer to access member variables memberand member functions bar().

int main() {
    
    
    int x = 0, y = 1;
    int m = 0, n = 1;

    auto swap1 = [](int &rx, int &ry) {
    
    
        int tmp = rx;
        rx = ry;
        ry = tmp;
    };

    swap1(x, y);
    cout << x << " " << y << endl;

    // 传值捕捉
    auto swap2 = [x, y]() mutable {
    
    
        int tmp = x;
        x = y;
        y = tmp;
    };

    swap2();
    cout << x << " " << y << endl;

    // 引用捕捉
    auto swap2 = [&x, &y]() {
    
    
        int tmp = x;
        x = y;
        y = tmp;
    };

    swap2();
    cout << x << " " << y << endl;

    // 混合捕捉
    auto func1 = [&x, y]() {
    
    
        //...
    };

    // 全部引用捕捉
    auto func2 = [&]() {
    
    
        //...
    };

    // 全部传值捕捉
    auto func3 = [=]() {
    
    
        //...
    };

    // 全部引用捕捉,x传值捕捉
    auto func4 = [&, x]() {
    
    
        //...
    };

    return 0;
}

Notice:

a. The parent scope refers to the statement block containing the lambda function

b. Grammatically, a capture list can consist of multiple capture items separated by commas. For example: [=, &a, &b]: capture variables a and b by reference, and capture all other variables by value [&, a, this]: capture variables a and this by value, and capture other variables by reference

c. The capture list does not allow variables to be passed repeatedly, otherwise it will cause compilation errors. For example: [=, a]: = has captured all variables by value transfer, capture a repetition

d. The capture list of lambda functions outside the block scope must be empty.

e. The lambda function in the block scope can only capture local variables in the parent scope, and capturing any non-scope or non-local variables will cause compilation errors

f. Lambda expressions cannot be assigned to each other, even if they appear to be of the same type

void (*PF)();
int main() {
    
    
    auto f1 = [] {
    
     cout << "hello world" << endl; };
    auto f2 = [] {
    
     cout << "hello world" << endl; };
    // 此处先不解释原因,等lambda表达式底层实现原理看完后,大家就清楚了
    //f1 = f2;   // 编译失败--->提示找不到operator=()
    // 允许使用一个lambda表达式拷贝构造一个新的副本
    auto f3(f2);
    f3();
    // 可以将lambda表达式赋值给相同类型的函数指针
    PF = f2;
    PF();
    return 0;
}

mutable

mutableIs a keyword used to modify the Lambda expression, representing the mutable state of the Lambda function body. By default, variables captured in the body of a Lambda function are read-only and cannot be modified. However, sometimes we may need to modify the value of these variables in the Lambda function body, which requires the use of mutablekeywords.

After mutablethe Lambda function is modified with keywords, the captured variables can be modified in the function body. This is useful for things like needing to maintain some internal state or counters inside a Lambda function.

#include <iostream>

int main() {
    
    
    int x = 0;

    auto lambda = [x]() mutable {
    
    
        x++;// 修改被捕获的变量x
        std::cout << "Value of x: " << x << std::endl;
    };

    /*  auto lambda = [&x]() {
        x++; // 修改被捕获的变量x
        std::cout << "Value of x: " << x << std::endl;
    } */

    lambda();// 输出: Value of x: 1
    lambda();// 输出: Value of x: 2

    std::cout << "Final value of x: " << x << std::endl;// 输出: Final value of x: 0

    return 0;
}

In the above example, the Lambda function captures the variable by value x. Due to mutablethe keyword decoration, the value of the captured variable can be modified inside the Lambda function body x. Each time the Lambda function is invoked, xthe value of is incremented and the corresponding result is output.

It should be noted that mutablekeywords are only valid for variables in the Lambda function body, and have no effect on variables captured by reference passing mutable.

The keyword needs to be used mutablewith caution as it makes the Lambda function have side effects that can lead to unexpected results. When using mutableit, it is important to ensure that modifying the captured variable does not cause race conditions or other problems.

3. Function objects and Lambda expressions

A function object, also known as a functor, is an object that can be used like a function, that is, a class object that overloads the operator() operator in the class.

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);
    // Lambda
    auto r2 = [=](double monty, int year) -> double {
    
    
        return monty * rate * year;
    };
    r2(10000, 2);
    return 0;
}

In terms of usage, function objects are exactly the same as lambda expressions.

The function object has rate as its member variable, and the initial value is sufficient when defining the object, and the Lambda expression can directly capture the variable through the capture list.
insert image description here
In fact, the way the underlying compiler handles lambda expressions is completely in the way of function objects, that is, if a lambda expression is defined, the compiler will automatically generate a class (the class number is different each time ), operator() is overloaded in this class.

おすすめ

転載: blog.csdn.net/ikun66666/article/details/131341347