自学内容网 自学内容网

lambda表达式

1、概念

        定义在代码块中的小型函数对象,可捕获其所在作用域中的变量。其提供了一种便捷的方式来定义和使用短小的、一次性的函数对象,而无需显式定义一个独立的函数或类。

        lambda表达式在编译阶段由编译器自动生成一个闭包类,在运行阶段由闭包类产生一个对象,称其为闭包(一个函数对象,匿名且可包含定义时作用域上下文)。

2、基本语法

[capture](parameters) specifiers exception -> return_type { function_body }

1、captures捕获列表

捕获当前函数作用域的零个或多个变量,多个变量间使用逗号分隔;捕获后,可在lambda表达式function_body内使用。

捕获的值必须为局部非静态变量。

(1)值捕获

将函数作用域变量复制到lambda表达式函数对象的内部;

捕获的变量默认类型为常量;

若确实需要改变(“改变”只是function_body内部,值传递的本质没有变化,脱离lambda表达式作用域后,变量的值与传入前一致)捕获的变量,使用mutable修饰;

获取变量的值在定义lambda表达式时已确定,后续修改无效。

(2)引用捕获

值捕获的变量前加&。

(3)特殊方式的捕获

[this]捕获this指针。

[=]按值捕获lambda表达式一定作用域的全部变量值,包含this。

[&]按引用捕获lambda表达式一定作用域的全部变量的引用,包含this。

(4)初始化捕获

捕获表达式赋值给labmda表达式作用域变量的结果:

int var = 5;
auto ret = [expr = var + 1]{ return expr; };

[=, *this]使得lambda表达式中保存一份*this的对象副本。

[=, this]只是为了区分与[=, *this]的不同,含义与[=]一致。

2、parameters可选参数列表

与普通函数参数列表一样,不需要时可忽略。

当参数列表中的变量未指定类型,而使用auto修饰时,lambda表达式具备了模板函数的能力,入参根据传入的实参进行推导。

3、specifiers限定符

可选。一般可指定为mutable,表示function_body内改变按值捕获的变量,或调用非const的成员函数。

4、exception异常说明符

可选。使用noexcept来指明lambda不会抛出异常。

5、return_type返回类型

可选。无返回值时为void,有返回值时,可依赖编译器进行推导,不必指定。

无状态的labmda表达式可隐式转换为函数指针,如

最简单的lambda表达式[] { }可作为函数void f(void(*)())的入参,即f([] { })合法。

6、 function_body函数体

与普通函数形式一致。

3、使用场景及示例

1. 作为参数传递给STL算法
在STL算法中,有许多函数接受函数对象作为参数。Lambda表达式可以方便地作为这些函数对象传入,如sort、find_if、replace_if等。

std::vector<int> numbers = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
// 查找numbers中第一个大于5的元素
auto it = std::find_if(numbers.begin(), numbers.end(), [](int num) {
    return num > 5;
});

2. 作为函数对象
代替函数对象,省略了定义函数对象的步骤,简化了代码。函数对象通常是通过重载类的operator()来实现的,而lambda表达式提供了一种更简洁的语法来实现相同的功能。

auto printNumber = [](int num) {
    std::cout << num << std::endl; 
};

// 使用标准库算法for_each和lambda表达式遍历向量
std::for_each(numbers.begin(), numbers.end(), printNumber);

3. 简化回调函数
在一些回调函数的场景中,lambda表达式可以直接在调用函数的地方定义,从而避免了定义全局函数或者类成员函数的繁琐步骤。这使得回调函数的使用更加灵活和方便。

#include <iostream>
#include <functional>
#include <chrono>
#include <thread>

class Timer {
public:
    using Callback = std::function<void()>;

    Timer(int interval, Callback cb)
        : interval_(interval), callback_(cb) {}

    void start()
    {
        std::this_thread::sleep_for(std::chrono::seconds(interval_));
        callback_();
    }

    int get_interval() const
    {
        return interval_;
    }
private:
    int interval_;
    Callback callback_;
};

