C++日新月异的未来代码:C++11(下)

C++日新月异的未来代码:C++11(下)

文章目录

接上篇,继续学习C++11的常用新特性

1.lambda表达式

1.1 引入

lambda 表达式是一种匿名函数对象,允许在代码中直接定义和使用小型的函数,无需额外定义函数或函数对象类,这么讲,感觉还是太理论了,下面将通过特定场景介绍其使用:

structGoods{ string _name;// 名字double _price;// 价格int _evaluate;// 评价Goods(constchar* str,double price,int evaluate):_name(str),_price(price),_evaluate(evaluate){}};structComparePriceLess{booloperator()(const Goods& gl,const Goods& gr){return gl._price < gr._price;}};structComparePriceGreater{booloperator()(const Goods& gl,const Goods& gr){return gl._price > gr._price;}};intmain(){ vector<Goods> v ={{"苹果",2.1,5},{"香蕉",3,4},{"橙子",2.2,3},{"菠萝",1.5,4}};sort(v.begin(), v.end(),ComparePriceLess());sort(v.begin(), v.end(),ComparePriceGreater());}

日常生活中,一件商品包含多个特性,若想针对某个特性进行排序,那么就需要使用算法库里的 sort,设置自定义类型的比较方式,那么仿函数就是个很好的方式

随着 C++ 语法的发展,人们开始觉得上面的写法太复杂了,每次为了实现一个 algorithm 算法,都要重新去写一个类,如果每次比较的逻辑不一样,还要去实现多个类,特别是相同类的命名,这些都给编程者带来了极大的不便。因此,在 C++11 语法中出现了 lambda 表达式

1.2 语法

lambda表达式书写格式:

[capture-list] (parameters) mutable -> return-type { statement }

lambda表达式各部分说明:

  • [capture-list] : 捕捉列表,该列表总是出现在 lambda 函数的开始位置,编译器根据 [] 来判断接下来的代码是否为 lambda 函数,捕捉列表能够捕捉上下文中的变量供 lambda 函数使用
  • (parameters):参数列表,与普通函数的参数列表一致,如果不需要参数传递,则可以连同 () 一起省略
  • mutable:默认情况下,lambda 函数总是一个 const 函数,mutable 可以取消其常量性。使用该修饰符时,参数列表不可省略(即使参数为空)
  • ->returntype:返回值类型,用追踪返回类型形式声明函数的返回值类型,没有返回值时此部分可省略。返回值类型明确情况下,也可省略,由编译器对返回类型进行推导
  • {statement}:函数体,在该函数体内,除了可以使用其参数外,还可以使用所有捕获到的变量

🔥值得注意的是:lambda 函数定义中,参数列表返回值类型都是可选忽略部分,而捕捉列表函数体可以为。因此 C++11 中最简单的 lambda 函数为:[]{};lambda 函数不能做任何事情

1.3 使用

intmain(){ vector<Goods> v ={{"苹果",2.1,5},{"香蕉",3,4},{"橙子",2.2,3},{"菠萝",1.5,4}};sort(v.begin(), v.end(),[](const Goods& g1,const Goods& g2)->bool{return g1._price < g2._price;});sort(v.begin(), v.end(),[](const Goods& g1,const Goods& g2)->bool{return g1._price > g2._price;});sort(v.begin(), v.end(),[](const Goods& g1,const Goods& g2)->bool{return g1._evaluate < g2._evaluate;});sort(v.begin(), v.end(),[](const Goods& g1,const Goods& g2)->bool{return g1._evaluate > g2._evaluate;});return0;}

因此,lambda 表达式可以这样套用在 sort 里,比仿函数确实方便且可观性更高了,可以看出 lambda 表达式实际是一个匿名函数(无名函数),该函数无法直接调用,如果想要直接调用,可借助 auto 将其赋值给一个变量

auto ret = [ ](const Goods& g1, const Goods& g2) {return g1._evaluate < g2._evaluate; }

对于捕捉列表 [],平常一般使用的不多,但是某些情况还是要使用的,需要了解其用法

捕捉列表描述了上下文中那些数据可以被lambda使用,以及使用的方式传值还是传引用:

  • [x]:表示值传递方式捕捉变量 x
  • [=]:表示值传递方式捕获所有父作用域中的变量(包括 this )
  • [&x]:表示引用传递捕捉变量 x
  • [&]:表示引用传递捕捉所有父作用域中的变量(包括 this )
  • [this]:表示值传递方式捕捉当前的 this 指针

🔥值得注意的是:

  1. 父作用域指包含 lambda 函数的语句块
  2. lambda 默认以值传递的方式进行,传值捕捉的变量是不可修改的
intmain(){int x =10;auto func =[x]()mutable{ x =20; cout << x << std::endl;};func(); cout << x << endl;return0;}

使用 mutable 关键字就可以修改了,但是这种修改只是对 lambda 内部的副本进行修改,不会影响到原始的变量。在 main 函数中再次输出 x 时,其值仍为 10

  1. 语法上捕捉列表可由多个捕捉项组成,并以逗号分割,比如:[=, &a, &b],以引用传递的方式捕捉变量 ab,值传递方式捕捉其他所有变量;[&,a, this],值传递方式捕捉变量 athis,引用方式捕捉其他变量
  2. 捕捉列表不允许变量重复传递,否则就会导致编译错误
  3. 在块作用域以外的 lambda 函数捕捉列表必须为空,在全局作用域中,并没有局部变量可供 lambda 函数捕获
  4. 在块作用域中的 lambda 函数仅能捕捉父作用域中局部变量,捕捉任何非此作用域或者非局部变量都会导致编译报错
  5. lambda 表达式之间不能相互赋值,即使看起来类型相同,但是可以拷贝构造(每个 lambda 表达式都有其独特的、未命名的类型。即使两个 lambda 表达式的参数列表和返回类型相同,它们的类型也是不同的)

1.4 本质

在这里插入图片描述

转到反汇编可以发现,其实 lambda 的本质就是被包装的仿函数,编译器会自动生成一个类,在该类中重载了 operator()

2.类的新增语法

2.1 移动构造、移动赋值运算符

C++11 新增了两个:移动构造函数和移动赋值运算符重载,在上一篇有进行详细的说明

传送门:C++日新月异的未来代码:C++11(上)
  • 如果你没有自己实现移动构造函数,且没有实现析构函数 、拷贝构造、拷贝赋值重载中的任意一个。那么编译器会自动生成一个默认移动构造。默认生成的移动构造函数,对于内置类型成员会执行逐成员按字节拷贝,自定义类型成员,则需要看这个成员是否实现移动构造,如果实现了就调用移动构造,没有实现就调用拷贝构造
  • 如果你没有自己实现移动赋值重载函数,且没有实现析构函数 、拷贝构造、拷贝赋值重载中的任意一个,那么编译器会自动生成一个默认移动赋值。默认生成的移动构造函数,对于内置类型成员会执行逐成员按字节拷贝,自定义类型成员,则需要看这个成员是否实现移动赋值,如果实现了就调用移动赋值,没有实现就调用拷贝赋值。(默认移动赋值跟上面移动构造完全类似)
  • 如果你提供了移动构造或者移动赋值,编译器不会自动提供拷贝构造和拷贝赋值

2.2 default

classPerson{public:Person(constchar* name ="",int age =0):_name(name),_age(age){}Person(const Person& p):_name(p._name),_age(p._age){}Person(Person && p)=default;private: bit::string _name;int _age;};intmain(){ Person s1; Person s2 = s1; Person s3 = std::move(s1);return0;}

default 是强制生成默认函数的关键字,我们提供了拷贝构造,就不会生成移动构造了,那么我们可以使用 default 关键字显示指定移动构造生成

2.3 delete

classPerson{public:Person(constchar* name ="",int age =0):_name(name),_age(age){}Person(const Person& p)=delete;private: bit::string _name;int _age;};intmain(){ Person s1; Person s2 = s1; Person s3 = std::move(s1);return0;}

delete 是禁止生成默认函数的关键字,当类显式删除了拷贝构造函数时,编译器不会自动生成移动构造函数(即使没有显式删除移动构造函数),代码中没有显式定义移动构造函数,且隐式移动构造函数被禁用,因此无法完成移动初始化

🔥值得注意的是:

移动构造函数的核心目的是高效转移资源所有权(如动态内存、文件句柄等),而拷贝构造函数的目的是创建资源的独立副本。如果一个类禁用了拷贝构造函数,通常意味着:

  • 资源不可复制: 例如独占式资源,拷贝会导致资源管理混乱
  • 防止意外拷贝: 开发者希望禁止对象的复制操作,强制使用移动语义

此时,如果编译器仍然自动生成移动构造函数,可能会破坏这种设计意图

3.可变参数模板

3.1 概念

在这里插入图片描述

其实可变模板参数早在C语言就已经有了,后面三个点点点就是可变模板参数,比如: printf("%d,%d,%d", x, y, z),后面的参数个数是可以自己控制有多少个的,这就是一种可变模板参数

template<class...Args>voidShowList(Args... args){}intmain(){ShowList();ShowList(1);ShowList(1,2.2);ShowList(1,2,"xxxxx");return0;}

回到实际定义,Args 是一个模板参数包,args 是一个函数形参参数包,声明一个参数包Args... args,这个参数包中可以包含 0 到任意个模板参数

3.2 获取个数

template<class...Args>voidShowList(Args... args){ cout <<sizeof...(args)<< endl;}intmain(){ShowList();ShowList(1);ShowList(1,2.2);ShowList(1,2,"xxxxx");return0;}

这个用法也是很奇葩。。。

3.3 展开参数包

不知道当初设计怎么想的,这里想要 for 循环遍历展开是不可行的,编译器不支持,所以这里的展开方法做了解即可

3.3.1 递归函数

// 递归终止函数template<classT>voidShowList(const T& t){ cout << t << endl;}// 展开函数template<classT,class...Args>voidShowList(T value, Args... args){ cout << value <<" ";ShowList(args...);}intmain(){ShowList(1);ShowList(1,'A');ShowList(1,'A',string("sort"));return0;}

模式匹配: 展开函数 ShowList(T value, Args... args) 匹配 至少一个参数 的情况,每次取出第一个参数 value,剩余参数构成新的参数包 args... ,终止函数 ShowList(const T& t) 匹配仅有一个参数 的情况,结束递归

参数包展开:args... 在递归调用时会被解包,每次减少一个参数,直到参数包为空,
关键语句 ShowList(args...) 会触发模板的递归实例化,直到匹配终止函数

输出顺序: 先打印当前参数 value,再递归处理剩余参数,确保参数按传入顺序输出

3.3.2 逗号表达式

template<classT>voidPrintArg(T t){ cout << t <<" ";}//展开函数template<class...Args>voidShowList(Args... args){int arr[]={(PrintArg(args),0)...}; cout << endl;}intmain(){ShowList(1);ShowList(1,'A');ShowList(1,'A',string("sort"));return0;}

(PrintArg(args), 0)... 会将参数包 args... 展开为多个表达式,打印对应的值,然后返回 0(用于填充数组)

// 原始代码int arr[]={(PrintArg(args),0)...};// 展开后等价于int arr[]={(PrintArg(1),0),(PrintArg('A'),0),(PrintArg("sort"),0)};

PrintArg 的返回值是 void,无法初始化 int 数组,即使 PrintArg 返回参数类型(如 T),参数包可能包含不同类型(如 int, char),仍会导致类型不匹配

每个元素必须是 int 类型,因此需要用 0 作为统一的返回值,保证初始化的数组元素都为相同类型

3.4 emplace系列的接口

intmain(){ list< pair<int,char>> mylist; mylist.emplace_back(10,'a'); mylist.emplace_back(20,'b'); mylist.emplace_back(make_pair(30,'c')); mylist.push_back(make_pair(40,'d')); mylist.push_back({50,'e'});for(auto e : mylist) cout << e.first <<":"<< e.second << endl;return0;}

emplace_back 的作用和 push_back 相同,但是 mylist.emplace_back(20, 'b') 这种格式的写法更方便一些

其实我们会发现其实差别也不大,emplace_back 是直接构造了,push_back 是先构造,再移动构造,移动构造的消耗很小,其实没啥影响

3.5 可变参数模板的实际应用

classDate{public:Date(int year =1,int month =1,int day =1):_year(year),_month(month),_day(day){ cout <<"Date构造"<< endl;}Date(const Date& d):_year(d._year),_month(d._month),_day(d._day){ cout <<"Date拷贝构造"<< endl;}private:int _year;int _month;int _day;};template<class...Args> Date*Create(Args... args){ Date* ret =newDate(args...);return ret;}intmain(){ list<Date> lt; Date d(2023,9,27);// 只能传日期类对象 lt.push_back(d);// 既能传日期类对象// 又能传日期类对象的参数包// 参数包,一路往下传,直接去构造或者拷贝构造节点中日期类对象 lt.emplace_back(d); lt.emplace_back(2023,9,27);return0;}

push_back 只能传日期类对象,emplace_back 既能传日期类对象,又能传日期类对象的参数包。参数包,一路往下传,直接去构造或者拷贝构造节点中日期类对象

4.包装器

4.1 function

template<classF,classT> T useF(F f, T x){staticint count =0; cout <<"count:"<<++count << endl; cout <<"count:"<<&count << endl;returnf(x);}doublef(double i){return i /2;}structFunctor{doubleoperator()(double d){return d /3;}};intmain(){// 函数名 cout <<useF(f,11.11)<< endl;// 函数对象 cout <<useF(Functor(),11.11)<< endl;// lamber表达式 cout <<useF([](double d)->double{return d /4;},11.11)<< endl;return0;}

我们知道函数指针,仿函数,lambda表达式,这三种都是函数对象的创建方式,同时调用这三个方式实例化模板,useF函数模板实例化了三份,明明都是相同的内容,实在是没有必要,会导致模板的效率低下

那么这种时候就需要使用头文件 <functional> 中的 functionfunction 包装器也叫作适配器。C++中的 function 本质是一个类模板,也是一个包装器

// 类模板原型如下template<classT> function;// undefinedtemplate<classRet,class... Args>classfunction<Ret(Args...)>;

模板参数说明:

  • Ret : 被调用函数的返回类型
  • Args…:被调用函数的形参

下面直接修改以上代码,来展示 function 的使用效果:

template<classF,classT> T useF(F f, T x){staticint count =0; cout <<"count:"<<++count << endl; cout <<"count:"<<&count << endl;returnf(x);}doublef(double i){return i /2;}structFunctor{doubleoperator()(double d){return d /3;}};intmain(){// 函数名 function<double(double)> func1 = f; cout <<useF(func1,11.11)<< endl;// 函数对象 function<double(double)> func2 =Functor(); cout <<useF(func2,11.11)<< endl;// lamber表达式 function<double(double)> func3 =[](double d)->double{return d /4;}; cout <<useF(func3,11.11)<< endl;return0;}

三种可调用对象被统一为同一类型:包装类,模板只实例化一次,静态变量共享(即这个 count 只有一份),

4.2 bind

bind 函数定义在头文件中,是一个函数模板,它就像一个函数包装器(适配器),接受一个可调用对象,生成一个新的可调用对象来“适应”原对象的参数列表。

bind 可以理解为一个接收包装类的适配器,上面的例子都是直接将函数给到包装类,那么 bind 就是将特定的函数和参数绑定到包装类,通过例子解析会更容易理解:

intPlus(int a,int b){return a + b;}classSub{public:intsub(int a,int b){return a - b;}};intmain(){//表示绑定函数plus 参数分别由调用 func1 的第一,二个参数指定 std::function<int(int,int)> func1 = std::bind(Plus, placeholders::_1, placeholders::_2);//auto func1 = std::bind(Plus, placeholders::_1, placeholders::_2);//func2的类型为 function<void(int, int, int)> 与func1类型一样//表示绑定函数 plus 的第一,二为: 1, 2auto func2 = std::bind(Plus,1,2); cout <<func1(1,2)<< endl; cout <<func2()<< endl; Sub s;// 绑定成员函数 std::function<int(int,int)> func3 = std::bind(&Sub::sub, s, placeholders::_1, placeholders::_2);// 参数调换顺序 std::function<int(int,int)> func4 = std::bind(&Sub::sub, s, placeholders::_2, placeholders::_1); cout <<func3(1,2)<< endl; cout <<func4(1,2)<< endl;return0;}

bind 的第一个参数传的是函数,后面的是一系列要传的参数,_1 为第一个参数,_2 为第二个参数,以此类推,参数既可以是待定的,也可以是具体的值,placeholders 属于 std 命名空间,若展开了就不用写

在这里插入图片描述

🔥值得注意的是:

  • 若函数是非静态成员函数,必须在 Sub::sub 前加上 &,因为非静态成员函数依赖对象,必须显式调用其地址,普通函数指针直接指向代码地址,而成员函数指针需要同时包含类的类型信息和函数地址,因此还需要将对象 s 传过去
  • 若函数是静态成员函数,和普通函数一样都是全局函数,就不需要加 & 和传对象

希望读者们多多三连支持

小编会继续更新

你们的鼓励就是我前进的动力!

请添加图片描述

Read more

最强开源多模态大模型它来啦——一文详解Qwen3.5核心特性

最强开源多模态大模型它来啦——一文详解Qwen3.5核心特性

前言 各位小伙伴新年好!新的一年祝大家龙马精神、阖家幸福、身体健康、事业进步!2025 年 DeepSeek 发布的 DeepSeek-R1 模型震惊全球,此后国内各大厂商充分发挥“能征善战”的拼劲,纷纷选择重大节日推出新品。今年除夕夜,阿里 Qwen 团队再次放出大招——Qwen3.5 模型正式开源,为国产大模型阵营再添一员猛将。 Qwen3.5 是目前全球最强的原生多模态开源大模型,不仅支持图片和视频的多模态输入,在对话、推理、编程、Agent 构建等方面也样样精通。其综合能力已达到 GPT-5.2、Gemini 3.0 Pro 的平均水平,推理能力尤为突出。例如那道曾让无数模型“翻车”的逻辑题——“50 米距离该走路还是开车去洗车”,Qwen3.5 也能轻松作答。

By Ne0inhk
手动部署开源OpenClaw汉化中文版过程中常见问题排查手册

手动部署开源OpenClaw汉化中文版过程中常见问题排查手册

部署开源OpenClaw汉化中文版过程中常见问题排查手册 遇到问题?按错误消息搜索本页,或按场景分类查找解决方案。 🎬 摘要 😤 部署 OpenClaw 汉化版又双叒叕报错了? 别慌!这份实战排查手册专为「踩坑」而生。 无论是 Docker 镜像拉取失败、容器启动闪退,还是 Dashboard 死活连不上、远程访问 502 报错——我们按错误场景分类整理,支持按错误关键词秒搜定位。每个解决方案均来自真实部署案例,附带紧急修复通道和根因分析,让你从「报错一脸懵」到「秒级排障」。 🔧 适用版本:OpenClaw 汉化中文版(Docker 部署) 📌 更新策略:与主仓库每小时同步,排查方案持续迭代 ⚡ 建议收藏:部署前通读「零、紧急修复」,关键时刻能救命! 目录 * 零、紧急修复 ⚠️ * 一、安装问题 * 二、启动问题

By Ne0inhk

TRAE、VSCode上进行git管理

最近在学习Node.js,但是对TRAE/VSCode的git操作有点不太会,因此记录一下,如有不对,请指出。 我这里使用的是TRAE演示,VSCode应该差不多。 首先是从github,或者gitee上将项目clone下来。看图操作 此时会在页面最上方显示一个弹窗,输入你的项目地址 选择你的项目存放路径 稍等片刻后,项目就clone到你本地了。 使用TRAE/VSCode打开项目。 一般项目会有很多分支,比如主分支,上线版本分支,需求分支,开发分支,咱们举个例子: 主分支:main(作为所有分支的主分支,会合并所有没有bug的代码) 版本分支:release_projectName_versionCode_date(一般用来归档项目版本节点,如果后期某个版本有线上Bug,就基于这个分支修改) 需求分支:feature_projectName_versionCode_main_date(一般有新需求了,就会新建这个分支) 开发分支:feature_projectName_versionCode_userName_

By Ne0inhk