深入浅出 C++ Lambda表达式:语法、特点和应用

宁子希 2024-07-20 17:35:01 阅读 96

C++ Lambda表达式介绍


在本文中,我们将介绍 c++ Lambda表达式的定义、用法和特点,以及它与普通函数和普通类的关系。通过多个例子,展示了 Lambda表达式的参数、返回值、捕获、引用、修改等方式,以及如何用它们定义匿名函数和算法。

什么是 Lambda表达式

Lambda表达式是一种在被调用的位置或作为参数传递给函数的位置定义匿名函数对象(闭包)的简便方法。Lambda表达式的基本语法如下:

<code>[capture list] (parameter list) -> return type { function body }

其中:

capture list 是捕获列表,用于指定 Lambda表达式可以访问的外部变量,以及是按值还是按引用的方式访问。捕获列表可以为空,表示不访问任何外部变量,也可以使用默认捕获模式 &= 来表示按引用或按值捕获所有外部变量,还可以混合使用具体的变量名和默认捕获模式来指定不同的捕获方式。parameter list 是参数列表,用于表示 Lambda表达式的参数,可以为空,表示没有参数,也可以和普通函数一样指定参数的类型和名称,还可以在 c++14 中使用 auto 关键字来实现泛型参数。return type 是返回值类型,用于指定 Lambda表达式的返回值类型,可以省略,表示由编译器根据函数体推导,也可以使用 -> 符号显式指定,还可以在 c++14 中使用 auto 关键字来实现泛型返回值。function body 是函数体,用于表示 Lambda表达式的具体逻辑,可以是一条语句,也可以是多条语句,还可以在 c++14 中使用 constexpr 来实现编译期计算。

Lambda表达式的捕获方式

值捕获(capture by value):在捕获列表中使用变量名,表示将该变量的值拷贝到 Lambda 表达式中,作为一个数据成员。值捕获的变量在 Lambda 表达式定义时就已经确定,不会随着外部变量的变化而变化。值捕获的变量默认不能在 Lambda 表达式中修改,除非使用 mutable 关键字。例如:

int x = 10;

auto f = [x] (int y) -> int { return x + y; }; // 值捕获 x

x = 20; // 修改外部的 x

cout << f(5) << endl; // 输出 15,不受外部 x 的影响

引用捕获(capture by reference):在捕获列表中使用 & 加变量名,表示将该变量的引用传递到 Lambda 表达式中,作为一个数据成员。引用捕获的变量在 Lambda 表达式调用时才确定,会随着外部变量的变化而变化。引用捕获的变量可以在 Lambda 表达式中修改,但要注意生命周期的问题,避免悬空引用的出现。例如:

int x = 10;

auto f = [&x] (int y) -> int { return x + y; }; // 引用捕获 x

x = 20; // 修改外部的 x

cout << f(5) << endl; // 输出 25,受外部 x 的影响

隐式捕获(implicit capture):在捕获列表中使用 =&,表示按值或按引用捕获 Lambda 表达式中使用的所有外部变量。这种方式可以简化捕获列表的书写,避免过长或遗漏。隐式捕获可以和显式捕获混合使用,但不能和同类型的显式捕获一起使用。例如:

int x = 10;

int y = 20;

auto f = [=, &y] (int z) -> int { return x + y + z; }; // 隐式按值捕获 x,显式按引用捕获 y

x = 30; // 修改外部的 x

y = 40; // 修改外部的 y

cout << f(5) << endl; // 输出 55,不受外部 x 的影响,受外部 y 的影响

初始化捕获(init capture):C++14 引入的一种新的捕获方式,它允许在捕获列表中使用初始化表达式,从而在捕获列表中创建并初始化一个新的变量,而不是捕获一个已存在的变量。这种方式可以使用 auto 关键字来推导类型,也可以显式指定类型。这种方式可以用来捕获只移动的变量,或者捕获 this 指针的值。例如:

int x = 10;

auto f = [z = x + 5] (int y) -> int { return z + y; }; // 初始化捕获 z,相当于值捕获 x + 5

x = 20; // 修改外部的 x

cout << f(5) << endl; // 输出 20,不受外部 x 的影响

Lambda表达式的优点

Lambda表达式相比于普通函数和普通类,有以下几个优点:

简洁:Lambda表达式可以省略函数名和类名,直接定义和使用,使得代码更加简洁和清晰。灵活:Lambda表达式可以捕获外部变量,可以作为函数参数,也可以作为函数返回值,使得代码更加灵活和方便。安全:Lambda表达式可以控制外部变量的访问方式,可以避免全局变量的定义,可以避免悬空指针和无效引用的产生,使得代码更加安全和稳定。

