lambda函数

lambda函数

lambda函数是C++11中新增的非常有用的特性,它允许你在需要函数对象的地方,如for_eachsort等算法,直接定义一个匿名函数。Lambda函数简洁、高效,也为编写现代C++代码提供了更多的便利。

示例:

[](const int & a)-> void{ cout<< a << endl; };

基本语法

[capture list](parameters) multable noexcept -> return type {statement}
  • capture list 是捕获列表。
  • parameters 是参数列表。
  • mutable 是一个可选的说明符,允许你修改通过值捕获的变量。
  • throw-spec 是异常说明。(这里一般使用noexcept
  • return-type 是返回类型。
  • statement 是lambda函数体。

示例:

#include <iostream>
#include <vector>
#include <algorithm>

using namespace std;

// 普通函数
void print(int x) {
    cout << x << " ";
}

// 仿函数 (functor)
struct Print {
    void operator()(int x) const {
        cout << x << " ";
    }
};

int main() {
    vector<int> vec = {1, 2, 3, 4, 5};
    
    // 使用普通函数
    cout << "Using normal function: ";
    for_each(vec.begin(), vec.end(), print);
    cout << endl;

    // 使用仿函数
    cout << "Using functor: ";
    for_each(vec.begin(), vec.end(), Print());
    cout << endl;

    // 使用lambda函数
    cout << "Using lambda: ";
    for_each(vec.begin(), vec.end(), [](int x) {
        cout << x << " ";
    });
    cout << endl;

    return 0;
}

参数列表

Lambda函数的参数列表与普通函数或方法的参数列表非常相似。参数列表包围在()内。但与普通函数不同的是:

  • 没有默认参数:您不能为lambda函数的参数提供默认值。

    // 错误!lambda函数不支持默认参数
    auto lambda = [](int x = 5) { cout << x; };
    
  • 所有参数必须命名:每个参数都需要一个名字,即使您不打算在函数体中使用它。

  • 不支持可变参数:不能使用像...这样的可变参数来定义lambda函数。

返回类型

Lambda函数通常会根据函数体内的代码推断其返回类型。例如,如果函数体只包含一个返回语句,那么该返回语句的类型就是lambda函数的返回类型。但在某些情况下,您可能想要显式地指定返回类型,尤其是当函数体有多条返回语句,且它们的返回类型不同的时候。显式指定返回类型使用->符号。

auto lambda = [](int x) -> double { return 2.0 * x; };

在上面的例子中,尽管2.0 * x的结果可以是intdouble,但由于我们显式地指定了返回类型为double,所以lambda函数的返回类型是double

函数体

Lambda函数的函数体与普通函数的函数体非常相似。它包围在{}内,并可以包含任意数量的语句。

捕获列表

通过捕获列表,lambda函数可以访问父作用域中的非静态局部变量(静态局部变量可以直接访问,不能访问全局变量)。

捕获列表书写在[]中,与函数参数的传递类似,捕获方式可以是值或引用。

下面是不同捕获列表的方式。

image-20231025161150922

1.值传递

与传递参数类似,采用值捕获的前提是变量可以拷贝。

与传递参数不同,变量的值是在lambda函数创建时拷贝,而不是调用时拷贝。

由于被捕获的值是在lambda函数创建时拷贝,因此在随后对其修改不会影响到lambda内部的值。

默认情况下,如果以传值方式捕获变量,则在lambda函数中不能修改变量的值。

2.引用捕获

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

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

3. 隐式捕获

除了显式列出我们希望使用的父作域的变量之外,还可以让编译器根据函数体中的代码来推断需要捕获哪些变量,这种方式称之为隐式捕获。

隐式捕获有两种方式,分别是[=][&][=]表示以值捕获的方式捕获外部变量,[&]表示以引用捕获的方式捕获外部变量。

4.混合方式捕获

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; };    // 编译出错,默认引用捕获,显式引用捕获,冲突了
5.修改值捕获变量的值

在lambda函数中,如果以传值方式捕获变量,则函数体中不能修改该变量,否则会引发编译错误。

在lambda函数中,如果希望修改值捕获变量的值,可以加mutable选项,但是,在lambda函数的外部,变量的值不会被修改。

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

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

示例:

#include <iostream>
using namespace std;

int main() {
    int a = 10, b = 20, c = 30;

    // 1. 值捕获
    auto value_capture = [a]() {
        cout << "Value capture: " << a << endl;
    };
    a = 100;
    value_capture();  // 输出: Value capture: 10

    // 2. 引用捕获
    auto reference_capture = [&b]() {
        b++;
        cout << "Reference capture: " << b << endl;
    };
    reference_capture();  // 输出: Reference capture: 21
    cout << "Original b: " << b << endl;  // 输出: Original b: 21

    // 3. 隐式捕获 (全部以值捕获)
    auto implicit_value_capture = [=]() {
        cout << "Implicit value capture: " << a << " " << b << endl;
    };
    implicit_value_capture();  // 输出: Implicit value capture: 100 21

    // 4. 隐式捕获 (全部以引用捕获)
    auto implicit_reference_capture = [&]() {
        a++;
        cout << "Implicit reference capture: " << a << " " << b << endl;
    };
    implicit_reference_capture();  // 输出: Implicit reference capture: 101 21

    // 5. 混合方式捕获
    auto mixed_capture = [&, c]() {  // 默认引用捕获,但c是值捕获
        a++;
        cout << "Mixed capture: " << a << " " << c << endl;
    };
    mixed_capture();  // 输出: Mixed capture: 102 30

    // 6. 修改值捕获变量
    auto modify_value_capture = [c]() mutable {
        c++;
        cout << "Modify value capture: " << c << endl;
    };
    modify_value_capture();  // 输出: Modify value capture: 31
    cout << "Original c: " << c << endl;  // 输出: Original c: 30
}

lambda函数的本质

当我们编写了一个lambda函数之后,编译器将它翻译成一个类,该类中有一个重载了()的函数。捕获的外部变量会被存储为类的成员,并在这个类的构造函数中进行初始化。

  1. 采用值捕获

    考虑以下代码:

    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。这样上面的限制就能讲通了。

  2. 采用引用捕获

    考虑以下的lambda函数:

    int a = 10;
    auto printFun = [&a]() { cout << a << endl; };
    

    这个lambda函数可以被转化为以下的类形式:

    class PrintClass {
        int& m_a;   // 该成员变量是一个引用,对应通过引用捕获的变量。
    
    public:
        // 构造函数中的引用初始化
        PrintClass(int& a) : m_a(a) {}
    
        // 重载()运算符
        void operator()() const {
            cout << m_a << endl;
        }
    };
    

    你可以通过以下方式使用这个类:

    int a = 10;
    PrintClass printObj(a); 
    printObj();
    
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Tian Meng

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值