【C++】C++11的新特性 --- lambda表达式 ,新的类功能,模块的可变参数 , emplace系列接口

在这里插入图片描述

如果你停止,就是谷底!
如果你还在继续,就是上坡!
这是我听过关于人生低谷最好的阐述。
-- 刘同

C++11的新特性

  • 1 lambda表达式
    • 1.1 基本用法
    • 1.2 细谈参数列表与捕捉列表
  • 2 新的类功能
    • 2.1 移动构造与移动赋值
    • 2.2 default和delete
  • 3 模块的可变参数
  • 4 emplace系列接口
  • Thanks♪(・ω・)ノ谢谢阅读!!!
  • 下一篇文章见!!!

1 lambda表达式

1.1 基本用法

C语言解决自定义排序问题时,会使用函数指针;C++我们解决排序问题时,一般都会使用仿函数,通过自定义类来实现自定义比较大小。如果涉及的比较排序很多,就要写出很多类,比较繁琐。通
今天的lambda表达式也是一种解决办法。我们来看:这是我们传统的仿函数写法

struct Goods
{string _name; // 名字double _price; // 价格int _evaluate; // 评价Goods(const char* str, double price, int evaluate):_name(str), _price(price), _evaluate(evaluate){}
};class GoodsPriceLess
{
public:bool operator()(Goods& a , Goods& b){return a._price > b._price;}
};class GoodsPriceGreater
{
public:bool operator()(Goods& a, Goods& b){return a._price < b._price;}
};int main()
{vector<Goods> v = { { "苹果", 2.1, 5 }, { "香蕉", 3, 4 }, { "橙子", 2.2,3 }, { "菠萝", 1.5, 4 } };sort(v.begin(), v.end(), GoodsPriceLess());for (auto g : v){cout << g._name << " " << g._price << " " << g._evaluate << endl;}sort(v.begin(), v.end(), GoodsPriceGreater());for (auto g : v){cout << g._name << " " << g._price << " " << g._evaluate << endl;}return 0;
}

通过lambda表达式可以简单化:


int main()
{vector<Goods> v = { { "苹果", 2.1, 5 }, { "香蕉", 3, 4 }, { "橙子", 2.2,3 }, { "菠萝", 1.5, 4 } };sort(v.begin(), v.end(), [](const Goods& a, const Goods& b) { return a._price > b._price; });for (auto g : v){cout << g._name << " " << g._price << " " << g._evaluate << endl;}cout << endl;sort(v.begin(), v.end(), [](const Goods& a, const Goods& b) { return a._price < b._price; });for (auto g : v){cout << g._name << " " << g._price << " " << g._evaluate << endl;}return 0;
}

lambda表达式是一种匿名参数,格式是:
在这里插入图片描述

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

可以使用auto 来承接这个匿名函数

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

就可以调用func1来做到像函数一样的效果!
我们可以看一下这个匿名函数的类型:
在这里插入图片描述
即使是一模一样的,类型也是不同的!

lambda的本质是仿函数,类型是lambda+一个随机字符串UUID,也就是一个仿函数的名称,编译器在编译时,会生成对应仿函数的名称。lambda表达式就类似范围for,只是表现不同,底层本质还是仿函数!

1.2 细谈参数列表与捕捉列表

我们来看一个程序:

int main()
{int a = 1 ; int b = 2;auto swap1 = [](int& x, int& y){int tmp = x;x = y;y = tmp;};swap1(a, b);return 0;
}

这个程序可以帮我们完成交换a、b的值。注意这里使用auto可以帮助我们解决不知道函数类型的问题。同样我们也可以不通过参数列表来达到更换的作用:

	//捕捉a b 对象给lambda表达式用 //注意加上mutable才能对捕捉对象进行修改(一般不需要)auto swap2 = [a, b]() mutable{int tmp = a;a = b;b = tmp;};swap2();

通过调试,我们发现swap2函数并没有对ab进行交换,只是在函数作用域下进行了交换,因为[a, b]是传值捕捉,类似传值参数,捕捉的是一个拷贝,当然是无法进行修改的!我们可以使用引用传参来达到修改的作用:

	//传引用捕捉auto swap3 = [&a, &b]() mutable{int tmp = a;a = b;b = tmp;};swap3();

这样可以直接调用swap3做到交换的要求。但是这里回旋镖就回来了:[&a, &b]你说这是地址呢?还是引用呢?
设计引用时为了尽可能减少使用运算符,就使用了&!所以我们要注意[&a, &b]是引用方式捕捉!如果想要捕捉地址,就需要简介捕捉:

