首页
学习
活动
专区
圈层
工具
发布
社区首页 >专栏 >C++11之lambda及包装器

C++11之lambda及包装器

作者头像
用户11991900
发布2026-01-15 12:11:40
发布2026-01-15 12:11:40
170
举报
在这里插入图片描述
在这里插入图片描述

一.Lambda

lambda 表达式本质是⼀个匿名函数对象,跟普通函数不同的是他可以定义在函数内部。lambda 表达式语法使⽤层⽽⾔没有类型,所以我们⼀般是⽤auto或者模板参数定义的对象去接收 lambda 对象。

1.1Lambda 表达式

Lambda 表达式是一种匿名函数对象,它可以在代码中直接定义并使用。其基本语法结构如下:

代码语言:javascript
复制
[capture](parameters)->return-type { body }
  • capture:捕获列表,用于捕获 Lambda 表达式外部的变量。捕获方式有值捕获([x],将变量 x 的值复制到 Lambda 中)、引用捕获([&x],通过引用捕获变量 x)等多种方式。
  • parameters:参数列表,与普通函数的参数列表类似,用于接收调用时传入的参数。
  • return-type:返回类型,可以省略,如果 Lambda 表达式只有一条语句且该语句有返回值,则返回类型由该语句的返回类型推导得出。
  • body:函数体,是 Lambda 表达式的核心部分,用于实现具体的功能逻辑。

1.2捕捉列表

捕获列表的基本概念

捕获列表位于 Lambda 表达式的开头,用方括号[]表示。它允许 Lambda 表达式捕获外部作用域中的变量,从而在 Lambda 表达式的函数体内使用这些变量。捕获列表的语法如下: [capture1, capture2, …] 捕获列表中的每个捕获项可以是一个变量名、一个变量名前加&符号(表示通过引用捕获),或者是一个变量名前加*符号(表示捕获变量的地址)。捕获项之间用逗号分隔。

捕获方式
(一)值捕获

值捕获是将外部变量的值复制到 Lambda 表达式中。这种方式下,Lambda 表达式内部的变量是外部变量的一个副本,对内部变量的修改不会影响外部变量。例如:

代码语言:javascript
复制
int x = 10;
auto lambda = [x]() {
    x = 20; // 修改的是 Lambda 内部的 x 副本
};
lambda();
std::cout << x << std::endl; // 输出 10,外部 x 的值未改变

值捕获适用于那些不需要修改外部变量,或者希望在 Lambda 表达式中独立操作变量的情况。

(二)引用捕获

引用捕获是通过引用的方式捕获外部变量。这种方式下,Lambda 表达式内部的变量是外部变量的引用,对内部变量的修改会直接影响外部变量。例如:

代码语言:javascript
复制
int x = 10;
auto lambda = [&x]() {
    x = 20; // 直接修改外部变量 x
};
lambda();
std::cout << x << std::endl; // 输出 20,外部 x 的值被修改

引用捕获适用于那些需要在 Lambda 表达式中修改外部变量的情况,但需要注意的是,如果外部变量的生命周期短于 Lambda 表达式的生命周期,可能会导致悬空引用,引发运行时错误。

(三)默认捕获
代码语言:javascript
复制
在捕捉列表中混合使⽤隐式捕捉和显⽰捕捉。[=,&x]表⽰其他变量隐式值捕捉,
x引⽤捕捉;[&,x,y]表⽰其他变量引⽤捕捉,x和y值捕捉。
当使⽤混合捕捉时,第⼀个元素必须是&或=,并且&混合捕捉时
后⾯的捕捉变量必须是值捕捉,
同理=混合捕捉时,后⾯的捕捉变量必须是引⽤捕捉。

除了显式指定捕获的变量外,Lambda 表达式还支持默认捕获。默认捕获有两种方式: [=]:表示默认按值捕获所有外部变量。 [&]:表示默认按引用捕获所有外部变量。 默认捕获可以简化捕获列表的书写,但需要谨慎使用,因为过度捕获可能会导致不必要的性能开销或潜在的错误。例如:

代码语言:javascript
复制
int x = 10;
int y = 20;
auto lambda = [=]() {
    x = 30; // 修改的是 Lambda 内部的 x 副本
    y = 40; // 修改的是 Lambda 内部的 y 副本
};
lambda();
std::cout << x << " " << y << std::endl; // 输出 10 20,外部 x 和 y 的值未改变

