c++基础补强--Day08
·
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 = λ // 编译错误!类型不同
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); // 72.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_call2.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(); // 423.4
bindvs LambdaC++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 不保证编译期确定
constvsconstexpr:表格
特性 constconstexpr含义 只读,运行时不修改 编译期就能确定值 初始化 运行时或编译期 必须是编译期 用于数组大小 有时可以(看编译器) ✅ 一定可以 用于模板参数 ❌ 不行 ✅ 可以 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编译期确定的常量 ✅ 可以 ✅ 更好,语义更明确
更多推荐


所有评论(0)