news 2026/10/23 3:04:00

c++11进阶学习 第二部分

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
c++11进阶学习 第二部分

1:lambda表达式

lambda表达式定义了一个匿名函数,并且可以捕获一定范围内的变量。lambda表达式的语法形式简单归纳如下:

[capture](params) opt -> ret {body;};

其中capture是捕获列表,params是参数列表,opt是函数选项,ret是返回值类型,body是函数体。

捕获列表[]:捕获一定范围内的变量

参数列表():和普通函数的参数列表一样,如果没有参数参数列表可以省略不写。

auto f = [](){return 1;} // 没有参数, 参数列表为空 auto f = []{return 1;} // 没有参数, 参数列表省略不写

opt 选项,不需要可以省略

  • mutable: 可以修改按值传递进来的拷贝(注意是能修改拷贝,而不是值本身)
  • exception: 指定函数抛出的异常,如抛出整数类型的异常,可以使用throw();

返回值类型:在C++11中,lambda表达式的返回值是通过返回值后置语法来定义的。

函数体:函数的实现,这部分不能省略,但函数体可以为空。

1.2:捕获列表
lambda表达式的捕获列表可以捕获一定范围内的变量,具体使用方式如下:

  • [] - 不捕捉任何变量
  • [&] - 捕获外部作用域中所有变量, 并作为引用在函数体内使用 (按引用捕获)
  • [=] - 捕获外部作用域中所有变量, 并作为副本在函数体内使用 (按值捕获)

拷贝的副本在匿名函数体内部是只读的

  • [=, &foo] - 按值捕获外部作用域中所有变量, 并按照引用捕获外部变量 foo
  • [bar] - 按值捕获 bar 变量, 同时不捕获其他变量
  • [&bar] - 按引用捕获 bar 变量, 同时不捕获其他变量
  • [this] - 捕获当前类中的this指针

让lambda表达式拥有和当前类成员函数同样的访问权限
如果已经使用了 & 或者 =, 默认添加此选项

#include <iostream> #include <functional> using namespace std; class Test { public: void output(int x, int y) { auto x1 = [] {return m_number; }; // error auto x2 = [=] {return m_number + x + y; }; // ok auto x3 = [&] {return m_number + x + y; }; // ok auto x4 = [this] {return m_number; }; // ok auto x5 = [this] {return m_number + x + y; }; // error auto x6 = [this, x, y] {return m_number + x + y; }; // ok auto x7 = [this] {return m_number++; }; // ok } int m_number = 100; };
  • x1:错误,没有捕获外部变量,不能使用类成员 m_number
  • x2:正确,以值拷贝的方式捕获所有外部变量
  • x3:正确,以引用的方式捕获所有外部变量
  • x4:正确,捕获this指针,可访问对象内部成员
  • x5:错误,捕获this指针,可访问类内部成员,没有捕获到变量x,y,因此不能访问。
  • x6:正确,捕获this指针,x,y
  • x7:正确,捕获this指针,并且可以修改对象内部变量的值