int *pa = &a , *pb = &b;
auto swap3 = [pa, pb]()

这样就可以进行捕捉了!

来总结一下捕捉的种类:

  1. [var]:表示值传递方式捕捉变量var
  2. [=]:表示值传递方式捕获所有父作用域中的变量(包括this)
  3. [&var]:表示引用传递捕捉变量var
  4. [&]:表示引用传递捕捉所有父作用域中的变量(包括this)
  5. [this]:表示值传递方式捕捉当前的this指针
  6. 混合捕捉:[ 多种普通类型的捕捉 ]
//全部捕捉
int a = 1, b = 2, c = 4, d = 5;
auto swap4 = [=]() mutable{return a + b * c - d;};
int ret = swap4();

好的,lambda表达式就是这些内容,一定要运用到实际中去,在一些需要仿函数的地方可以进行lambda表达式的优化!

2 新的类功能

2.1 移动构造与移动赋值

在原本的C++类中,有六个默认成员函数:

  1. 构造函数
  2. 析构函数
  3. 拷贝构造函数
  4. 拷贝赋值重载
  5. 取地址重载
  6. const 取地址重载

最后重要的是前4个,后两个用处不大。默认成员函数就是我们不写编译器会生成一个默认的!
注意只有写了任意一个构造函数(构造,拷贝构造,拷贝赋值)就不生成默认构造

在C++11之后,加入了右值引用和移动语义,就产生了新的类默认成员函数—移动构造和移动赋值。这些针对的是需要深拷贝的自定义类型(string , vector,list)通过与将亡值的数据进行交换做到效率提高!

针对移动构造函数和移动赋值运算符重载有一些需要注意的点如下:

如果你没有自己实现移动构造函数且没有实现析构函数 、拷贝构造、拷贝赋值重载中的任意一个。那么编译器会自动生成一个默认移动构造。析构函数 、拷贝构造、拷贝赋值通常是绑定在一起的(需要深拷贝),实现一个就都要写,否则一个不写!默认生成的移动构造函数,对于内置类型成员会执行逐成员按字节拷贝,自定义类型成员,则需要看这个成员是否实现移动构造,如果实现了就调用移动构造,没有实现就调用拷贝构造

class Person
{
public:Person(const char* name = "", int age = 0):_name(name), _age(age){}private:bit::string _name;int _age;
};int main()
{Person s1; //string会进行一次构造Person s2 = s1;//Person s3 = std::move(s1);Person s4;s4 = std::move(s2);return 0;
}

在这里插入图片描述

Person s2 = s1;因为没有写拷贝构造,所以会默认生成一个,内置类型逐字节拷贝,自定义类型如果有拷贝构造就调用拷贝构造否则进行浅拷贝。
Person s3 = std::move(s1);move后是右值,会进行移动构造!s1和s3交换数据s4 = std::move(s2);因为s4已经构造过,进行移动赋值!s2和s4交换数据
如果我们在person内部加入析构函数 、拷贝构造、拷贝赋值任意一个,就不会产生默认构造了
在这里插入图片描述

如果你没有自己实现移动赋值重载函数,且没有实现析构函数 、拷贝构造、拷贝赋值重载中的任意一个,那么编译器会自动生成一个默认移动赋值。默认生成的移动构造函数,对于内置类型成员会执行逐成员按字节拷贝,自定义类型成员,则需要看这个成员是否实现移动赋值,如果实现了就调用移动赋值,没有实现就调用拷贝赋值。(默认移动赋值跟上面移动构造完全类似)

如果你提供了移动构造或者移动赋值,编译器不会自动提供拷贝构造和拷贝赋值。

2.2 default和delete

default关键字可以强制生成!如果我们写了析构函数 、拷贝构造、拷贝赋值重载中几个,我们还想要生成默认移动构造,就可以使用default强制生成:

class Person
{
public:Person(const char* name = "", int age = 0):_name(name), _age(age){}//强制生成Person(Person&& p) = default;~Person(){}
private:bit::string _name;int _age;
};

但是会发生报错:
在这里插入图片描述
因为加入Person(Person&& p)会产生一系列受到影响,Person&& p引用折叠可以接收右值也可以接收左值,那么拷贝构造就不会默认产生,所以进行强制生成后,要将四个进行绑定:

public:Person(const char* name = "", int age = 0):_name(name), _age(age){}//强制生成Person(const Person& p) = default;Person& operator=(const Person& p) = default;Person(Person&& p) = default;Person& operator=(Person&& p) = default;~Person(){}
private:bit::string _name;int _age;
};

