当前位置: 首页 > news >正文

C++11(下) 入门三部曲终章(基础篇):夯实语法,解锁基础编程能力 - 详解

在这里插入图片描述

文章目录

  • 前言
  • 1️⃣一、emplace接口(重点)
    • 1.1push/insert为何低效?
    • 1.2emplace的核心原理
  • 2️⃣二、lambda
    • 2.1lambda表达式语法
    • 2.2捕捉列表
  • 3️⃣三、包装器
    • 3.1function
    • 3.2bind
  • 总结


前言

上一篇博客重点讲解了可变参数模板,主要在于可变二字。以往我们写函数一般都会把参数写死(也就是说传过去的参数的个数被固定了),这样反倒不灵活。有了可变参数模板,这就使得我们可以传任意个参数过去,增加了我们代码的灵活性。接下来我们看看可变参数模板是怎么在我们STL接口中运用起来的。


提示:以下是本篇文章正文内容,下面案例可供参考

1️⃣一、emplace接口(重点)

可以这么说,可变参数是emplace的语法基石,而emplace就是可变参数模板的经典落地实战

1.1push/insert为何低效?

  • 在emplace接口诞生之前,我们向容器插入数据通常都是调用push_back/push_front等接口进行操作,这样的操作固然没有问题,但却无形之中增加了不必要的临时对象的创建和销毁

  • 即使push/insert重载了右值引用的版本push_back(T&& x)(右值引用可以抢夺资源,调用移动构造,减少拷贝),看似解决了性能问题,但也逃不了内存的开销

我们先分C++11前与后关于插入接口的效率对比
①C++11前,只有左值引用的版本push(T& x)

std::vector<std::string> vec;// 传入字符串字面量,编译器先构造临时std::string对象vec.push_back("hello emplace");
  • 先构造一个临时的string对象"hello emplace",第一步开销
  • 再调用拷贝构造,分配内存,把这个临时对象拷贝进容器中,第二步开销
  • 再销毁原先创建的临时对象

这个过程是显而易见的构造-拷贝-销毁的过程,几乎是冗余操作,尤其是到了自定义类型的对象,如string等,开销更大

②C++11后,重载右值版本进行移动构造push(T&& x)

std::vector<std::string> vec;// C++11后,匹配push_back(std::string&&),调用移动构造vec.push_back("hello emplace");
  • 以"hello emplace"为参数,构造一个临时对象(分配内存,拷贝内容)
  • 调用std::string的移动构造函数,将临时对象的资源所有权转移到容器的内存空间中(仅修改指针指向,无堆内存分配、无内容拷贝,开销极小,但并非为 0)
  • 函数调用结束后,销毁临时的 std::string 对象(此时临时对象已无资源,销毁仅做简单清理 —— 开销极小,但并非为 0)

核心痛点:传统接口的设计缺陷 —— 只能接收 “已构造的对象”

  • 无论是 C++11 前的拷贝构造,还是 C++11 后的移动构造,传统 push/insert 接口的底层设计缺陷从未改变:它们的参数是已构造完成的 T 类型对象(左值或右值),而非构造 T 对象所需的参数
  • 这就决定了:只要通过传统接口插入 “未提前构造的对象”,就必须先在外部构造一个临时对象(左值 / 右值),再将这个对象传递给接口—— 临时对象的创建是无法通过移动语义规避的,这是传统接口的先天限制。

1.2emplace的核心原理

基于上面的分析,那有没有能够跳出临时对象的构建与销毁,直接在目标容器里构造对象的方法呢?答案就是emplace接口。

emplace 系列接口的设计思路很简单:不接受已构造的对象,而是接受构造对象所需的所有参数,然后在容器的目标内存位置,直接调用对象的构造函数完成构造。

而实现这一个思路,必须得解决两个问题

  • ①我们传的参数要对应匹配该容器的构造函数,所以就注定了我们传的参数个数、类型是随机的(因为要方便我们调用合适的构造函数)——可变参数模板就解决了这个问题,通过Args&&... agrs参数包,可以让emplace接受任何类型、个数的参数
  • ②要保证传递给构造函数的参数类型不被破坏(左值还是左值,右值还是右值),避免不必要的拷贝 ——完美转发(std::forward<Args>(args)...)解决了这个问题,实现参数的无损传递。(上文提过,右值引用的变量具有左值属性)
