lambda表达式 (C++11)

在这篇博客中,我会尽量用简单易懂的语言和相关的例子来解释C++11中的lambda表达式的用法以及为什么要学习它们,内容不难,多多应用才是关键。

1. 什么是Lambda表达式?

Lambda表达式是一种匿名函数,也就是说,它们没有名字,可以在代码中被直接定义和使用。它们通常用于简化代码,特别是在需要传递小块逻辑(比如排序、过滤、或遍历集合)的时候。这么说还是很抽象,不如直接看例子。

例子:

// 使用命名函数
bool isEven(int n) {
    return n % 2 == 0;
}
std::vector<int> v = {1, 2, 3, 4, 5};
vec.erase(std::remove_if(vec.begin(), vec.end(), isEven), vec.end());

// 使用lambda表达式
std::vector<int> vec = {1, 2, 3, 4, 5};
vec.erase(std::remove_if(vec.begin(), vec.end(), [](int n) 
{ return n % 2 != 0; }), vec.end());

【解释】
这几行代码的目的是从vec(一个std::vector<int>类型的容器)中移除所有奇数元素。

std::remove_if(vec.begin(), vec.end(), [](int n) { return n % 2 != 0; })

  • std::remove_if 是标准库中的一个算法,它将所有满足给定条件的元素移到容器末尾,并返回一个新的“末尾”迭代器。
  • [](int n) { return n % 2 != 0; } 是一个lambda表达式,表示一个匿名函数。这个lambda表达式接收一个整数参数n,并返回n % 2 != 0的结果,即当n是奇数时返回true,否则返回false

所以,std::remove_if会遍历vec中的每个元素,并将所有奇数元素移到容器末尾,返回指向第一个被移除的奇数元素的位置。在这里,std::remove_if返回的新“末尾”迭代器作为vec.erase的第一个参数,vec.end()作为第二个参数。这样,vec.erase就会移除所有被std::remove_if标记为奇数的元素。


2. 使用思路一:直接调用

注意:lambda表达式是一种匿名函数,没有名称,因此不能像普通函数那样直接调用。为了使用lambda表达式,需要将其赋值给一个变量,以便可以通过这个变量调用它。此外,lambda表达式的类型是编译器生成的一个匿名类型,通常无法显式声明,所以需要使用auto关键字进行类型推断。

Lambda表达式在C++中的语法可以分为几个部分,每个部分都有其特定的作用。让我们来看一下它的基本语法:

[capture-list](parameters) -> return_type { statement }
[捕获列表](参数列表) -> 返回类型 { 函数体 }

a. 捕获列表 [ ]

捕获列表用于捕获作用域内的变量,使得lambda表达式可以访问这些变量。可以有以下几种方式:

  1. [ ]:不捕获任何变量。
  2. 按值捕捉(=):捕捉外部作用域中的变量的副本。
  3. 按引用捕捉(&):捕捉外部作用域中的变量的引用。
  4. 捕捉特定变量:可以按值或按引用捕捉特定的变量。
  5. 捕捉所有外部变量(= 或 &):分别按值或按引用捕捉所有外部变量。
  6. 混合捕捉:按值和按引用混合捕捉特定的变量。

b. 参数列表 ( )

参数列表与普通函数的参数列表类似,可以为空,也可以包含多个参数。

c. 返回类型 ->

返回类型可以省略,编译器会自动推断。如果明确指定返回类型,可以使用->关键字。

d. 函数体 { }

函数体包含lambda表达式的实际代码逻辑。

示例

以下是几个简单的例子来展示lambda表达式的不同用法:

例子1:基本的lambda表达式
	auto func = []() { return 42; };
	std::cout << func() << std::endl;  // 输出 42

这里的lambda表达式没有捕获任何变量,没有参数,返回整数42

例子2:捕捉列表的使用
  1. 按值捕捉
    int x = 10;
    auto lambda = [=]() {
        std::cout << "x = " << x << std::endl; // 按值捕捉 x
    };
    lambda();
  1. 按引用捕捉
    int x = 10;
    auto lambda = [&]() {
        x = 20; // 可以修改外部的 x
        std::cout << "x = " << x << std::endl;
    };
    lambda();
    std::cout << "Outside lambda, x = " << x << std::endl; // x 已被修改
  1. 捕捉特定变量
    int x = 10;
    int y = 20;
    auto lambda = [x, &y]() {
        // 按值捕捉 x,按引用捕捉 y
        // x 不能被修改,y 可以被修改
        std::cout << "x = " << x << ", y = " << y << std::endl;
        y = 30;
    };
    lambda();
    std::cout << "Outside lambda, y = " << y << std::endl; // y 已被修改
  1. 捕捉所有外部变量
    int x = 10;
    int y = 20;
    auto lambda = [=, &y]() {
        // 按值捕捉所有变量,但 y 按引用捕捉
        std::cout << "x = " << x << ", y = " << y << std::endl;
        y = 30;
    };
    lambda();
    std::cout << "Outside lambda, y = " << y << std::endl; // y 已被修改
  1. 混合捕捉
    int x = 10;
    int y = 20;
    auto lambda = [=, &y, this]() {
        // 按值捕捉所有变量,但 y 按引用捕捉,捕捉 this 指针(在类中使用时有效)
        std::cout << "x = " << x << ", y = " << y << std::endl;
        y = 30;
    };
    lambda();
    std::cout << "Outside lambda, y = " << y << std::endl; // y 已被修改
  • Lambda表达式本质上是一个可调用对象(类似于一个带有operator()的类)。将lambda表达式赋值给变量后,可以通过该变量调用这个可调用对象,就像调用一个普通函数一样。