int main()
{
    // 使用lambda表达式定义回调函数
    Timer timer(3, []() {
        std::cout << "Timer expired! Executing callback." << std::endl;
    });
    std::cout << "Timer started. Waiting for " << timer.get_interval() << " seconds..." << std::endl;
    timer.start();
    return 0;
}

4. 多线程编程
在多线程编程中,lambda表达式可以方便地传递给线程对象,从而简化了线程创建和管理的过程。例如,可以使用lambda表达式来定义线程要执行的任务,然后将其传递给线程构造函数或相关函数。

#include <iostream>
#include <thread>
#include <vector>

// 打印线程ID和消息
void printThreadInfo(const std::string& message) 
{
    std::cout << "Thread ID: " << std::this_thread::get_id() << " " << message << std::endl;
}

int main() 
{
    constexpr int numThreads = 5;
    std::vector<std::thread> threads;
    // 使用lambda表达式创建并启动线程
    for (int i = 0; i < numThreads; ++i) {
        threads.emplace_back([i]() {
            printThreadInfo("Hello from thread " + std::to_string(i));
            std::this_thread::sleep_for(std::chrono::seconds(1));
            printThreadInfo("Thread " + std::to_string(i) + " finished work.");
        });
    }

    for (auto& thread : threads) {
        thread.join();
    }

    return 0;
}

5. 事件处理
在图形界面编程等场景中,lambda表达式可以作为事件处理函数,从而简化了事件处理的逻辑。例如,在GUI框架中,可以将lambda表达式作为事件处理器绑定到特定的按钮或事件上,以响应用户的操作。

#include <iostream>
#include <functional>

class Button {
public:
    using ClickHandler = std::function<void()>;
    Button(const std::string& label) : label_(label) {}
    // 绑定点击事件处理器
    void onClick(ClickHandler handler) 
    {
        clickHandler_ = handler;
    }
    // 模拟按钮点击
    void simulateClick() 
    {
        if (clickHandler_) {
            clickHandler_();
        }
    }
private:
    std::string label_;
    ClickHandler clickHandler_;
};

int main() 
{
    Button myButton("Click Me");
    // 使用lambda表达式绑定点击事件处理器
    myButton.onClick([]() {
        std::cout << "Button was clicked!" << std::endl;
    });
    // 模拟用户点击按钮
    myButton.simulateClick();
    return 0;
}

6. STL容器的遍历
Lambda表达式可以方便地在STL容器中进行遍历操作,提高了代码的可读性和简洁性。例如,可以使用std::for_each算法结合lambda表达式来遍历容器中的元素,并对每个元素执行特定的操作。

std::list<int> lst = {1, 2, 3, 4, 5};
// 使用std::accumulate和lambda表达式计算总和
int sum = std::accumulate(lst.begin(), lst.end(), 0, [](int acc, int x) {
    return acc + x;
});

7. 自定义排序和比较规则
当对自定义类型的数据集合进行排序时,需要根据自定义类型的不同属性去实现不同的排序方法。Lambda表达式可以方便地定义这些排序和比较规则,并将其传递给sort等算法函数。

// 使用lambda表达式对自定义类型的数据集合进行排序。
#include <iostream>
#include <vector>
#include <algorithm>
#include <string>

struct Person {
    std::string name;
    int age;
    double height;
};

int main()
{
    std::vector<Person> people = {
        {"Alice", 30, 5.5},
        {"Bob", 25, 6.0},
        {"Charlie", 35, 5.8},
        {"David", 28, 5.9}
    };

    // 按年龄排序
    std::sort(people.begin(), people.end(), [](const Person& a, const Person& b) {
        return a.age < b.age;
    });
    // 按身高排序
    std::sort(people.begin(), people.end(), [](const Person& a, const Person& b) {
        return a.height < b.height;
    });
    // 按名字排序
    std::sort(people.begin(), people.end(), [](const Person& a, const Person& b) {
        return a.name < b.name;
    });
    return 0;
}

原文地址:https://blog.csdn.net/gaopeng1111/article/details/142798612

免责声明:本站文章内容转载自网络资源,如本站内容侵犯了原著者的合法权益,可联系本站删除。更多内容请关注自学内容网(zxcms.com)!