【C++】 C++11 知识点梳理(下)


C++ Lambda 完整详解

一、Lambda 是什么

Lambda 是 C++11 引入的匿名可调用对象,无需定义独立函数 / 仿函数类,就地书写一段可执行逻辑,常搭配算法(sort/for_each)、异步线程、回调使用。

基础语法总览

复制代码
[捕获列表](参数列表) mutable -> 返回类型 { 函数体 };

拆解五部分:

  1. []:捕获外部变量(核心难点)
  2. ():形参列表,无参可省略()(C++14 起)
  3. mutable可选,允许修改值捕获的变量
  4. **-> T:尾置返回类型,**能自动推导时可省略
  5. {}:函数执行体

二、捕获列表 [] 全规则(重点)

捕获决定 lambda 能否访问函数外部局部变量,分值捕获、引用捕获、隐式捕获、this 捕获。

1. 空捕获 []

不捕获任何外部变量,仅能使用全局变量、静态变量、自身入参。

复制代码
int a = 10;
auto f = [](){ cout << a; }; // 报错,未捕获a

2. 值捕获 [x]

拷贝一份变量,lambda 内只读,无法修改原变量,也不能修改拷贝副本(不加mutable

复制代码
int num = 100;
auto f = [num](){ cout << num; }; 
f(); // 输出100
num = 200;
f(); // 依旧100,捕获的是拷贝时的值

3. 引用捕获 [&x]

捕获变量的引用,lambda 内读写都会同步修改外部原变量

复制代码
int num = 100;
auto f = [&num](){ num++; };
f();
cout << num; // 101

4. 隐式批量捕获

  • [=]:全部外部局部变量值捕获

  • [&]:全部外部局部变量引用捕获

    int a=1,b=2,c=3;
    auto f1 = ={ cout << a << b << c; }; // 全部值拷贝
    auto f2 = &{ a++;b++;c++; }; // 全部引用

5. 混合捕获(常用)

批量隐式 + 单独指定捕获方式,单独变量必须和批量方式相反

  • [=, &x]:默认全部值捕获,唯独 x 用引用

  • [&, x]:默认全部引用捕获,唯独 x 用值拷贝

    int a=1,b=2;
    auto f = =, &a{
    a++; // 引用修改原a
    cout << b; // 值拷贝,只读
    };

6. this 捕获 [this] / [*this]

仅在类成员函数内使用,访问成员变量 / 成员函数:

  1. [this]:捕获对象指针,引用原对象,修改成员会影响外部

  2. [*this](C++17):拷贝当前对象副本,修改仅作用于副本,不影响原对象

    struct Test{
    int val = 10;
    void func(){
    auto f1 = this{ val++; };
    auto f2 = *this{ val++; }; // 副本,外部val不变
    f1();
    }
    };

7. 初始化捕获(C++14)

捕获时创建临时变量,可移动捕获unique_ptr等不可拷贝对象

复制代码
auto p = make_unique<int>(10);
auto f = [ptr=move(p)](){ cout << *ptr; };
// p 已被移动,外部失效

捕获禁止事项

  1. 不能同时 [=, &],冲突;
  2. 全局变量不需要捕获,直接访问;
  3. 捕获列表不能重复写同一个变量:[a,a] 编译报错。

三、mutable 关键字

值捕获默认拷贝的变量是const,无法修改副本;加mutable解除 const 限制,仅修改拷贝副本,不影响外部原变量。

复制代码
int x = 10;
auto f = [x]() mutable {
    x++; // 允许修改副本
    cout << x;
};
f(); // 11
cout << x; // 外部依旧10

mutable 存在时,参数括号()不能省略。


四、返回类型

1. 自动推导(绝大多数场景省略->

  • 多条 return 返回同类型:自动推导

  • 单条 return、无 return(返回 void):自动推导

    auto add = [](int a,int b){ return a+b; };

2. 必须显式指定 -> T 的场景

函数体内多条return且返回值类型不同:

复制代码
// 必须写 -> double,否则推导失败
auto f = [](int x)->double{
    if(x>0) return x;
    else return 0.5;
};

3. 返回引用

复制代码
int x=10;
auto getX = [&]()->int& { return x; };
getX() = 100; // 外部x变为100

五、参数列表特性

  1. C++14 支持泛型 lambda(auto形参,模板效果)

    auto print = [](auto val){ cout << val; };
    print(10);
    print("hello");
    print(3.14);

  2. C++20 支持模板 lambda,更精细控制泛型

    auto f = [](T a){};

  3. 支持右值引用、默认参数

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


六、lambda 本质(底层原理)

编译器遇到 lambda,会自动生成一个匿名仿函数类:

  1. 捕获的变量作为类的成员;
  2. operator()重载为函数体;
  3. mutable 控制operator()是否为 const;

示例:

复制代码
int a=10;
auto lam = [a](int x){ return a+x; };

编译器等价生成:

复制代码
class __Lambda_xxx{
private:
    int a; // 值捕获成员
public:
    __Lambda_xxx(int _a):a(_a){}
    int operator()(int x) const { // 无mutable则const
        return a+x;
    }
};
__Lambda_xxx lam(a);

特性推导:

  1. 无捕获的 lambda []() 可隐式转为普通函数指针;
  2. 有捕获的 lambda 无法转函数指针,只能用function包装。

function 包装 lambda

用于存储、传递可调用对象:

复制代码
#include <functional>
std::function<int(int,int)> add = [](int a,int b){return a+b;};
cout << add(1,2);

七、常用实战场景

1. 标准算法 sort 自定义排序

复制代码
vector<int> v = {3,1,4,2};
// 降序排序
sort(v.begin(),v.end(),[](int a,int b){ return a>b; });

// 按结构体成员排序
struct Person{ string name; int age; };
vector<Person> ps = {{"A",20},{"B",18}};
sort(ps.begin(),ps.end(),[](const Person& x,const Person& y){
    return x.age < y.age;
});

2. for_each 遍历

复制代码
vector<int> v={1,2,3};
for_each(v.begin(),v.end(),[](int x){ cout << x*2 << " "; });

3. 线程回调 std::thread

复制代码
#include <thread>
int num = 10;
thread t([&num](){
    for(int i=0;i<5;i++) num++;
});
t.join();

4. 递归 lambda(难点)

lambda 不能捕获自身,需用function先声明再捕获:

复制代码
function<int(int)> fib;
fib = [&fib](int n)->int{
    if(n<=2) return 1;
    return fib(n-1)+fib(n-2);
};
cout << fib(10);

5. 移动捕获资源(unique_ptr)

复制代码
auto ptr = make_unique<string>("test");
auto f = [p=move(ptr)](){ cout << *p; };
f();

function 完整详解

一、std::function 是什么

头文件:<functional>``std::function<R(Args...) 是通用可调用对象包装器,可以统一存放、传递任意可调用类型:普通函数、函数指针、lambda、仿函数(重载operator()的类)、类成员函数、静态成员函数。

核心作用

  1. 统一类型:不同形式可调用物可以存到同一个容器、参数传递;
  2. 延迟调用、回调、存储 lambda(尤其是递归 lambda);
  3. 替代笨重的函数指针,支持捕获的 lambda。

二、基础语法

复制代码
#include <functional>
// R:返回值,Args...:参数列表
std::function<返回类型(参数1类型, 参数2类型...)> 变量名;

示例基础用法

cpp 复制代码
#include <iostream>
#include <functional>
using namespace std;

// 普通函数
int add(int a, int b) {
    return a + b;
}

int main() {
    // 包装普通函数
    function<int(int, int)> f1 = add;
    cout << f1(1, 2) << endl;

    // 包装lambda
    function<int(int, int)> f2 = [](int x, int y) {
        return x * y;
    };
    cout << f2(3, 4) << endl;

    // 无返回无参数
    function<void()> f3 = []() {
        cout << "hello function\n";
    };
    f3();
    return 0;
}

三、能包装的所有可调用对象

1. 普通全局 / 静态函数

复制代码
void print(string s) { cout << s; }
function<void(string)> f = print;
f("test");

2. Lambda(有无捕获都可以)

函数指针不能存带捕获的 lambda,但function可以,这是最大优势:

复制代码
int x = 10;
// 带引用捕获的lambda,只能用function存
function<int()> f = [&x](){ return x + 5; };
cout << f();

3. 仿函数(重载 operator () 的类)

复制代码
struct FuncObj {
    int operator()(int a) {
        return a * a;
    }
};

int main() {
    function<int(int)> f = FuncObj();
    cout << f(6);
    return 0;
}

4. 类静态成员函数

复制代码
struct Test {
    static int sub(int a, int b) {
        return a - b;
    }
};

function<int(int, int)> f = Test::sub;
cout << f(10, 3);

5. 普通成员函数(特殊写法,必须绑定对象)

成员函数隐藏第一个this*参数,要用bind或传入对象:

复制代码
#include <functional>
#include <iostream>
using namespace std;

struct Person {
    int age = 18;
    void show(int num) {
        cout << age + num;
    }
};

int main() {
    Person p;
    // 方式1:bind绑定对象
    function<void(int)> f1 = bind(&Person::show, &p, placeholders::_1);
    f1(5);

    // 方式2:function签名带上对象指针
    function<void(Person*, int)> f2 = &Person::show;
    f2(&p, 10);
    return 0;
}

四、关键成员函数

  1. operator():调用包装的可调用对象

    复制代码
    f(1,2);
  2. bool operator bool():判断是否持有有效可调用对象

    复制代码
    function<void()> f;
    if (!f) {
        cout << "为空,不能调用";
    }
  3. reset():清空,置为空

    复制代码
    f.reset();
  4. target<T>():取出底层存储的可调用对象(少用)


五、经典场景:递归 Lambda

lambda 不能直接捕获自身,必须先用function声明,再赋值、捕获自己:

复制代码
#include <iostream>
#include <functional>
using namespace std;

int main() {
    function<int(int)> fib;
    fib = [&fib](int n) -> int {
        if (n <= 2) return 1;
        return fib(n - 1) + fib(n - 2);
    };
    cout << fib(10);
    return 0;
}

六、配合容器存储回调

可以把多个不同 lambda 统一放进 vector:

复制代码
#include <iostream>
#include <functional>
#include <vector>
using namespace std;

int main() {
    vector<function<int(int)>> ops;
    ops.push_back([](int x) { return x + 1; });
    ops.push_back([](int x) { return x * 2; });
    ops.push_back([](int x) { return x * x; });

    for (auto& f : ops) {
        cout << f(5) << " ";
    }
    return 0;
}

七、std::function vs 函数指针

特性 函数指针 std::function
存储带捕获 lambda ❌ 不支持 ✅ 支持
存储仿函数、成员函数 ❌ 麻烦 ✅ 统一包装
类型统一存放容器 限制多 任意可调用统一类型
开销 极小 少量堆内存开销
空判断 需判空指针 直接 if 判断

无捕获 lambda 可以隐式转函数指针;有捕获 lambda只能用 function。


八、常见坑

  1. function调用直接崩溃

    复制代码
    function<void()> f;
    f(); // 未赋值,运行时报错

    解决:调用前判断 if(f)

  2. 成员函数绑定忘记传对象,调用崩溃

  3. 递归 lambda 必须[&fib]引用捕获 function 变量

  4. 频繁创建销毁大量 function 会有少量性能损耗,高频计算场景尽量直接 lambda 调用


九、bind 搭配 function

bind用于提前绑定参数,生成新可调用对象存入 function:

复制代码
#include <functional>
#include <iostream>
using namespace std;

int add(int a, int b) {
    return a + b;
}

int main() {
    // 固定第一个参数为10,只留第二个参数可变
    function<int(int)> add10 = bind(add, 10, placeholders::_1);
    cout << add10(5); // 15
    return 0;
}

placeholders::_1_2代表调用时传入的第 1、2 个参数。


十、无返回值简写 void

复制代码
// 接收两个int,无返回
function<void(int, int)> print = [](int a, int b) {
    cout << a << b;
};

十一、题目运用

逆波兰表达式求值

150. 逆波兰表达式求值https://leetcode.cn/problems/evaluate-reverse-polish-notation/

给你一个字符串数组 tokens ,表示一个根据 逆波兰表示法 表示的算术表达式。

请你计算该表达式。返回一个表示表达式值的整数

通用写法
cpp 复制代码
class Solution {
public:
    int shift(string& x) {
        int i = 0, t = 0,sign=1;
        while (i < x.size()) {
            if(x[i]=='-')
            {
                sign=-1;
                i=1;
            }
            t = t * 10 + (x[i] - '0');
            i++;
        }
        int result=sign*t;
        return result;
    }
    int evalRPN(vector<string>& tokens) {
        stack<int> st;
        int n = 0, x = 0, sum = 0;
        char c;
        string d;
        while (n < tokens.size()) {
            //是不是数字
            d = (tokens[n]);
            if ((d[0] >= '0' && d[0] <= '9')||(d.size() > 1 && d[0] == '-'))
            {
                x = stoi(d);
                st.push(x);
            }
            //不是数字
            else {
                sum = st.top();
                st.pop();
                c=tokens[n][0];
                if (c == '+') {
                    st.top() += sum;
                } else if (c == '-') {
                    st.top() -= sum;
                } else if (c == '*') {
                    st.top() *= sum;
                } else {
                    st.top() /= sum;
                }
            }
            n++;
        }
        return st.top();
    }
};
优化写法
cpp 复制代码
class Solution {
public:
    int evalRPN(vector<string>& tokens) {
        stack<int> st;
        // function作为map的映射可调⽤对象的类型
        map<string, function<int(int, int)>> opFuncMap = {
            {"+", [](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; }}};
        for (auto& str : tokens) {
            if (opFuncMap.count(str)) // 操作符
            {
                int right = st.top();
                st.pop();
                int left = st.top();
                st.pop();
                int ret = opFuncMap[str](left, right);
                st.push(ret);
            } else {
                st.push(stoi(str));
            }
        }
        return st.top();
    }
};

bind 完整详解

头文件:<functional>

一、作用

bind 是参数绑定工具,接收一个可调用对象(函数、函数指针、lambda、成员函数、仿函数),生成一个新的可调用包装器:

  1. 预先固定一部分参数;
  2. 调整参数顺序;
  3. 适配类成员函数(补齐隐藏的this参数);
  4. 配合 function 存储回调。

二、基础语法

复制代码
auto new_callable = std::bind(可调用对象, 绑定参数列表...);

绑定参数分两类:

  1. 普通常量:直接写值,永久固定;
  2. 占位符 placeholders::_1 / _2 / _3...:代表调用新函数时传入的第 1/2/3 个实参。

三、基础示例:固定部分参数

复制代码
#include <iostream>
#include <functional>
using namespace std;

int add(int a, int b) {
    return a + b;
}

int main() {
    // 固定第一个参数 a=10,只留第二个参数动态传入
    auto add10 = bind(add, 10, placeholders::_1);
    cout << add10(5);   // 10+5=15
    cout << add10(20);  // 10+20=30

    // 两个参数全部固定,调用时无需传参
    auto add20 = bind(add, 10, 10);
    cout << add20();    // 20
    return 0;
}

四、调整参数顺序

通过调换占位符顺序,改变入参映射关系:

复制代码
int sub(int a, int b) {
    return a - b;
}

int main() {
    // 原:sub(a,b) = a-b
    // bind后:调用时传(x,y),映射为 sub(y, x)
    auto reverse_sub = bind(sub, placeholders::_2, placeholders::_1);
    cout << reverse_sub(10, 3); // sub(3,10) = -7
    return 0;
}

五、传参绑定规则:值绑定 / 引用绑定

1. 默认:值拷贝绑定

bind 默认会拷贝传入的变量,后续外部修改不影响绑定内部:

复制代码
void print(int x) { cout << x; }

int main() {
    int num = 10;
    auto f = bind(print, num);
    num = 100;
    f(); // 输出10,不是100
    return 0;
}

2. std::ref /std::cref 引用绑定

想要绑定变量引用,需要用 ref()(可读写引用)、cref()(const 只读引用):

复制代码
void modify(int& x) { x *= 2; }

int main() {
    int num = 10;
    auto f = bind(modify, ref(num));
    f();
    cout << num; // 20,成功修改外部变量
    return 0;
}

六、绑定类成员函数(高频难点)

成员函数自带隐藏第一个参数 this,bind 必须手动传入对象指针 / 对象。

示例 1:普通成员函数

复制代码
struct Person {
    int base = 5;
    int calc(int x) {
        return base + x;
    }
};

int main() {
    Person p;
    // &Person::calc:成员函数地址
    // &p:this指针,固定绑定对象p
    // _1:调用时传入calc的形参x
    auto func = bind(&Person::calc, &p, placeholders::_1);
    cout << func(3); // 5+3=8
    return 0;
}

示例 2:绑定对象副本 vs 对象引用

  • bind(..., p, ...):拷贝一份 p,修改不影响原对象
  • bind(..., &p, ...):绑定原对象指针,修改同步

示例 3:静态成员函数

静态成员无this,绑定方式和普通全局函数一致:

复制代码
struct Test {
    static int mul(int a, int b) { return a*b; }
};
auto f = bind(Test::mul, 2, placeholders::_1);
cout << f(5); // 10

示例 4:绑定成员变量

可以直接绑定类成员变量,生成取值可调用对象:

复制代码
struct P { int age = 18; };
P p;
auto get_age = bind(&P::age, &p);
cout << get_age(); // 18

七、配合 function 使用

bind 返回的匿名对象可以存入function,统一管理回调:

复制代码
#include <vector>

int add(int a,int b) {return a+b;}

int main() {
    vector<function<int(int)>> calls;
    // 批量生成固定左值的加法器
    calls.push_back(bind(add, 1, placeholders::_1));
    calls.push_back(bind(add, 10, placeholders::_1));

    for(auto& f : calls) {
        cout << f(5) << " "; // 6 15
    }
    return 0;
}

八、bind 搭配 lambda 的对比

  1. 固定少量参数:bind 代码更短;
  2. 复杂逻辑、多参数调整、泛型:lambda 可读性更强;
  3. 类成员函数封装回调:bind 无可替代。

等价对比:

复制代码
int add(int a,int b) {return a+b;}
// bind写法
auto add10 = bind(add, 10, placeholders::_1);
// lambda等价写法
auto add10_lam = [](int b){ return add(10, b); };

九、占位符说明

复制代码
placeholders::_1 // 调用时第1个实参
placeholders::_2 // 调用时第2个实参
placeholders::_3 // 调用时第3个实参
...

占位符只关心调用顺序,bind 内部可以重复使用同一个占位符:

复制代码
auto double_add = bind(add, placeholders::_1, placeholders::_1);
cout << double_add(6); // 6+6=12

十、常见坑点

  1. 成员函数忘记传对象指针,运行崩溃;
  2. 需要引用传参时不用 std::ref,拿到的永远是拷贝快照;
  3. 占位符序号写错,参数错位;
  4. bind 存储可调用对象会产生少量内存开销,极致性能场景优先 lambda;
  5. 绑定临时对象,后续调用时对象已销毁,野引用崩溃。

十一、完整综合示例

cpp 复制代码
#include <iostream>
#include <functional>
using namespace std;

void show(string prefix, int num) {
    cout << prefix << num << endl;
}

struct Student {
    string name = "张三";
    void say(int score) {
        cout << name << "分数:" << score << endl;
    }
};

int main() {
    // 1. 固定第一个参数
    auto show_info = bind(show, "成绩:", placeholders::_1);
    show_info(95);

    // 2. 成员函数绑定
    Student s;
    auto stu_say = bind(&Student::say, &s, placeholders::_1);
    stu_say(88);

    // 3. 引用绑定外部变量
    int x = 100;
    auto ref_print = bind([](int& v){v++;}, ref(x));
    ref_print();
    cout << x; // 101
    return 0;
}