int main(void) { int a = 10, b = 20; auto f1 = [] {return a; }; // error auto f2 = [&] {return a++; }; // ok auto f3 = [=] {return a; }; // ok auto f4 = [=] {return a++; }; // error auto f5 = [a] {return a + b; }; // error auto f6 = [a, &b] {return a + (b++); }; // ok auto f7 = [=, &b] {return a + (b++); }; // ok return 0; }
  • f1:错误,没有捕获外部变量,因此无法访问变量 a
  • f2:正确,使用引用的方式捕获外部变量,可读写
  • f3:正确,使用值拷贝的方式捕获外部变量,可读
  • f4:错误,使用值拷贝的方式捕获外部变量,可读不能写
  • f5:错误,使用拷贝的方式捕获了外部变量a,没有捕获外部变量b,因此无法访问变量b
  • f6:正确,使用拷贝的方式捕获了外部变量a,只读,使用引用的方式捕获外部变量b,可读写
  • f7:正确,使用值拷贝的方式捕获所有外部变量以及b的引用,b可读写,其他只读

1.3:返回值

很多时候,lambda表达式的返回值是非常明显的,在C++11中允许省略lambda表达式的返回值。

// 完整的lambda表达式定义 auto f = [](int a) -> int { return a+10; }; // 忽略返回值的lambda表达式定义 auto f = [](int a) { return a+10; };

1.4:函数本质

使用lambda表达式捕获列表捕获外部变量,如果希望去修改按值捕获的外部变量,那么应该如何处理呢?

这就需要使用mutable选项,被mutable修改是lambda表达式就算没有参数也要写明参数列表,并且可以去掉按值捕获的外部变量的只读(const)属性。

最后再剖析一下为什么通过值拷贝的方式捕获的外部变量是只读的:

  • lambda表达式的类型在C++11中会被看做是一个带operator()的类,即仿函数。
  • 按照C++标准,lambda表达式的operator()默认是const的,一个const成员函数是无法修改成员变量值的。

mutable选项的作用就在于取消operator()的const属性。

1.5:Lambda 表达式会产生一个可调用对象

Lambda 表达式会产生一个可调用对象。直接通过 名字() 调用。

它可以转换成函数指针,也可以交给std::function包装,或者交给std::bind绑定参数。

捕获列表没有捕获任何外部变量,作为函数指针使用。

如果捕获列表存在参数,则只能作为仿函数使用,用可调用包装器,绑定器

#include <iostream> #include <functional> using namespace std; void func(int x, int y) { // 1. Lambda 转换成普通函数指针 using Ptr = void (*)(int); Ptr p1 = [](int value) { cout << "函数指针:" << value << endl; }; p1(11); // 2. 使用 std::function 包装 Lambda // [x] 表示保存外部变量 x 的一份副本 function<void(int)> f2 = [x](int value) { cout << "包装器:" << x + value << endl; }; f2(11); // 3. 使用 std::bind 绑定 Lambda 的参数 auto add = [](int first, int second) { return first + second; }; add(10,20); //返回30 // 固定第一个参数为 x,第二个参数留到调用时传入 auto f3 = bind(add, x, placeholders::_1); cout << "绑定器:" << f3(y) << endl; } int main() { func(10, 20); return 0; }

第一点:

Ptr p1 = [](int value) { cout << "函数指针:" << value << endl; };

这里的 Lambda:

  • []:不捕获外部变量。
  • (int value):接收一个整数。
  • 函数体没有返回结果,所以返回类型是void。

它的调用形式与Ptr匹配,因此可以转换成函数指针,保存到p1。

第二点:当执行func(10, 20);

f2就可以捕获到x是10,随后调用f2(11);

计算:10 + 11=21.

2:右值引用

左值引用写成T&,右值引用写成T&&。

它们都是给对象起别名,主要区别在于:能绑定什么表达式。

C++11 中右值可以分为两种:一个是将亡值,另一个则是纯右值:

纯右值:非引用返回的临时变量、运算表达式产生的临时变量、原始字面量和 lambda 表达式等
将亡值:与右值引用相关的表达式,比如,T&&类型函数的返回值、 std::move 的返回值等。

引用类型示例绑定a绑定10能通过引用修改对象
普通左值引用int&可以不可以可以
常量左值引用const int&可以可以不可以
右值引用int&&不可以直接绑定可以可以

2.1:基础理解

#include <iostream> #include <utility> // std::move using namespace std; int main() { int a = 10; const int ca = 20; // ==================================== // 1. 普通左值引用:int& // ==================================== int& left = a; // 可以:绑定普通变量 a left = 11; // 可以:通过引用修改 a cout << "a = " << a << endl; // 11 // int& bad1 = 10; // 不可以:普通左值引用不能绑定整数右值 // int& bad2 = a + 1; // 不可以:a + 1 是计算得到的临时结果 // int& bad3 = ca; // 不可以:不能通过普通 int& 引用 const int 对象 // ==================================== // 2. 常量左值引用:const int& // ==================================== const int& c1 = a; // 可以:绑定普通变量 const int& c2 = 100; // 可以:绑定整数右值 const int& c3 = a + 1; // 可以:绑定临时计算结果 const int& c4 = ca; // 可以:绑定 const 变量 cout << "c1 = " << c1 << ", c2 = " << c2 << ", c3 = " << c3 << ", c4 = " << c4 << endl; // c1 = 30; // 不可以:不能通过 const 引用修改对象 // c2 = 40; // 不可以:不能通过 const 引用修改对象 // ==================================== // 3. 右值引用:int&& // ==================================== int&& right1 = 200; // 可以:绑定整数右值 int&& right2 = a + 1; // 可以:绑定临时计算结果 right1 = 300; // 可以:右值引用也能用于修改对象 cout << "right1 = " << right1 << ", right2 = " << right2 << endl; // int&& bad4 = a; // 不可以:表达式 a 是左值 // int&& bad5 = ca; // 不可以:表达式 ca 也是左值 // ==================================== // 4. 使用 std::move // ==================================== int&& right3 = std::move(a); // 可以 // 注意:这里没有创建 a 的副本 // right3 引用的仍然是原来的 a right3 = 50; cout << "a = " << a << endl; // 50 // int& bad6 = std::move(a); // 不可以:普通左值引用不能绑定 std::move(a) 产生的右值表达式 // ==================================== // 5. 有名字的右值引用变量,使用时是左值 // ==================================== int& left2 = right1; // 可以:表达式 right1 是左值 left2 = 400; // 修改 right1 引用的对象 cout << "right1 = " << right1 << endl; // 400 // int&& bad7 = right1; // 不可以:虽然 right1 的类型是 int&&, // 但直接使用名字 right1 时,它是左值表达式 return 0; }
被绑定的表达式int&const int&int&&
普通变量a✅✅❌
常量变量ca❌✅❌
整数值10❌✅✅
计算结果a + 1❌✅✅
std::move(a)❌✅✅
已命名的右值引用变量right1✅✅❌

2.2:关于右值引用的使用,参考代码如下:

#include <iostream> using namespace std; int&& value = 520; class Test { public: Test() { cout << "construct: my name is jerry" << endl; } Test(const Test& a) { cout << "copy construct: my name is tom" << endl; } }; Test getObj() { return Test(); } int main() { int a1; int &&a2 = a1; // 错误:普通左值引用不能绑定临时对象 Test& t = getObj(); // 正确:右值引用可以绑定临时对象 Test && t = getObj(); const Test& t = getObj(); return 0; }
  • 在上面的例子中int&& value = 520;里面520是纯右值,value是对字面量520这个右值的引用。
  • 在int &&a2 = a1;中a1虽然写在了=右边,但是它仍然是一个左值,使用左值初始化一个右值引用类型是不合法的。
  • 在Test& t = getObj()这句代码中语法是错误的,右值不能给普通的左值引用赋值。
  • 在Test && t = getObj();中getObj()返回的临时对象被称之为将亡值,t是这个将亡值的右值引用。
  • const Test& t = getObj()这句代码的语法是正确的,常量左值引用是一个万能引用类型,它可以接受左值、右值、常量左值和常量右值。

注意:

C++ 中的“值”不只包括数字,类的对象也可以按值返回。

Test getObj() { return Test(); }

它按值返回Test对象,因此getObj()这个调用表达式是右值。

先看第一行:

Test& t = getObj(); // 错误

两边分别是:

Test& t getObj() 普通左值引用 ← 右值

普通左值引用不能绑定这个右值,所以编译错误。

如果右边换成已有对象的名字,就可以:

Test obj; Test& t = obj; // 正确:表达式 obj 是左值
Test&& t = getObj(); // 正确

两边分别是:

Test&& t getObj() 右值引用 ← 右值

右值引用允许绑定这个右值,所以正确。

2.3:性能优化

在C++中在进行对象赋值操作的时候,很多情况下会发生对象之间的深拷贝,如果堆内存很大,这个拷贝的代价也就非常大,在某些情况下,如果想要避免对象的深拷贝,就可以使用右值引用进行性能的优化。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/23 3:03:40

边缘AI进入建筑后,WiFi Halow技术帮集成商搞定带宽、距离和功耗

边缘AI正在改变数据在建筑里的流动方式&#xff0c;让建筑变得越来越智能。视频监控能实时认出人、车辆和异常活动&#xff1b;门禁系统能把身份验证、数据分析和自动反应结合起来&#xff1b;空调和供暖系统会根据房间里有没有人来调节能耗&#xff1b;环境传感器能在空气质量…

作者头像 李华
网站建设 2026/10/23 3:03:24

地址输入提示接口实战:从敲第一个字到选中地点的完整接入

摘要&#xff1a;电商下单页、外卖收货地址、物流寄件表单——凡是需要用户填写地址的场景&#xff0c;都少不了"输入提示"&#xff08;也叫地址联想、地址自动补全&#xff09;。本文以丰图地址输入提示API为例&#xff0c;给出一个可直接改造的前端接入方案&#x…

作者头像 李华
网站建设 2026/10/23 3:03:19

前端通用组件开发全指南|从 0 到 1 打造稳定可复用的业务组件

适合 Vue / React 开发者&#xff0c;面向 B 端后台系统&#xff08;跨境、ERP、SaaS 场景&#xff09;&#xff0c;兼顾设计原则、避坑经验&#xff0c;附思维导图要点&#xff0c;适合技术博客发布很多团队都会踩同一个坑&#xff1a;业务写着写着&#xff0c;到处是重复弹窗…

作者头像 李华
网站建设 2026/10/23 3:03:01

Go reflect 包动态调用与方法反射 —Call、MakeFunc 与性能

1. Value.Call 全景&#xff1a;一次反射调用的生命周期:Call/CallSlice 都落到 Value.call&#xff08;value.go:385&#xff0c;约 200 行&#xff09;。调用一个函数&#xff0c;编译器生成的代码要做的事 reflect 全要手工做一遍&#xff1a;Value.Call(in []Value) []Valu…

作者头像 李华
网站建设 2026/10/23 3:02:58

CDS Table Function 上层 WHERE 到底能不能下推到数据库表

在 SAP 项目里使用 CDS Table Function 时,有一个性能问题特别容易靠经验判断。 假设底层数据库表有几百万甚至几千万条数据,我们通过 CDS Table Function 包了一层 AMDP SQLScript,业务程序再从这个 Table Function 上查询数据,并加上类似下面这样的过滤条件。 WHERE re…

作者头像 李华