在默认捕获的情况下,如果需要对某个变量采用不同的捕获方式,可以通过显式捕获来覆盖默认捕获。例如:

代码语言:javascript
复制
int x = 10;
int y = 20;
auto lambda = [=, &y]() {
    x = 30; // 修改的是 Lambda 内部的 x 副本
    y = 40; // 直接修改外部变量 y
};
lambda();
std::cout << x << " " << y << std::endl; // 输出 10 40,外部 x 的值未改变,y 的值被修改

在这个例子中,x 按值捕获,y 按引用捕获。

(四)捕获其他特性
  1. lambda 表达式如果在函数局部域中,他可以捕捉 lambda 位置之前定义的变量,不能捕捉静态局部变量和全局变量,静态局部变量和全局变量也不需要捕捉, lambda 表达式中可以直接使⽤。这也意味着 lambda 表达式如果定义在全局位置,捕捉列表必须为空。
代码语言:javascript
复制
// 局部的静态和全局变量不能捕捉,也不需要捕捉 
 static int m = 0;
 auto func6 = []
 {
 int ret = x + m;
 return ret;
 };
  1. 默认情况下, lambda 捕捉列表是被const修饰的,也就是说传值捕捉的过来的对象不能修改,mutable加在参数列表的后⾯可以取消其常量性,也就说使⽤该修饰符后,传值捕捉的对象就可以修改了,但是修改还是形参对象,不会影响实参。使⽤该修饰符后,参数列表不可省略(即使参数为空)。

i

代码语言:javascript
复制
// 传值捕捉本质是⼀种拷⻉,并且被const修饰了 
 // mutable相当于去掉const属性,可以修改了 
 // 但是修改了不会影响外⾯被捕捉的值,因为是⼀种拷⻉ 
 auto func7 = [=]()mutable

 {
 a++;
 b++;
 c++;
 d++;
 return a + b + c + d;
 };
 cout << func7() << endl;
(五)在类中捕获私有

在类中,若想要使用私有成员变量时,直接捕获私有成员是报错的;这时需要通过捕获this指针来使用私有成员变量。

1.3lambda的应⽤

• 在学习 lambda 表达式之前,我们的使⽤的可调⽤对象只有函数指针和仿函数对象,函数指针的类型定义起来⽐较⿇烦,仿函数要定义⼀个类,相对会⽐较⿇烦。使⽤ lambda 去定义可调⽤对象,既简单⼜⽅便。

• lambda 在很多其他地⽅⽤起来也很好⽤。⽐如线程中定义线程的执⾏函数逻辑,智能指针中定制删除器等

代码语言:javascript
复制
struct Goods

{
 string _name; // 名字 
 double _price; // 价格 
 int _evaluate; // 评价 
 // ...

 Goods(const char* str, double price, int evaluate)
 :_name(str)
 , _price(price)
 , _evaluate(evaluate)
 {}
};

struct ComparePriceLess

{
 bool operator()(const Goods& gl, const Goods& gr)
 {
 return gl._price < gr._price;
 }
};

struct ComparePriceGreater

{
 bool operator()(const Goods& gl, const Goods& gr)
 {
  return gl._price > gr._price;
 }
};

int main()
{
 vector<Goods> v = { { "苹果", 2.1, 5 }, { "⾹蕉", 3, 4 }, { "橙⼦", 2.2, 3 
}, { "菠萝", 1.5, 4 } };
 // 类似这样的场景,我们实现仿函数对象或者函数指针⽀持商品中 
 // 不同项的⽐较,相对还是⽐较⿇烦的,那么这⾥lambda就很好⽤了 
 sort(v.begin(), v.end(), ComparePriceLess());
 sort(v.begin(), v.end(), ComparePriceGreater());
 sort(v.begin(), v.end(), [](const Goods& g1, const Goods& g2) {
 return g1._price < g2._price;
 });
 sort(v.begin(), v.end(), [](const Goods& g1, const Goods& g2) {
 return g1._price > g2._price; 
 });
 sort(v.begin(), v.end(), [](const Goods& g1, const Goods& g2) {
 return g1._evaluate < g2._evaluate;
 });
 sort(v.begin(), v.end(), [](const Goods& g1, const Goods& g2) {
 return g1._evaluate > g2._evaluate;
 });
 return 0;
}