禁止生成默认函数的关键字delete:
如果能想要限制某些默认函数的生成,在C++98中,是该函数设置成private,并且只声明不定义,这样只要其他人想要调用就会报错。在C++11中更简单,只需在该函数声明加上=delete即可,该语法指示编译器不生成对应函数的默认版本,称=delete修饰的函数为删除函数。:

  • C++98 : 私有 + 只声明不实现
  • C++11 : 直接delete

遇到不想要进行拷贝的类可以使用delete,例如单例模式下的对象,只希望产生一个对象。
或者需要类的对象只能生成在堆上,就可以将构造函数delete就不能随意构造

class HeapOnly
{
public:HeapOnly* CreateObj(){}
private:HeapOnly(){}int _a = 1;
};int main()
{HeapOnly* p = CreateObj();
}

这样就涉及“先有蛋,先有鸡”的问题,想要对象需要调用方法,想要方法需要对象。这是就可以将类对象方法使用static修饰,变为类方法,调用类域中的函数既可以

HeapOnly* p = HeapOnly::CreateObj();

但是这样没有把路子卡死:

HeapOnly obj(*p);

就又可以进行栈上对象的拷贝构造了,所以不期望进行一个拷贝,就要将拷贝构造进行delete!
流对象就是不可以进行拷贝的

3 模块的可变参数

可变参数在C语言中我们见过的:
在这里插入图片描述
其中的…就是可变参数,我们可以传入任意的参数,都可以进行按照格式进行打印,这个的底层是一个数组,通过这个数组来获取所有的参数。虽然printf可以传入多个参数,但是只能打印内置类型!而通过模版的可变参数可以打印任意类型!

在C++中的可变参数上升了一个维度:模版的可变参数

// Args是一个模板参数包,args是一个函数形参参数包
// 声明一个参数包Args...args,这个参数包中可以包含0到任意个模板参数。
template <class ...Args>
void ShowList(Args... args)
{}

之前使用的模版类中模版参数都是固定的,使用这个参数包就可以进行可变参数了!注意参数包的使用方式:

  1. template <class …Args>:...在前
  2. Args… args:后面是...

我们可以来看看是不是传入多个参数:
在这里插入图片描述
没问题,我们可以通过sizeof...(args)来检查传入了多少个参数。
那么如何调用参数包的数据呢?

for(int i = 0 ; i < sizeof...(args) ; i++)
{cout<< args[i] <<endl;
}

注意奥,不是数组奥!这里不是通过数组来实现的!上面是运行时代码,但实际上解析模版参数包的工作是编译时做的!一定要区分好编译时和运行时。所以是不可能支持怎么操作的!
实际上是使用递归来做到:

//终止递归函数
template<class T>
void _PrintList(const T& val)
{cout << val << endl;
}
//过程函数
template<class T ,class ...Args>
void _PrintList(const T& val ,Args... args)
{cout << val << " ";_PrintList(args...);
}
//起始函数
template<class ...Args>
void PrintList(Args... args)
{_PrintList(args...);
}

在这里插入图片描述
这样就可以进行一个打印了!在编译时,编译器会自动推导出来这个打印函数的参数:(以三个参数的为例)
在这里插入图片描述
直接对...args是没有办法进行取出参数的,要进行递归逐个进行取用!同样的,我们也可以利用其他编译时方法来进行推导:数组处理。

template<class T>
int _PrintList(const T& val)
{cout << val << endl;return 0;
}template<class ...Args>
void PrintList(Args... args)
{int arr[] = { _PrintList(args)... };
}

通过这个数组会在编译器里进行推导!args...里面有几个参数,就会调用_PrinfList多少次,就会有几个返回值!

4 emplace系列接口

我们来看emplace系列接口:
在这里插入图片描述
在这里就使用到了模版的可变参数,是push_back的加强版!

int main()
{std::vector< std::pair<int, char> > v;// emplace_back支持可变参数,拿到构建pair对象的参数后自己去创建对象// 那么在这里我们可以看到除了用法上,和push_back没什么太大的区别v.emplace_back(10, 'a');v.emplace_back(make_pair(20, 'b'));std::pair<int, char> p(30, 'c');v.push_back(p);for (auto e : v)cout << e.first << ":" << e.second << endl;return 0;
}

运行看看:
在这里插入图片描述
插入bc时是和push_back是一致的 !但是比较特殊的是a,直接进行可变参数的插入!只要是单个参数是没有区别的,v.emplace_back(10, 'a');多参数的构造是emplace的优势!构造pair对象的对象,传给参数包直接进行构造,实际上和移动构造的效率差距并不大,对于只会进行浅拷贝的类型就没有优化!