Lambda表达式的示例

下面我们通过一些示例来展示 Lambda表达式的用法和效果。

示例一:使用 Lambda表达式定义简单的匿名函数

我们可以使用 Lambda表达式来定义一些简单的匿名函数,例如计算两个数的和、判断一个数是否为奇数等。例如:

#include <iostream>

using namespace std;

int main()

{

// 定义一个 Lambda表达式,计算两个数的和

auto plus = [] (int a, int b) -> int { return a + b; };

// 调用 Lambda表达式

cout << plus(3, 4) << endl; // 输出 7

// 定义一个 Lambda表达式,判断一个数是否为奇数

auto is_odd = [] (int n) { return n % 2 == 1; };

// 调用 Lambda表达式

cout << is_odd(5) << endl; // 输出 1

cout << is_odd(6) << endl; // 输出 0

return 0;

}

示例二:使用 Lambda表达式捕获外部变量

我们可以使用 Lambda表达式的捕获列表来指定 Lambda表达式可以访问的外部变量,以及是按值还是按引用的方式访问。例如:

#include <iostream>

using namespace std;

int main()

{

int x = 10;

int y = 20;

// 定义一个 Lambda表达式,按值捕获 x 和 y

auto add = [x, y] () -> int { return x + y; };

// 调用 Lambda表达式

cout << add() << endl; // 输出 30

// 修改 x 和 y 的值

x = 100;

y = 200;

// 再次调用 Lambda表达式

cout << add() << endl; // 输出 30,捕获的是 x 和 y 的副本,不受外部变化的影响

// 定义一个 Lambda表达式,按引用捕获 x 和 y

auto mul = [&x, &y] () -> int { return x * y; };

// 调用 Lambda表达式

cout << mul() << endl; // 输出 20000

// 修改 x 和 y 的值

x = 1000;

y = 2000;

// 再次调用 Lambda表达式

cout << mul() << endl; // 输出 2000000,捕获的是 x 和 y 的引用,会反映外部变化的影响

return 0;

}

示例三:使用 Lambda表达式作为函数参数

我们可以使用 Lambda表达式作为函数的参数,这样可以方便地定义和传递一些简单的函数对象,例如自定义排序规则、自定义比较函数等。例如:

#include <iostream>

#include <vector>

#include <algorithm>

using namespace std;

// 定义一个结构体

struct Item

{

Item(int aa, int bb) : a(aa), b(bb) { }

int a;

int b;

};

int main()

{

vector<Item> vec;

vec.push_back(Item(1, 19));

vec.push_back(Item(10, 3));

vec.push_back(Item(3, 7));

vec.push_back(Item(8, 12));

vec.push_back(Item(2, 1));

// 使用 Lambda表达式,根据 Item 中的成员 a 升序排序

sort(vec.begin(), vec.end(), [] (const Item& v1, const Item& v2) { return v1.a < v2.a; });

// 使用 Lambda表达式,打印 vec 中的 Item 成员

for_each(vec.begin(), vec.end(), [] (const Item& item) { cout << item.a << " " << item.b << endl; });

return 0;

}

示例四:使用 Lambda表达式作为函数返回值

我们可以使用 Lambda表达式作为函数的返回值,这样可以方便地定义和返回一些简单的函数对象,例如工厂函数、闭包函数等。例如:

#include <iostream>

using namespace std;

// 定义一个函数,返回一个 Lambda表达式,实现两个数的加法

auto make_adder(int x)

{

return [x] (int y) -> int { return x + y; };

}

int main()

{

// 调用函数,得到一个 Lambda表达式

auto add5 = make_adder(5);

// 调用 Lambda表达式

cout << add5(10) << endl; // 输出 15

return 0;

}

Lambda表达式与普通函数和普通类的关系


Lambda表达式虽然是一种语法糖,但它本质上也是一种函数对象,也就是重载了 operator() 的类的对象。每一个 Lambda表达式都对应一个唯一的匿名类,这个类的名称由编译器自动生成,因此我们无法直接获取或使用。Lambda表达式的捕获列表实际上是匿名类的数据成员,Lambda表达式的参数列表和返回值类型实际上是匿名类的 operator() 的参数列表和返回值类型,Lambda表达式的函数体实际上是匿名类的 operator() 的函数体。例如,下面的 Lambda表达式:

int x = 10;

auto f = [x] (int y) -> int { return x + y; };

相当于定义了一个匿名类,类似于:

int x = 10;

class __lambda_1

