Function / Lambda / Bind

一、Lambda 表达式(最常用,先学)

1.1 基本语法

cpp

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

cpp

// 最简单的 Lambda:无捕获,无参数
auto f = []() { return 42; };
std::cout << f();  // 42

// 有参数
auto add = [](int a, int b) { return a + b; };
std::cout << add(3, 4);  // 7

// 显式返回类型(通常可省略,编译器自动推导)
auto div = [](int a, int b) -> double {
    return static_cast<double>(a) / b;
};

1.2 捕获列表

捕获列表决定 Lambda 可以使用哪些外部变量:

表格

捕获方式 写法 含义
不捕获 [] 不能使用外部变量
值捕获 [x] 拷贝 x 的值进 Lambda
引用捕获 [&x] 引用外部 x,可修改
隐式值捕获 [=] 拷贝所有用到的外部变量
隐式引用捕获 [&] 引用所有用到的外部变量
混合捕获 [=, &x] 默认拷贝,x 用引用
混合捕获 [&, x] 默认引用,x 用拷贝
移动捕获 [x = std::move(x)] C++14,移动语义

cpp

int x = 10, y = 20;

// 值捕获:修改的是 Lambda 内部的拷贝,不影响外部 x
auto f1 = [x]() { x = 100; };  // ❌ 编译错误!值捕获默认是 const
auto f2 = [x]() mutable { x = 100; };  // ✅ mutable 允许修改拷贝
// 外部 x 仍然是 10

// 引用捕获:修改的是外部变量本身
auto f3 = [&x]() { x = 100; };
f3();
// 外部 x 变成 100

// 隐式捕获
auto f4 = [=]() { return x + y; };   // 拷贝 x 和 y
auto f5 = [&]() { x++; y++; };        // 引用 x 和 y

// 混合捕获
auto f6 = [=, &x]() { x++; return y; };  // x 引用,y 拷贝

1.3 Lambda 的本质

Lambda 是编译器生成的匿名类(闭包):

cpp

// 这个 Lambda:
auto add = [](int a, int b) { return a + b; };

// 编译器大致生成:
struct __lambda_1 {
    int operator()(int a, int b) const {
        return a + b;
    }
};
__lambda_1 add;

有捕获时,编译器生成带成员变量的类:

cpp

int x = 10;
auto f = [x](int a) { return x + a; };

// 编译器生成:
struct __lambda_2 {
    int x;  // 捕获的值作为成员
    __lambda_2(int _x) : x(_x) {}
    int operator()(int a) const { return x + a; }
};

二、std::function — 函数包装器

2.1 为什么需要 std::function

Lambda、函数指针、函数对象类型各不相同,无法统一存储或传递:

cpp

// 三种不同的类型!
void foo() {}                           // 函数
auto lambda = []() {};                  // Lambda(匿名类)
struct Functor { void operator()() {} };
Functor functor;                        // 函数对象

// ❌ 不能用同一个变量存储
// void* p = &foo;      // 可以,但类型不安全
// p = &lambda;         // 编译错误!类型不同

std::function 统一包装所有可调用对象:

cpp

#include <functional>

std::function<void()> f;  // 可以存储任何无参数、返回 void 的可调用对象

f = foo;                  // ✅ 存储普通函数
f = lambda;               // ✅ 存储 Lambda
f = functor;              // ✅ 存储函数对象
f = std::bind(foo, ...);  // ✅ 存储绑定表达式

f();  // 统一调用

2.2 声明与使用

cpp

#include <functional>
#include <iostream>

// 声明:返回 int,接受两个 int 参数
std::function<int(int, int)> op;

// 赋值各种可调用对象
op = [](int a, int b) { return a + b; };
std::cout << op(3, 4);  // 7

op = [](int a, int b) { return a - b; };
std::cout << op(3, 4);  // -1

op = std::multiplies<int>();  // 标准库函数对象
std::cout << op(3, 4);  // 12

// 存储成员函数(需要对象绑定)
struct Calculator {
    int add(int a, int b) { return a + b; }
};
Calculator calc;
std::function<int(int, int)> mem_fn = std::bind(&Calculator::add, &calc, std::placeholders::_1, std::placeholders::_2);
std::cout << mem_fn(3, 4);  // 7

2.3 空状态检查

cpp

std::function<void()> f;

if (!f) {
    std::cout << "f 是空的,不能调用\n";
}

f = []() { std::cout << "hello\n"; };
if (f) {
    f();  // 输出 hello
}

f = nullptr;  // 清空
// f();       // ❌ 空 function 调用抛 std::bad_function_call

2.4 与函数指针的区别

表格

特性 函数指针 std::function
存储对象 只能存普通函数 函数、Lambda、函数对象、bind
类型安全 强(编译期检查签名)
开销 极小(就是一个地址) 有类型擦除开销(虚函数或函数指针)
拷贝 轻量 可能深拷贝捕获的变量
空状态 可以是 nullptr 可以显式清空

三、std::bind — 参数绑定

3.1 基本用法

std::bind 将函数和部分参数绑定,生成新的可调用对象:

cpp

#include <functional>

void print(int a, int b, int c) {
    std::cout << a << " " << b << " " << c << "\n";
}

// 绑定前两个参数,生成只接受第3个参数的函数
auto f1 = std::bind(print, 10, 20, std::placeholders::_1);
f1(30);  // 输出: 10 20 30

// 绑定第1和第3个参数,第2个参数留空
auto f2 = std::bind(print, 10, std::placeholders::_1, 30);
f2(20);  // 输出: 10 20 30

// 调整参数顺序
auto f3 = std::bind(print, std::placeholders::_2, std::placeholders::_1, 40);
f3(50, 60);  // 输出: 60 50 40(_1=50, _2=60,但 print 先接收 _2)