3. 使用思路二:配套STL

就回到刚刚引入的那个例子:

在使用标准库算法时,可以直接在函数调用中定义lambda表达式:

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

int main() {
    std::vector<int> vec = {1, 2, 3, 4, 5, 6};

    // 直接在std::remove_if调用中定义lambda表达式
    vec.erase(
        std::remove_if(vec.begin(), vec.end(), [](int n) { return n % 2 == 0; }),
        vec.end()
    );
    return 0;
}

在这个例子中,lambda表达式[](int n) { return n % 2 == 0; }直接作为参数传递给std::remove_if,不需要赋值给变量。


为什么要使用Lambda?

  1. 代码简洁:Lambda表达式让你可以在需要的地方直接定义和使用函数,而不需要单独定义一个命名函数,这使得代码更加紧凑和易读。

  2. 灵活性:Lambda表达式可以捕获周围的变量,这让它们非常灵活,能够在局部范围内使用外部变量,而不需要通过参数传递。

  3. 性能:由于lambda表达式是内联的,它们可能会比普通函数调用更高效,因为编译器可以对它们进行优化。

  4. 标准库兼容性:C++标准库(如STL)中很多算法和函数都支持使用lambda表达式,这使得你可以更好地利用标准库的强大功能。

什么时候使用Lambda表达式?

  • 当你需要传递小块逻辑时,如排序、过滤或遍历集合。
  • 当函数只在局部范围内使用,不需要全局访问时。
  • 当你希望保持代码简洁和易读,避免定义过多的命名函数时。

lambda和仿函数

Lambda表达式和仿函数(函数对象)在C++中都是可调用对象,主要用于传递逻辑到算法或容器中。它们在功能上有许多相似之处,但在使用方式和实现细节上有一些重要的区别。

共同点

  1. 可调用对象

    • 仿函数和lambda表达式都是可调用对象,可以像函数一样调用。
    • 它们都可以传递给标准库算法,如std::sortstd::for_each等。

  2. 状态

    • 仿函数和lambda表达式都可以保存状态(即捕获变量或成员变量),这使得它们比普通函数指针更强大。

区别

  1. 语法和定义方式

    • 仿函数:仿函数通常是一个类或结构体,重载了operator()。定义比较繁琐,需要单独定义一个类型。
    • lambda表达式:lambda表达式是一个匿名函数,可以在需要的地方直接定义和使用。定义更加简洁。

    仿函数示例

    struct Add {
        int operator()(int a, int b) const {
            return a + b;
        }
    };
    
    Add add;
    int result = add(3, 4); // 调用仿函数
    

    lambda表达式示例

    auto add = [](int a, int b) { return a + b; };
    int result = add(3, 4); // 调用lambda表达式
    
  2. 捕获变量

    • 仿函数:仿函数可以通过成员变量保存状态,但需要显式定义和初始化这些成员变量。
    • lambda表达式:lambda表达式可以使用捕获列表捕获作用域内的变量,捕获方式包括按值捕获和按引用捕获。

  1. 类型

    • 仿函数:仿函数有明确的类型,可以通过类名来引用。
    • lambda表达式:lambda表达式的类型是匿名的、编译器生成的类型,通常需要使用auto关键字进行类型推断。

  2. 编译器支持

    • 仿函数:仿函数在C++98及以后版本都可以使用。
    • lambda表达式:lambda表达式是C++11引入的新特性,只有在支持C++11及以上标准的编译器中才能使用。

性能和用途

  • 性能:在大多数情况下,lambda表达式和仿函数在性能上是相似的。由于lambda表达式通常是内联的,编译器可以对它们进行优化,有时甚至比仿函数更高效。
  • 用途:lambda表达式更适合定义短小、临时的可调用对象,而仿函数更适合定义复杂的、重用的逻辑。

总结

  • 仿函数:适用于需要定义复杂逻辑和保存状态的场景,需要单独定义类型。
  • lambda表达式:适用于需要简洁地定义和使用匿名函数的场景,语法更加简洁,支持捕获局部变量。

选择使用仿函数还是lambda表达式,主要取决于具体需求和代码的复杂性。如果你需要定义一个复杂的、带状态的可调用对象,仿函数可能是更好的选择;如果你需要简洁地传递一小段逻辑,lambda表达式则更加方便。

  • 20
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值