1.4lambda的原理

• lambda 的原理和范围for很像,编译后从汇编指令层的⻆度看,压根就没有 lambda 和范围for这样的东西。范围for底层是迭代器,⽽lambda底层是仿函数对象,也就说我们写了⼀个lambda 以后,编译器会⽣成⼀个对应的仿函数的类。

• 仿函数的类名是编译按⼀定规则⽣成的,保证不同的 lambda ⽣成的类名不同,lambda参数/返回类型/函数体就是仿函数operator()的参数/返回类型/函数体, lambda 的捕捉列表本质是⽣成的仿函数类的成员变量,也就是说捕捉列表的变量都是 lambda 类构造函数的实参,当然隐式捕捉,编译器要看使⽤哪些就传那些对象。

二.包装器

2.1function

std::function是C++标准模板库(STL)中定义在<functional>头文件中的一个类模板。它能够存储、复制,并调用任何可调用对象,如普通函数、Lambda表达式、函数对象、以及其他函数封装器(如std::bind)的结果。这使得std::function成为一种通用的函数封装方式,极大地增强了函数的通用性和可扩展性。

代码语言:javascript
复制
std::function 是⼀个类模板,也是⼀个包装器。
 std::function 的实例对象可以包装存储其他的可以调⽤对象,
 包括函数指针、仿函数、 lambda 、 bind 表达式等,
 存储的可调⽤对象被称为std::function 的⽬标。
 若std::function 不含⽬标,则称它为空。
 调⽤空std::function 的⽬标导致抛出std::bad_function_call异常。
std::function的声明与定义

std::function的声明形式如下:

代码语言:javascript
复制
template <class> class function; // 未定义
template <class Ret, class... Args>
class function<Ret(Args...)>; // 定义

这里Ret表示函数返回值的类型,Args...表示函数参数的类型列表。例如,std::function<void()>表示一个没有参数且没有返回值的函数;std::function<int(int, int)>表示一个接受两个int参数并返回一个int值的函数。

函数指针、仿函数、 lambda 等可调⽤对象的类型各不相同, std::function 的优势就是统⼀类型,对他们都可以进⾏包装,这样在很多地⽅就⽅便声明可调⽤对象的类型,下⾯的第⼆个代码样例展⽰了 std::function 作为map的参数,实现字符串和可调⽤对象的映射表功能。特别要注意的是在调用普通成员函数

代码语言:javascript
复制
#include<functional>

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

struct Functor

{

public:
 int operator() (int a, int b)
 {
 return a + b;
 }
};

class Plus

{

public:
 Plus(int n = 10)
 :_n(n)
 {}
 static int plusi(int a, int b)
 {
 return a + b;
 }
 double plusd(double a, double b)
 {
 return (a + b) * _n;
 }

private:
 int _n;
};

int main()
{
 // 包装各种可调⽤对象 
 function<int(int, int)> f1 = f;
 function<int(int, int)> f2 = Functor();
 function<int(int, int)> f3 = [](int a, int b) {return a + b; };
 cout << f1(1, 1) << endl;
 cout << f2(1, 1) << endl;
 cout << f3(1, 1) << endl;
 // 包装静态成员函数 
 // 成员函数要指定类域并且前⾯加&才能获取地址 
 function<int(int, int)> f4 = &Plus::plusi;
 cout << f4(1, 1) << endl;
 // 包装普通成员函数 
 // 普通成员函数还有⼀个隐含的this指针参数,所以绑定时传对象或者对象的指针过去都可以 
 function<double(Plus*, double, double)> f5 = &Plus::plusd;
 Plus pd;
 cout << f5(&pd, 1.1, 1.1) << endl;
 function<double(Plus, double, double)> f6 = &Plus::plusd;
 cout << f6(pd, 1.1, 1.1) << endl;
 cout << f6(pd, 1.1, 1.1) << endl;
 function<double(Plus&&, double, double)> f7 = &Plus::plusd;
 cout << f7(move(pd), 1.1, 1.1) << endl;
 cout << f7(Plus(), 1.1, 1.1) << endl;
 return 0;
}

2.2bind

bind的声明与定义