template<class ...Args>
void emplace_back(Args&&... args)
{//进行完美转发,避免将右值变成左值emplace(end() , forward<T>(args)...);
}
//emplace
template<class ...Args>
void emplace(Args&&... args)
{Node* prev = pos._node->_prev;Node* next = pos._node;//其余都是一样的//这里直接传入参数包//需要修改底层Node* node = new Node(args...);node->_prev = prev;node->_next = next;prev->_next = node;next->_prev = node;_size++;
}

list的上层加入了emplace,还有需要修改Node的底层,加入对应函数包的构造:

template<class ...Args>
ListNode(Args&& ...args):_next(nullptr),_prev(nullptr),_data(args...)//参数包传到底层进行构造
{
}

再来细致来看看,_data的构造进行递归,如果是pair就直接进行了构造,如果是参数,就到pair的底层进行可变参数的构造!
在这里插入图片描述
传入一个pair就是生成一个const char* str , int val多参数函数
在这里插入图片描述
然后继续深入去调用!参数包的本质就是编译时实例化生成一个多参数的函数,使用多参数函数就是调用这个
多参数的函数!

Thanks♪(・ω・)ノ谢谢阅读!!!

下一篇文章见!!!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/pingmian/51573.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

C语言:指针(1)

一. 内存和地址 比如&#xff0c;我们的内存就相当⼀栋宿舍楼&#xff0c;楼里有很多的房间&#xff0c;每个房间都有一个房间号&#xff0c;每个房间里都住着8个人。这时如果你的朋友想要来找你&#xff0c;我们只需要把房间号告诉他就能快速的找到我们。 然而&#xff0c;&…

Stable Diffusion 提示词攻略

一、提示词作用 提示词所做的工作是缩小模型出图的解空间&#xff0c;即缩小生成内容时在模型数据里的检索范围&#xff0c;而非直接指 定作画结果。 提示词的效果也受模型的影响&#xff0c;有些模型对自然语言做特化训练&#xff0c;有些模型对单词标签对做特化训练&#xf…

负债了,打死也别干的六件事!逾期了,六句谎言千万别信!

负债这事儿&#xff0c;真是一言难尽&#xff0c;稍不留神&#xff0c;就可能让情况雪上加霜。今儿咱们聊聊&#xff0c;负债后那几件打死也别干的几件事&#xff0c;尤其是针对还没有逾期的朋友们&#xff0c;免得后悔莫及。 首先&#xff0c;千万别动歪脑筋&#xff0c;拿公款…

【Golang 面试 - 基础题】每日 5 题(十)

✍个人博客&#xff1a;Pandaconda-CSDN博客 &#x1f4e3;专栏地址&#xff1a;http://t.csdnimg.cn/UWz06 &#x1f4da;专栏简介&#xff1a;在这个专栏中&#xff0c;我将会分享 Golang 面试中常见的面试题给大家~ ❤️如果有收获的话&#xff0c;欢迎点赞&#x1f44d;收藏…

YOLOV8-人员越界识别

原文:YOLOV8-人员越界识别 - 知乎 (zhihu.com) 一、人员越界识别背景描述 实际生活中某些场景下需要配合摄像头自动识别危险区域,并在发现有人员闯入危险区域(禁止进入区域)时进行报警。翻越围墙监测预警系统对监控区域内的护栏、围墙设定警戒围墙区域,一旦有可疑人员靠…

springboot电子产品销售系统-计算机毕业设计源码80294

摘 要 电子商务行业在全球范围内迅速发展&#xff0c;随之而来的是电子产品销售市场的快速增长和消费者对在线购物体验的需求提升&#xff0c;因此&#xff0c;电子产品销售系统应运而生。该系统旨在满足电子产品市场的需求&#xff0c;提供全面的购物功能和高效的管理操作。 …

高级及架构师高频面试题-基础型

1、设计模式有哪些原则&#xff08;待解释的更直白&#xff09; 单一职责原则&#xff1a;一个类或方法应只负责一项职责&#xff0c;避免一个类因为多个变化原因而改变。开闭原则&#xff1a;软件实体应对扩展开放&#xff0c;对修改封闭。比如要增加用户类别的时候可以新增一…

校车购票小程序的设计

管理员账户功能包括&#xff1a;系统首页&#xff0c;个人中心&#xff0c;学生管理&#xff0c;我的乘车信息管理&#xff0c;车辆信息管理&#xff0c;座位管理&#xff0c;系统管理 微信端账号功能包括&#xff1a;系统首页&#xff0c;车辆信息&#xff0c;我的 开发系统…