3.2 占位符

cpp

using namespace std::placeholders;  // _1, _2, _3...

auto f = std::bind(print, _1, _2, 100);
f(1, 2);  // 输出: 1 2 100

// 占位符编号表示新函数的第几个参数
auto g = std::bind(print, _2, _1, _3);
g(1, 2, 3);  // 输出: 2 1 3(_2=2, _1=1, _3=3)

3.3 绑定成员函数

cpp

struct Calculator {
    int add(int a, int b) { return a + b; }
    int value = 0;
};

Calculator calc;

// 绑定成员函数:第1个参数是对象指针/引用
auto f = std::bind(&Calculator::add, &calc, _1, _2);
std::cout << f(3, 4);  // 7

// 绑定成员变量
auto get_val = std::bind(&Calculator::value, &calc);
calc.value = 42;
std::cout << get_val();  // 42

3.4 bind vs Lambda

C++11 后,Lambda 基本取代了 bind

cpp

// 用 bind
auto f1 = std::bind(print, 10, _1, 30);

// 用 Lambda(更清晰,推荐)
auto f2 = [](int x) { print(10, x, 30); };

// 用 bind 绑定成员函数
auto f3 = std::bind(&Calculator::add, &calc, _1, _2);

// 用 Lambda
auto f4 = [&calc](int a, int b) { return calc.add(a, b); };

建议:优先用 Lambda,只有在需要复杂参数重排绑定成员函数时考虑 bind


四、综合对比与选择

表格

场景 推荐方案
简单内联函数 Lambda
需要捕获外部变量 Lambda
存储回调函数 std::function + Lambda
参数重排/部分应用 std::bind 或 Lambda
泛型算法参数 Lambda 或函数对象
需要类型擦除(运行时多态) std::function

constexpr

1.1 constexpr 是什么?

constexpr 是 C++11 引入的关键字,表示编译时常量——其值在编译期就能确定,而不是运行时计算。

cpp

constexpr int size = 10;      // 编译期常量
int arr[size];                // ✅ 可以,size 是编译期已知的

const int n = 10;
int arr2[n];                  // C++ 中通常也可以,但 const 不保证编译期确定

const vs constexpr

表格

特性 const constexpr
含义 只读,运行时不修改 编译期就能确定值
初始化 运行时或编译期 必须是编译期
用于数组大小 有时可以(看编译器) ✅ 一定可以
用于模板参数 ❌ 不行 ✅ 可以

cpp

const int a = get_value();     // ✅ 可以,运行时初始化
// constexpr int b = get_value(); // ❌ 错误!必须是编译期可计算

constexpr int c = 10;          // ✅ 编译期常量

二、constexpr 变量

2.1 声明与初始化

cpp

constexpr int max_size = 100;
constexpr double pi = 3.14159;

// 必须是编译期可计算
constexpr int square(int x) { return x * x; }
constexpr int result = square(5);  // 25,编译期计算

// ❌ 错误:不能运行时初始化
int n;
std::cin >> n;
// constexpr int m = n;  // 编译错误!

2.2 用于数组大小和模板参数

cpp

constexpr int size = 10;

int arr[size];                    // ✅ 编译期确定大小
std::array<int, size> std_arr;    // ✅ 模板参数需要编译期常量

// 模板参数
template<int N>
struct Buffer {
    char data[N];
};
Buffer<size> buf;                 // ✅

// switch case 标签
switch (x) {
    case size:                   // ✅ case 需要编译期常量
        // ...
}

三、constexpr 函数

3.1 基本规则(C++11/14)

cpp

// C++11: 函数体只能有一条 return 语句
constexpr int factorial(int n) {
    return n <= 1 ? 1 : n * factorial(n - 1);
}

// C++14 起放宽:可以有多个语句
constexpr int factorial_cpp14(int n) {
    int result = 1;           // C++11 不允许,C++14 允许
    for (int i = 1; i <= n; ++i) {
        result *= i;
    }
    return result;
}

constexpr int f5 = factorial(5);  // 120,编译期计算

3.2 constexpr 函数的双重特性

cpp

constexpr int square(int x) {
    return x * x;
}

// 场景1:编译期调用(需要编译期常量)
constexpr int a = square(5);      // 25,编译期计算
int arr[square(3)];               // 9,数组大小

// 场景2:运行时调用(参数是运行时的)
int n;
std::cin >> n;
int b = square(n);               // 运行时计算,和普通函数一样

关键constexpr 函数可以在编译期调用,也可以在运行时调用。编译器会尽量在编译期计算,但如果参数是运行时的,就退化为普通函数。

3.3 C++17 if constexpr(编译期条件分支)

cpp

template<typename T>
auto get_value(T t) {
    if constexpr (std::is_pointer_v<T>) {      // 编译期判断
        return *t;                            // 解引用指针
    } else {
        return t;                             // 直接返回
    }
}

int x = 10;
int* p = &x;

auto a = get_value(x);   // T=int,走 else 分支
auto b = get_value(p);   // T=int*,走 if 分支,返回 int

if constexpr 在编译期确定走哪个分支,另一个分支不会编译,避免类型错误。

二、constexpr 的优点

表格

优点 说明
性能 计算在编译期完成,运行时零开销
类型安全 编译期检查,避免运行时错误
可用于模板/数组 const 不一定能,但 constexpr 一定能
优化机会 编译器可以内联、常量折叠、消除死代码
明确语义 代码意图清晰:这个值一定是编译期确定的

三、constexpr 能替代 const 吗?(仅这一条constexpr更好)

不能全部替代,它们语义不同:

表格

场景 const constexpr
编译期确定的常量 ✅ 可以 更好,语义更明确
Logo

Agent 垂直技术社区,欢迎活跃、内容共建。

更多推荐