bind 是⼀个函数模板,它也是⼀个可调⽤对象的包装器,可以把他看做⼀个函数适配器,对接收的fn可调⽤对象进⾏处理后返回⼀个可调⽤对象。 bind 可以⽤来调整参数个数和参数顺序。bind 也在这个头⽂件中。

  1. 调⽤bind的⼀般形式: auto newCallable = bind(callable,arg_list); 其中 newCallable本⾝是⼀个可调⽤对象,arg_list是⼀个逗号分隔的参数列表,对应给定的callable的参数。当我们调⽤newCallable时,newCallable会调⽤callable,并传给它arg_list中的参数。
  2. arg_list中的参数可能包含形如_n的名字,其中n是⼀个整数,这些参数是占位符,表⽰newCallable的参数,它们占据了传递给newCallable的参数的位置。数值n表⽰⽣成的可调⽤对象中参数的位置:_1为newCallable的第⼀个参数,_2为第⼆个参数,以此类推。_1/_2/_3…这些占位符放到placeholders的⼀个命名空间中。
代码语言:javascript
复制
using placeholders::_1;

using placeholders::_2;

using placeholders::_3;
代码语言:javascript
复制
simple(1)

template <class Fn, class... Args>
 /* unspecified */ bind (Fn&& fn, Args&&... args);
 
with return type (2)

template <class Ret, class Fn, class... Args>
 /* unspecified */ bind (Fn&& fn, Args&&... args);
std::bind 的原理

std::bind 的原理基于模板和函数对象。它通过模板参数推导,将函数和参数封装成一个函数对象。这个函数对象重载了 operator(),当调用这个函数对象时,它会按照绑定时的参数和函数进行调用。 在实现上,std::bind 使用了完美转发和模板元编程技术。它能够处理各种类型的参数,包括左值、右值、引用等,并且能够正确地将参数转发到目标函数中。

std::bind的使用
代码语言:javascript
复制
#include<functional>

using placeholders::_1;

using placeholders::_2;

using placeholders::_3;

int Sub(int a, int b)
{
 return (a - b) * 10;
}

int SubX(int a, int b, int c)
{
 return (a - b - c) * 10;
}

class Plus

{

public:
 static int plusi(int a, int b)
 {
 return a + b;
 }
 double plusd(double a, double b)
 {
 return a + b;
 }
};

int main()
{
 auto sub1 = bind(Sub, _1, _2);
 cout << sub1(10, 5) << endl;
 // bind 本质返回的⼀个仿函数对象 
 // 调整参数顺序(不常⽤) 
 // _1代表第⼀个实参 
 // _2代表第⼆个实参 
 // ...

 auto sub2 = bind(Sub, _2, _1);
 cout << sub2(10, 5) << endl;
 // 调整参数个数 (常⽤) 
 auto sub3 = bind(Sub, 100, _1);
 cout << sub3(5) << endl;
 auto sub4 = bind(Sub, _1, 100);
 cout << sub4(5) << endl;
 // 分别绑死第123个参数 
 auto sub5 = bind(SubX, 100, _1, _2);
 cout << sub5(5, 1) << endl;
 auto sub6 = bind(SubX, _1, 100, _2);
 cout << sub6(5, 1) << endl;
 auto sub7 = bind(SubX, _1, _2, 100);
 cout << sub7(5, 1) << endl;
 // 成员函数对象进⾏绑死,就不需要每次都传递了 
 function<double(Plus&&, double, double)> f6 = &Plus::plusd;
 Plus pd;
 cout << f6(move(pd), 1.1, 1.1) << endl;
 cout << f6(Plus(), 1.1, 1.1) << endl;
 // bind⼀般⽤于,绑死⼀些固定参数 
 function<double(double, double)> f7 = bind(&Plus::plusd, Plus(), _1, _2);
 cout << f7(1.1, 1.1) << endl;
 }
本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2026-01-13,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 一.Lambda
    • 1.1Lambda 表达式
    • 1.2捕捉列表
      • 捕获列表的基本概念
      • 捕获方式
    • 1.3lambda的应⽤
    • 1.4lambda的原理
  • 二.包装器
    • 2.1function
      • std::function的声明与定义
    • 2.2bind
      • bind的声明与定义
      • std::bind 的原理
      • std::bind的使用
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档