【项目管理】高手项目经理都在用的6个SOP

SOP&#xff08;Standard Operating Procedure&#xff09;流程是一种标准化的操作指南&#xff0c;旨在确保组织或团队在各种情况下都能高效、一致地完成任务。SOP流程通常包括详细的步骤、关键控制点和责任分配&#xff0c;以确保质量和安全。SOP流程涉及从日常运营到危机管理…

28.x86游戏实战-初探XXX发包

免责声明&#xff1a;内容仅供学习参考&#xff0c;请合法利用知识&#xff0c;禁止进行违法犯罪活动&#xff01; 本次游戏没法给 内容参考于&#xff1a;微尘网络安全 工具下载&#xff1a; 链接&#xff1a;https://pan.baidu.com/s/1rEEJnt85npn7N38Ai0_F2Q?pwd6tw3 提…

GPT-4o mini小型模型具备卓越的文本智能和多模态推理能力

GPT-4o mini 是首个应用OpenAI 指令层次结构方法的模型&#xff0c;这有助于增强模型抵抗越狱、提示注入和系统提示提取的能力。这使得模型的响应更加可靠&#xff0c;并有助于在大规模应用中更安全地使用。 GPT-4o mini 在学术基准测试中&#xff0c;无论是在文本智能还是多模…

微信小游戏之三消(二)主要游戏控制方法

设计一个 game class。负责了游戏的核心控制逻辑&#xff0c;包括游戏状态管理、方块和道具的生成与效果处理&#xff0c;以及游戏的重新开始和复活流程。通过这些方法&#xff0c;脚本实现了游戏的基本玩法和用户交互。 主要游戏控制方法 gameStart()&#xff1a;开始游戏&am…

Java学习Day16:基础篇6

1.静态和非静态 2.调用静态和非静态的过程 注&#xff1a;在Java中&#xff0c;同类中&#xff0c;确实可以使用类的对象来调用静态方法&#xff0c;尽管这不是推荐的做法。静态方法属于类本身&#xff0c;而不是类的任何特定实例。因此&#xff0c;理论上讲&#xff0c;你应该…

【iOS】—— KVO与KVC

KVO与KVC 1. KVOKVO底层实现分析如何验证上面的说法&#xff1a;NSKVONotifyin_Person内部结构didChangeValueForKey:内部会调用observer的observeValueForKeyPath:ofObject:change:context:方法 回答问题&#xff1a; 2. KVC简介&#xff1a;key和keyPath的区别key&#xff1a…

探索 Electron:如何进行网址收藏并无缝收录网页图片内容?

Electron是一个开源的桌面应用程序开发框架&#xff0c;它允许开发者使用Web技术&#xff08;如 HTML、CSS 和 JavaScript&#xff09;构建跨平台的桌面应用程序&#xff0c;它的出现极大地简化了桌面应用程序的开发流程&#xff0c;让更多的开发者能够利用已有的 Web 开发技能…

EtherNet/IP转CAN协议转化网关(功能与配置)

怎么样把EtherNet/IP和CAN两个协议连接起来?有很多朋友想要了解这个问题&#xff0c;那么作者在这里统一说明一下。其实有一个不错的设备产品可以很轻易地解决这个问题&#xff0c;名为JM-EIP-ECAT网关。接下来作者就从该设备的功能及配置详细说明一下。 一&#xff0c;设备主…

聊聊基于Alink库的主成分分析(PCA)

概述 主成分分析&#xff08;Principal Component Analysis&#xff0c;PCA&#xff09;是一种常用的数据降维和特征提取技术&#xff0c;用于将高维数据转换为低维的特征空间。其目标是通过线性变换将原始特征转化为一组新的互相无关的变量&#xff0c;这些新变量称为主成分&…

TinyMCE一些问题

1.element 在el-dialog中使用tinymce导致富文本弹窗在el-dialog后面的问题 原因是富文本的弹窗层级太低了 在APP.vue中添加样式即可解决 /* 富文本菜单 */ .tox-tinymce-aux {z-index: 9999 !important; }2.element 在el-dialog中点击富文本的功能栏报错 由于 aria-hidden 属…

Midjourney、Sora和硅谷机密-《分析模式》漫谈15

DDD领域驱动设计批评文集 做强化自测题获得“软件方法建模师”称号 《软件方法》各章合集 “Analysis Patterns”的Preface&#xff08;前言&#xff09;有这么一句&#xff1a; Kent Beck, Ward Cunningham, and Jim Coplein encouraged me to get involved with the commu…