{

public:

__lambda_1(int x) : __x(x) { } // 构造函数,用于初始化捕获的变量

int operator() (int y) const // 重载的 operator(),用于调用 Lambda表达式

{

return __x + y; // 函数体,与 Lambda表达式的函数体相同

}

private:

int __x; // 数据成员,用于存储捕获的变量

};

auto f = __lambda_1(x); // 创建一个匿名类的对象,相当于 Lambda表达式

由于 Lambda表达式是一种函数对象,因此它可以赋值给一个合适的函数指针或函数引用,也可以作为模板参数传递给一个泛型函数或类。例如:

#include <iostream>

using namespace std;

// 定义一个函数指针类型

typedef int (*func_ptr) (int, int);

// 定义一个函数,接受一个函数指针作为参数

void apply(func_ptr f, int a, int b)

{

cout << f(a, b) << endl;

}

int main()

{

// 定义一个 Lambda表达式,计算两个数的乘积

auto mul = [] (int x, int y) -> int { return x * y; };

// 将 Lambda表达式赋值给一个函数指针

func_ptr fp = mul;

// 调用函数,传递函数指针

apply(fp, 3, 4); // 输出 12

return 0;

}

C++14 和 C++17 对 Lambda表达式的扩展和改进


C++14 和 C++17 对 Lambda表达式进行了一些扩展和改进,使得 Lambda表达式更加强大和灵活。主要有以下几个方面:

泛型 Lambda:C++14 允许在 Lambda表达式的参数列表和返回值类型中使用 auto 关键字,从而实现泛型 Lambda,即可以接受任意类型的参数和返回任意类型的值的 Lambda表达式。例如:

#include <iostream>

using namespace std;

int main()

{

// 定义一个泛型 Lambda,根据参数的类型返回不同的值

auto f = [] (auto x) -> auto

{

if (is_integral<decltype(x)>::value) // 如果 x 是整数类型

{

return x * 2; // 返回 x 的两倍

}

else if (is_floating_point<decltype(x)>::value) // 如果 x 是浮点类型

{

return x / 2; // 返回 x 的一半

}

else // 其他类型

{

return x; // 返回 x 本身

}

};

// 调用泛型 Lambda

cout << f(10) << endl; // 输出 20

cout << f(3.14) << endl; // 输出 1.57

cout << f("hello") << endl; // 输出 hello

return 0;

}

初始化捕获:C++14 允许在 Lambda表达式的捕获列表中使用初始化表达式,从而实现初始化捕获,即可以在捕获列表中创建和初始化一个新的变量,而不是捕获一个已存在的变量。例如:

#include <iostream>

using namespace std;

int main()

{

// 定义一个 Lambda表达式,使用初始化捕获,创建一个新的变量 z

auto f = [z = 10] (int x, int y) -> int { return x + y + z; };

// 调用 Lambda表达式

cout << f(3, 4) << endl; // 输出 17

return 0;

}

捕获 this 指针:C++17 允许在 Lambda表达式的捕获列表中使用 *this,从而实现捕获 this 指针,即可以在 Lambda表达式中访问当前对象的成员变量和成员函数。例如:

#include <iostream>

using namespace std;

// 定义一个类

class Test

{

public:

Test(int n) : num(n) { } // 构造函数,初始化 num

void show() // 成员函数,显示 num

{

cout << num << endl;

}

void add(int x) // 成员函数,增加 num

{

// 定义一个 Lambda表达式,捕获 this 指针

auto f = [*this] () { return num + x; };

// 调用 Lambda表达式

cout << f() << endl;

}

private:

int num; // 成员变量,存储一个整数

};

int main()

{

Test t(10); // 创建一个 Test 对象

t.show(); // 调用成员函数,输出 10

t.add(5); // 调用成员函数,输出 15

return 0;

}

总结

Lambda表达式是 c++11 引入的一个语法糖,它可以用来定义并创建匿名的函数对象,主要用于方便编程,避免全局变量的定义,并且变量安全。Lambda表达式的语法类似于一个函数定义,但它不需要函数名,可以直接定义并使用。Lambda表达式相比于普通函数和普通类,有以下几个优点:简洁、灵活和安全。Lambda表达式本质上是一个匿名类的对象,因此它可以赋值给一个函数指针或函数引用,也可以作为模板参数传递给一个泛型函数或类。C++14 和 C++17 对 Lambda表达式进行了一些扩展和改进,使得 Lambda表达式更加强大和灵活,主要有以下几个方面:泛型 Lambda、初始化捕获和捕获 this 指针。



声明

本文内容仅代表作者观点,或转载于其他网站,本站不以此文作为商业用途
如有涉及侵权,请联系本站进行删除
转载本站原创文章,请注明来源及作者。