从C回调函数到C++Lambda

从回调函数到 Lambda 表达式:C++ 中的函数式编程

在 C 编程中,回调函数是一种常见的模式,用于在某些事件发生时执行特定的功能。传统的方式是通过函数指针或函数对象来实现回调,2011年发布的 C++11 Lambda 表达式为这一过程带来了更为便利和灵活的选择。

一、回调函数的基础

首先,回顾一下传统的回调函数概念。回调函数是在某个事件发生时被调用的函数。例如,考虑以下使用函数指针的回调函数示例:

c 复制代码
#include <iostream>

// 回调函数
void callbackFunction(int value) {
    std::cout << "Callback function: " << value << std::endl;
}

// 接受回调函数的函数
void processValues(int values[], int size, void (*callback)(int)) {
    for (int i = 0; i < size; ++i) {
        callback(values[i]);
    }
}

int main() {
    int values[] = {1, 2, 3, 4, 5};
    
    // 通过函数指针传递回调函数
    processValues(values, 5, callbackFunction);

    return 0;
}

在这个例子中,callbackFunction 是一个简单的回调函数,用于处理传递给 processValues 函数的整数数组中的每个值。通过函数指针,我们将回调函数传递给 processValues

二、引入 Lambda 表达式

C++11 引入了 Lambda 表达式,这是一种在需要时内联定义匿名函数的便捷方式。Lambda 表达式不仅使代码更简洁,而且还提供了捕获外部变量、自动类型推导等功能。

现在,可以使用 Lambda 表达式重写上述示例:

c 复制代码
#include <iostream>

int main() {
    int values[] = {1, 2, 3, 4, 5};

    // 使用 Lambda 表达式作为回调函数
    auto callbackLambda = [](int value) {
        std::cout << "Lambda callback: " << value << std::endl;
    };

    // 通过 Lambda 表达式传递回调函数
    for (int value : values) {
        callbackLambda(value);
    }

    return 0;
}

Lambda 表达式通过 [] 中的捕获列表来捕获外部变量。在这个例子中,Lambda 表达式捕获了 value 变量,使得回调函数能够访问其值。Lambda 表达式的语法更为紧凑,使得定义和使用匿名函数变得更加直观。

三、Lambda 表达式的使用场景

Lambda 表达式在各种情况下都能展现其强大之处。以下是一些 Lambda 表达式的常见使用场景:

1. 简化算法

在需要传递函数对象的地方,Lambda 表达式可以使代码更简洁。例如,使用 std::sort 排序一个容器:

c 复制代码
std::vector<int> numbers = {3, 1, 4, 1, 5, 9, 2, 6, 5};
std::sort(numbers.begin(), numbers.end(), [](int a, int b) { return a < b; });

2. 回调函数

当需要在某个函数中传递一个回调函数时,使用 Lambda 表达式可以避免为简短的功能编写额外的函数定义。

c 复制代码
void processNumbers(const std::vector<int>& numbers, std::function<void(int)> callback) {
    for (int num : numbers) {
        callback(num);
    }
}

// 使用 Lambda 表达式作为回调函数
processNumbers(numbers, [](int num) { std::cout << num << " "; });

3. 异步编程

在异步编程中,Lambda 表达式可以方便地表示异步任务,例如使用 C++11 中引入的 std::async

c 复制代码
#include <future>

int main() {
    auto future = std::async([]() { return 100; });
    int result = future.get();
    std::cout << result << std::endl;
    return 0;
}

4. 简化某些函数调用

在一些场景下,Lambda 表达式可以使代码更紧凑,不必为短暂使用的函数编写完整的定义。

c 复制代码
cppCopy code
std::vector<int> numbers = {1, 2, 3, 4, 5};
auto square = [](int x) { return x * x; };

// 使用 Lambda 表达式将每个元素平方
std::transform(numbers.begin(), numbers.end(), numbers.begin(), square);

五、Lambda 表达式实现基础

Lambda 表达式在编译时会被转化为一个闭包类型(closure type)。这个类型具有一个调用运算符(operator())重载,使得 Lambda 表达式可以像函数一样被调用。Lambda 表达式的编译过程大致包括以下步骤:

  1. 生成闭包类型: 编译器会为 Lambda 表达式生成一个特定的闭包类型,这个类型是匿名的,并包含了 Lambda 表达式中所用到的局部变量的副本(或引用,根据捕获方式)。
  2. 生成调用运算符: Lambda 表达式中的代码块会被转换为生成的闭包类型的调用运算符。这使得闭包类型的对象可以被调用,就像一个普通的函数一样。
  3. 生成实例: 在使用 Lambda 表达式的地方,编译器会为 Lambda 表达式生成一个实例,并将捕获的变量的值传递给实例。这个实例就可以被调用,执行 Lambda 表达式中的代码。

Lambda 表达式的生成闭包类型的一般规则包括生成闭包类型、捕获外部变量、生成构造函数、生成调用运算符等步骤。具体的实现可能因编译器和编译选项而异,但上述规则描述了生成闭包类型的一般思路。

从汇编上看,就是调用匿名类的构造函数生成一个实例,

css 复制代码
01096489  push   eax  
0109648A  lea    ecx,[lambda]  
0109648D  call   <lambda_3762a67e7879953a702a38d>::<lambda_3762a67e7879953a702a38d> (01093970h)  

	// 它的实例被调用时,就是调用这个对象的 operator() 方法
	int result = lambda(5);
01096492  push        5  
01096494  lea         ecx,[lambda]  
01096497  call        <lambda_3762a67e7879953a702a38d>::operator() (01094C90h)  
0109649C  mov         dword ptr [result],eax

总结

大师讲过,源码面前,了无密码。同样在汇编面前,CPP的新特性无非靠着强大的编译器,最终代码都是面向CPU的,在指令层面。lambda表达式也是如此。

相关推荐
归寻太乙15 分钟前
C++函数重载完成日期类相关计算
开发语言·c++
尽蝶叙18 分钟前
C++:分苹果【排列组合】
开发语言·c++·算法
憧憬成为原神糕手43 分钟前
c++_list
开发语言·c++
zyh200504301 小时前
c++的decltype关键字
c++·decltype
2401_862886781 小时前
蓝禾,汤臣倍健,三七互娱,得物,顺丰,快手,游卡,oppo,康冠科技,途游游戏,埃科光电25秋招内推
前端·c++·python·算法·游戏
徐霞客3202 小时前
对于C++继承中子类与父类对象同时定义其析构顺序的探究
开发语言·c++
敲上瘾2 小时前
多态的使用和原理(c++详解)
开发语言·数据结构·c++·单片机·aigc·多态·模拟
心怀花木2 小时前
【C++】list容器的基本使用
c++·stl·list
我命由我123452 小时前
GPIO 理解(基本功能、模拟案例)
linux·运维·服务器·c语言·c++·嵌入式硬件·c#
weixin_515033933 小时前
ccfcsp-202006(4、5)
c++·算法