// 1. 极简待构造的类
class Product {
public:
string name;
int price;
// 带参构造(仅保留核心)
Product(string n,int p = 1)
:name(n)
,price(p)
{ }
};
// 2. 极致简洁的容器类(仅含emplace核心)
class SimpleContainer {
public:
// 核心emplace接口:可变参数模板接收构造参数
template <typename... Args>void emplace(Args&&... args) {// 直接用参数构造对象(emplace本质:传构造参数,而非现成对象)obj = Product(forward<Args>(args)...);}// 方便验证结果(非核心,仅辅助)Product get_obj() { return obj; }private:Product obj; // 存储的对象(简化为直接成员,无内存分配)};// 测试:仅调用emplaceint main() {SimpleContainer container;// emplace直接传构造参数(不用先造Product对象)container.emplace("耳机", 199);// 验证Product p = container.get_obj();cout << "emplace构造的对象:" << p.name << " | 价格:" << p.price << endl;return 0;}
  • emplace 函数体里的 “传值” 不是 “额外开销”,而是构造容器内对象的「必要步骤」;
  • push_back 的核心问题是 “多一次外部临时对象的构造 / 销毁”,这是「冗余开销」;
  • 对普通开发者来说,不用纠结 “单次传值的微小开销”,只要记住:传构造参数用 emplace,传现成对象用 push_back,这就够了。

2️⃣二、lambda

2.1lambda表达式语法

int main()
{
//lambda表达式
auto add = [](int x, int y)->int {return x + y; };
std::cout << add(1, 2) << std::endl;
auto func = [] {std::cout << "hello czh" << std::endl; };
func();
int a = 0, b = 1;
auto swap = [](int& x, int& y)
{
int tmp = x;
x = y;
y = tmp;
};
swap(a, b);
std::cout << a << ":" << b << std::endl;
return 0;
}

2.2捕捉列表

  • lambda表达式中默认只能用lambda函数体和参数中的变量,如果想用外层作用域的变量就要进行捕捉
  • 第一种捕捉方式是在捕捉列表中显示的传值捕捉和传引用捕捉,捕捉的多个变量用逗号分隔。[x,y,&z]表示x和y通过值捕捉,z通过传引用捕捉
  • 第二种就是隐式捕捉,[=],[&],这样我们在函数体里面使用什么变量,就会捕捉什么变量,所有的变量都是对应的值(=)捕捉和引用(&)捕捉
  • 第三种捕捉方式就是混合捕捉。[=,&x,&y]就代表我使用的变量除了x和y是传引用捕捉之外,其余都是值捕捉。当使用混合捕捉时,第一个元素必须是=或&
  • lambda的值捕捉的元素默认是被const修饰的
int x = 0;
auto func1 = []()
{
x++;
};
int main()
{
//lambda表达式
auto add = [](int x, int y)->int {return x + y; };
std::cout << add(1, 2) << std::endl;
auto func = [] {std::cout << "hello czh" << std::endl; };
func();
int a = 0, b = 1;
auto swap = [](int& x, int& y)
{
int tmp = x;
x = y;
y = tmp;
};
swap(a, b);
std::cout << a << ":" << b << std::endl;
int a = 0, b = 1, c = 2, d = 3;
auto func1 = [a, &b]//a值捕捉,b引用捕捉
{
b++;
int ret = a + b;
return ret;
};
std::cout << func1() << std::endl;
auto func2 = [=]//使用到的变量均是值捕捉
{
int ret = a + b + c;
return ret;
};
std::cout << func2() << std::endl;
auto func3 = [&]//使用到的变量均是引用捕捉
{
a++;
b++;
};
func3();
cout << a << b;
return 0;
}

3️⃣三、包装器

3.1function

template<class T>class function;template<class Ref,class... Args>class function<(Ref(Args...))>;
  • std::function是一个类模板,也是一个包装器。std::function的实力对象可以包装存储其他的可以调用对象,包括函数指针,仿函数,lambda,bind表达式等,存储的可调用对象被称为std::function的目标。若std::function不含目标,则称它为空。调用空会抛出异常
  • 函数指针、仿函数、lambda等可调用对象的类型各不相同,std::function的优势就是统一类型,对他们都可以进行包装,这样在很多地方就方便声明可调用对象的类型
int f(int a, int b)
{
return a + b;
}
struct func
{
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)//static修饰成员变量属于类本身,只能访问静态成员
{
return a + b;
}
double plusd(double a, double b)
{
return (a + b) * _n;
}
private:
int _n;
};
int main()
{
function<int(int, int)> f1 = f;func fc;function<int(int, int)> f2 = fc;function<int(int, int)> f3 = [](int a, int b) {return a + b; };cout << f1(1, 2) << endl;cout << f2(3, 4) << endl;cout << f3(5, 6) << endl;function<int(int, int)> f4 = &Plus::plusi;cout << f4(1, 1) << endl;function<double(Plus*, double, double)> f5 = &Plus::plusd;//要通过&取到函数的指针function<double(Plus, double, double)> f6 = &Plus::plusd;//可以传对象,也可以传对象的地址,也可以传右值function<double(Plus&&, double, double)> f7 = &Plus::plusd;Plus pd;cout << f5(&pd, 1.1, 1.1) << endl;cout << f6(pd, 1.1, 1.1) << endl;cout << f7(move(pd), 1.1, 1.1) << endl;cout<<f7(Plus(), 1.1, 1.1) << endl;return 0;}

3.2bind

  • bind是一个函数模板,它也是一个可调用对象的包装器,可以把他看作一个函数适配器,对接受的fn可调用对象进行处理后返回一个可调用对象。bind可以用来调整参数个数和参数顺序。bind也在functional这个头文件
  • 调用bind的一般形式:auto newcallable = bind(callbable,arg_list);其中newcallable本身也是一个可调用对象,arg_list是一个逗号分割的参数列表,对应给定的callable的参数。当我们调用newcallable,newcallable会调用callable,并传给它arg_list中的参数
  • arg_list中的参数可能包含形如_n的名字,其中n是⼀个整数,这些参数是占位符,表⽰
    newCallable的参数,它们占据了传递给newCallable的参数的位置。数值n表⽰⽣成的可调⽤对象中参数的位置:_1为newCallable的第⼀个参数,_2为第⼆个参数,以此类推。_1/_2/_3…这些占位符放到placeholders的⼀个命名空间中
#include <iostream>#include <functional>using namespace std;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;// 分别绑死第1、2、3个参数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;// 计算复利的 lambdaauto func1 = [](double rate, double money, int year)->double {double ret = money;for (int i = 0; i < year; i++){ret += ret * rate;}return ret - money;};// 绑死一些参数,实现出支持不同年华利率,不同金额和不同年份计算出复利的结算利息function<double(double)> func3_1_5 = bind(func1, 0.015, _1, 3);function<double(double)> func5_1_5 = bind(func1, 0.015, _1, 5);function<double(double)> func10_2_5 = bind(func1, 0.025, _1, 10);function<double(double)> func20_3_5 = bind(func1, 0.035, _1, 30);cout << func3_1_5(1000000) << endl;cout << func5_1_5(1000000) << endl;cout << func10_2_5(1000000) << endl;cout << func20_3_5(1000000) << endl;return 0;}

总结

C++11到这里就先告一段落了,希望大家能够从这篇文章真真实实的学到东西,不懂可以私信博主交流噢

http://www.jsqmd.com/news/452776/

相关文章:

  • 2026-03-09 闲话
  • 波段末段的心态
  • 模型加载权重的时候发生了什么
  • 2026年矿山煤矿电力电缆生产厂家推荐:中低压、低压、中压、变频等厂家名单 - 品牌2026
  • 2026年天津消防电缆生产厂家推荐(含耐火、阻燃、阻燃B1级等全品类) - 品牌2026
  • 346. Java IO API - 操作文件和目录
  • 0309晨间日记
  • 超越简单分类:构建面向真实世界的多层文本分类系统
  • 基于贾子军事战略理论体系的美国军事 AI 系统深度研究报告
  • Harmonyos应用示例32. 有余数的除法:分草莓动画
  • Harmonyos应用示例33. 数量间的乘除关系:倍数关系可视化
  • Harmonyos应用示例34. 万以内的数的认识:数位拨珠器
  • Harmonyos应用示例35. 万以内的数的认识:数字排序游戏
  • Harmonyos应用示例36. 万以内的加法和减法:竖式计算器
  • Harmonyos应用示例37. 万以内的加法和减法:智慧购物
  • Harmonyos应用示例38. 数学连环画:故事拼图
  • Harmonyos应用示例39. 有余数的除法:余数与除数关系
  • 拒绝全表扫描灾难:用 SSCAN 安全遍历 Redis 亿级 Set 集合
  • 2603,禁止微软更新工具
  • 2603C++,简单实现协程
  • 如何快速搭建简单SpringBoot项目网页
  • 如何使用 Python 连接 MySQL 数据库?
  • 如何在docker中的mysql容器内执行命令与执行SQL文件
  • Flutter 组件 postgres_crdt 的适配 鸿蒙Harmony 实战 - 驾驭分布式无冲突复制数据类型、实现鸿蒙端高性能离线对等同步架构方案
  • 基于Java+SSM+Django大学生成果登记系统(源码+LW+调试文档+讲解等)/大学生成果登记系统使用教程/大学生成果登记平台/大学生成果管理系统/大学生成果申报系统/大学生成果展示系统
  • 基于Java+SSM+Django健身中心管理系统(源码+LW+调试文档+讲解等)/健身中心管理软件/健身房管理系统/健身中心会员管理系统/健身房会员软件/健身房管理软件/健身俱乐部管理系统
  • Flutter 组件 t_stats 的适配 鸿蒙Harmony 实战 - 驾驭高性能统计学运算、实现鸿蒙端海量数据实时态势感知与工业级描述性统计方案
  • 在SpringBoot项目中集成MongoDB
  • 地址转坐标:利用高德API进行批量地理编码
  • 基于Java+SSM+Flask网页商城系统(源码+LW+调试文档+讲解等)/网页商城系统使用教程/网页商城系统开发/网页商城系统模板/网页商城系统源码/网页商城系统搭建/网页商城系统优势