
C++11 的发布为现代 C++ 带来了许多革命性的特性,其中 Lambda 表达式和函数包装器是提升代码简洁性和灵活性的代表性工具。Lambda 表达式让开发者能够像函数一样轻松地创建匿名函数,而函数包装器则为灵活地管理和调用可调用对象提供了一个强大的抽象。在这篇文章中,我们将详细探讨 Lambda 表达式和函数包装器的概念、用法以及它们如何在实际项目中提升代码的可读性和效率。
在 C++11 中,lambda 表达式(匿名函数)是一种便捷的语法,用于定义短小的函数或回调,特别适合在局部范围内或传递给算法使用。lambda 表达式使代码更简洁、清晰,尤其在需要自定义操作的 STL 算法中非常实用。
Lambda 表达式的基本语法如下:
[capture](parameters) -> return_type {
    // 函数体
};一个简单的 lambda 表达式示例:
#include <iostream>
int main() {
    auto add = [](int a, int b) -> int {
        return a + b;
    };
    std::cout << "Sum: " << add(3, 5) << std::endl;  // 输出:Sum: 8
    return 0;
}这里 add 是一个 lambda 表达式,定义了一个接收两个整数并返回它们和的匿名函数。
捕获列表用于在 lambda 表达式中访问外部变量,常见的捕获方式包括以下几种:
[=]:按值捕获所有外部变量(只读)。[&]:按引用捕获所有外部变量(可修改)。[=, &var]:按值捕获除 var 外的变量,var 按引用捕获。[a, &b]:按值捕获 a,按引用捕获 b。示例:
#include <iostream>
int main() {
    int x = 10;
    int y = 20;
    auto lambda1 = [=]() { return x + y; };  // 按值捕获 x 和 y
    auto lambda2 = [&]() { x += 10; y += 10; };  // 按引用捕获 x 和 y
    lambda2();
    std::cout << "x: " << x << ", y: " << y << std::endl;  // 输出 x: 20, y: 30
    return 0;
}Lambda 表达式在 STL 算法中非常有用,例如,可以用 std::sort 对容器中的元素进行自定义排序:
#include <vector>
#include <algorithm>
#include <iostream>
int main() {
    std::vector<int> numbers = {3, 1, 4, 1, 5};
    // 使用 Lambda 表达式进行降序排序
    std::sort(numbers.begin(), numbers.end(), [](int a, int b) {
        return a > b;
    });
    for (int num : numbers) {
        std::cout << num << " ";
    }
    // 输出: 5 4 3 1 1
    return 0;
}mutable 关键字默认情况下,lambda 表达式按值捕获的变量是只读的。如果需要修改这些变量,可以使用 mutable 关键字:
int value = 10;
auto lambda = [=]() mutable { value += 5; return value; };
std::cout << lambda() << std::endl;  // 输出 15
std::cout << value << std::endl;      // 输出 10,不影响原变量在这个例子中,mutable 允许对按值捕获的 value 进行修改,但不会影响原变量 value 的值。
如果 lambda 表达式的返回类型可以推导,通常可以省略 -> return_type。不过在条件语句等场景下,若返回类型不明确,可以显式指定:
auto divide = [](int a, int b) -> double {
    return static_cast<double>(a) / b;
};C++11 引入了 可变参数模板(Variadic Templates),允许模板接受不定数量的模板参数。这种特性极大地增强了模板的灵活性,适用于泛型编程场景,特别是那些参数个数不固定的情况,例如容器的初始化、递归调用和日志函数等。
可变参数模板使用...来表示不定数量的模板参数。例如:
template<typename... Args>
void func(Args... args) {
    // 函数体
}其中,Args... 表示可以接收任意数量和类型的模板参数。这些参数在函数体内可以通过 args... 进行展开和使用。
可变参数模板允许编写能接受任意数量参数的函数。在 C++11 中,由于没有折叠表达式,可以通过递归方式处理这些参数:
#include <iostream>
void print() {}  // 基础情况
template<typename T, typename... Args>
void print(T first, Args... args) {
    std::cout << first << " ";
    print(args...);  // 递归调用
}
int main() {
    print(1, 2.5, "Hello", 'A');  // 输出: 1 2.5 Hello A
    return 0;
}在这个例子中,print 函数每次取出一个参数,然后递归调用自己处理剩下的参数,直到没有参数为止。
sizeof... 获取参数数量C++11 提供了 sizeof... 运算符,用于获取可变参数的数量:
template<typename... Args>
void countArgs(Args... args) {
    std::cout << "参数数量: " << sizeof...(args) << std::endl;
}调用 countArgs(1, 2, 3); 会输出 参数数量: 3。
std::forward 完美转发在泛型编程中,使用可变参数模板和 std::forward 可以实现完美转发,特别适合构造和包装对象。例如,可以用一个工厂函数将参数转发给构造函数:
#include <utility>
#include <memory>
template<typename T, typename... Args>
std::unique_ptr<T> createObject(Args&&... args) {
    return std::make_unique<T>(std::forward<Args>(args)...);  // 完美转发
}这段代码将传入的参数 args... 以完美转发的方式传递给对象 T 的构造函数,从而创建对象。
std::functionstd::function 是 C++11 引入的一个通用函数包装器,可以存储、复制和调用任何可调用对象,包括普通函数、lambda 表达式、函数指针和函数对象。它提供了一个统一的接口,方便将各种不同类型的可调用对象作为参数传递或返回值返回。
std::function 的基本语法std::function 是一个模板类,接受一个函数签名(即返回类型和参数列表)作为模板参数。例如:
#include <functional>
#include <iostream>
// 泛型
std::function<ReturnType(ParameterType1,...,ParameterTypeN)>
// 例如定义一个函数类型:接收两个 int,返回 int
std::function<int(int, int)> func;在这个例子中,func 可以包装任何符合 int(int, int) 签名的可调用对象,即接收两个 int 参数并返回 int。
std::function 包装不同类型的可调用对象int add(int a, int b) {
    return a + b;
}
int main() {
    std::function<int(int, int)> func = add;
    std::cout << "Result: " << func(2, 3) << std::endl;  // 输出:Result: 5
    return 0;
}int main() {
    std::function<int(int, int)> func = [](int a, int b) {
        return a + b;
    };
    std::cout << "Result: " << func(2, 3) << std::endl;  // 输出:Result: 5
    return 0;
}#include <iostream>
#include <functional>
struct Adder {
    int operator()(int a, int b) const {
        return a + b;
    }
};
int main() {
    std::function<int(int, int)> func = Adder();
    std::cout << "Result: " << func(2, 3) << std::endl;  // 输出:Result: 5
    return 0;
}std::function 作为参数std::function 可以用来定义一个函数参数,允许将任何符合签名的可调用对象传入该参数。
#include <iostream>
#include <functional>
void execute(std::function<void(int)> func, int value) {
    func(value);  // 调用传入的可调用对象
}
int main() {
    // 使用 lambda 表达式作为参数
    execute([](int x) { std::cout << "Value: " << x << std::endl; }, 10);  // 输出:Value: 10
    return 0;
}std::function的实际应用力扣真题[逆波兰表达式求值](https://leetcode.cn/problems/evaluate-reverse-polish-notation/description/)
class Solution {
public:
    int evalRPN(vector<string>& tokens) {
        map<string, function<int(int, int)>> mp = {
            {"+", [](int x, int y){return x + y;}},
            {"-", [](int x, int y){return x - y;}},
            {"*", [](int x, int y){return x * y;}},
            {"/", [](int x, int y){return x / y;}}
        };
        stack<int> s;
        for(const auto& str :tokens){
            if(mp.find(str) != mp.end()){
                int right = s.top();
                s.pop();
                int left = s.top();
                s.pop();
                int ret = mp[str](left, right);
                s.push(ret);
            }
            else{
                s.push(stoi(str));
            }
        }
        return s.top();
    }
};std::bindstd::bind 是 C++11 引入的一个函数工具,用于将函数的某些参数绑定到特定的值,从而生成一个新的可调用对象。这个新对象可以在需要的时候被调用,减少了重复传参的麻烦。std::bind 可以将普通函数、成员函数、函数对象的部分参数预先绑定,也可以为其指定占位符,从而延迟参数传递。
std::bind 的基本语法std::bind 的基本用法如下:
std::bind(callable, arg1, arg2, ..., argN);std::placeholders::_1,std::placeholders::_2 等)。假设有一个加法函数 add,我们可以用 std::bind 将其中一个参数绑定到特定的值:
#include <iostream>
#include <functional>
int add(int a, int b) {
    return a + b;
}
int main() {
    // 使用 std::bind 将第一个参数绑定为 10
    auto addTen = std::bind(add, 10, std::placeholders::_1);
    std::cout << "Result: " << addTen(5) << std::endl;  // 输出:Result: 15
    return 0;
}在这里,addTen 是一个新的可调用对象,它把 add 函数的第一个参数固定为 10,只需要传入第二个参数即可调用。
std::bind 使用占位符 std::placeholders::_1, std::placeholders::_2, 等等,来指定调用时需要传递的参数位置。例如:
#include <iostream>
#include <functional>
void print(int x, int y, int z) {
    std::cout << x << ", " << y << ", " << z << std::endl;
}
int main() {
    // 使用 std::bind 重新排列参数顺序
    auto printReversed = std::bind(print, std::placeholders::_3, std::placeholders::_2, std::placeholders::_1);
    printReversed(1, 2, 3);  // 输出:3, 2, 1
    return 0;
}在这个例子中,printReversed 是一个新的可调用对象,参数顺序被反转,传入 (1, 2, 3) 时,输出为 3, 2, 1。
std::bind 也可以绑定成员函数,使用时需要传入类对象的指针:
#include <iostream>
#include <string>
#include <functional>
class MyClass {
public:
    void printMessage(const std::string& message) {
        std::cout << "Message: " << message << std::endl;
    }
};
int main() {
    MyClass obj;
    // 将成员函数与对象绑定(三种方法)
    // 1.对象地址
    auto printFunc1 = std::bind(&MyClass::printMessage, &obj, std::placeholders::_1);
    printFunc1("Hello, world1");  // 输出:Message: Hello, world1
    // 2.对象
    auto printFunc2 = std::bind(&MyClass::printMessage, obj, std::placeholders::_1);
    printFunc2("Hello, world2");  // 输出:Message: Hello, world2
    // 3.匿名对象
    auto printFunc3 = std::bind(&MyClass::printMessage, MyClass(), std::placeholders::_1);
    printFunc3("Hello, world3");  // 输出:Message: Hello, world3
    return 0;
}在这里,printFunc 将 obj 对象和 printMessage 函数绑定在一起,因此 printFunc 成为一个只需传入 message 参数的可调用对象。
std::bind 同样可以绑定类的成员变量,用于获取或设置特定对象的成员变量值。
#include <iostream>
#include <functional>
class MyClass {
public:
    int value = 0;
};
int main() {
    MyClass obj;
    // 绑定成员变量,创建 getter 和 setter
    auto getValue = std::bind(&MyClass::value, &obj);
    auto setValue = std::bind(&MyClass::value, &obj) = std::placeholders::_1;
    setValue(42);
    std::cout << "Value: " << getValue() << std::endl;  // 输出:Value: 42
    return 0;
}std::bind 的应用场景std::bind 将某个函数的参数预设,用于异步调用。std::bind 可以将函数和参数绑定在一起,在之后的特定时刻再执行。std::bind 可以适配不同函数的参数列表,减少代码重复。C++11 的 Lambda 表达式和函数包装器为开发者提供了更灵活、更高效的工具,用于解决复杂的编程问题。从简化回调函数到灵活管理可调用对象,这些特性为现代 C++ 编程提供了新的思路。在深入理解它们的原理和最佳实践后,你将能更自信地运用这些工具编写出简洁、可维护的代码。未来的 C++ 学习之旅中,期待你用这些新特性,探索更强大的编程世界!
今天的分享到这里就结束啦!如果觉得文章还不错的话,可以三连支持一下,17的主页还有很多有趣的文章,欢迎小伙伴们前去点评,您的支持就是17前进的动力!