【C++】C++11新特性重点:可变参数+lambda

C++11新特性第二篇重点

文章目录

  • 上一篇的补充
  • 一、可变参数模板
  • 二、lambda函数
  • 总结


前言

上一篇我们重点讲解了右值引用+移动语义,关于移动构造和移动赋值还有一些需要补充的知识:

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

一、较为简单的新特性

1.delete关键字

当我们不想让一个类的拷贝构造函数去拷贝,那么我们可以在后面加上delete关键字,如下:

class Person
{
public:
	Person(const char* name = "", int age = 0)
		:_name(name)
		, _age(age)
	{}
	Person(const Person& s) = delete;
private:
	string _name;
	int _age;
};
int main()
{
	Person p1;
	Person p2(p1);
	return 0;
}

 我们将Person类中拷贝构造函数加上delete后,我们发现不能再使用拷贝构造了,如下图:

 2.final关键字

final关键字的作用是修饰一个类,让一个类不能被继承。final也可以修饰一个成员函数,可以让这个成员函数不能被重写。

3.override关键字

override可以强制检查派生类的虚函数是否完成重写,如果没有完成重写就报错。(纯虚函数可以强制子类完成重写)

4.可变参数列表

C++11 的新特性可变参数模板能够让您创建可以接受可变参数的函数模板和类模板,相比 C++98/03,类模版和函数模版中只能含固定数量的模版参数,可变模版参数无疑是一个巨大的改
进。然而由于可变模版参数比较抽象,使用起来需要一定的技巧,所以这块还是比较晦涩的。
// Args 是一个模板参数包, args 是一个函数形参参数包
// 声明一个参数包 Args...args ,这个参数包中可以包含 0 到任意个模板参数。
template <class ...Args>
void ShowList(Args... args)
{
	cout << sizeof...(args) << endl;
}
int main()
{
	ShowList();
	ShowList(" ", 11, "hello", 12);
	return 0;
}

 上面是一个可变参数包,第一个参数包的参数为0,第二个参数包的参数为4,一个空格字符串,一个数字11,一个hello字符串,一个数字12,我们sizeof打印的时候一定是...在括号外面,实际上...就代表参数包,下面我们看看运行结果是否正确:

 那么如果解析这个可变参数包呢?解析参数包的意思就是拿到参数包的内容,比如第二个参数包的4个参数。这里我们给出两种方法:

第一种:递归方式

void ShowList()
{
	cout << endl;
}
template <class T, class ...Args>
void ShowList(const T& val, Args... args)
{
	/*cout << sizeof...(args) << endl;*/
	cout << val << " ";
	ShowList(args...);
}
int main()
{
	ShowList();
	ShowList(" ", 11, "hello", 12);
	return 0;
}

注意:递归传参数包的时候后面必须加上...

第二个方法:

template <class T>
void PrintArg(T t)
{
	cout << t << " ";
}
template <class ...Args>
void ShowList(Args... args)
{
	int arr[] = { (PrintArg(args),0)... };
	cout << endl;
}
int main()
{
	ShowList(1, 'A', "hello", 54);
	return 0;
}

这种展开参数包的方式,不需要通过递归终止函数,是直接在 expand 函数体中展开的 , printarg
不是一个递归终止函数,只是一个处理参数包中每一个参数的函数。这种就地展开参数包的方式
实现的关键是逗号表达式。我们知道逗号表达式会按顺序执行逗号前面的表达式。
expand 函数中的逗号表达式: (printarg(args), 0) ,也是按照这个执行顺序,先执行
printarg(args) ,再得到逗号表达式的结果 0 。同时还用到了 C++11 的另外一个特性 —— 初始化列
表,通过初始化列表来初始化一个变长数组 , {(printarg(args), 0)...} 将会展开成 ((printarg(arg1),0),
(printarg(arg2),0), (printarg(arg3),0), etc... ) ,最终会创建一个元素值都为 0 的数组int arr[sizeof...
(Args)] 。由于是逗号表达式,在创建数组的过程中会先执行逗号表达式前面的部分 printarg(args)
打印出参数,也就是说在构造 int 数组的过程中就将参数包展开了,这个数组的目的纯粹是为了在
数组构造的过程展开参数包

 上面arr数组中存放的参数包至于这个数组多大看参数包有多少个参数,...一定要放在括号外面这样编译器才能推出来参数,其中PrintArg用的逗号表达式是因为最后的结果必须是0,如果我们不用PrintArg的返回值的话,我们可以修改一下代码:

template <class T>
int PrintArg(T t)
{
	cout << t << " ";
	return 0;
}
template <class ...Args>
void ShowList(Args... args)
{
	int arr[] = { PrintArg(args)... };
	cout << endl;
}
int main()
{
	ShowList(1, 'A', "hello", 54);
	return 0;
}

将...写在括号外面的意思是:每次生成一个PrintArg函数,具体要生成多少个要看这个参数包有多少个参数。

不知道大家还记不记得STL容器中的emplace系列,这个系列就是用参数包做的:

实际上这个参数包就是C语言中的可变参数列表变化而来的。

 下面我们看看参数包对应的emplace系列的使用:

int main()
{
	list<sxy::string> mylist;
	sxy::string s1("hello");
	mylist.push_back(s1);
	mylist.emplace_back(s1);
	return 0;
}

我们先看看emplace系列和普通的push_back有什么不同,可以看到emplace系列用了可变参数包,这里用了万能引用,也就是左值引用和右值引用都可以使用,下面我们运行起来看看区别:

 运行后我们发现并没有什么区别,我们再试试右值:

 那么emplace系列真正的区别点在哪里呢?下面我们看看匿名对象当右值:

int main()
{
	list<sxy::string> mylist;
	//有区别
	mylist.push_back("3333");
	mylist.emplace_back("3333");
	return 0;
}

 我们可以看到,push_back在插入右值的时候,是构造+移动构造,而emplace版本只需要一个构造,为什么会这样呢?因为我们传的const cahr*类型,这个编译器推参数包的时候直接推导为char*类型,而我们string类本来就有char*类型的直接构造,所以emplace比普通版本的插入效率高!

总结:emplace系列对于深拷贝的类效果不大,因为这里的参数包没有多大效果。对于Date类这种emplace就起了效果,参数包可以直接调用其构造函数。

二、lambda表达式

首先我们学习一下语法:

lambda 表达式书写格式:
[capture-list] (parameters) mutable -> return-type { statement }
1. lambda 表达式各部分说明
[capture-list] : 捕捉列表 ,该列表总是出现在 lambda 函数的开始位置, 编译器根据 []
判断接下来的代码是否为 lambda 函数 捕捉列表能够捕捉上下文中的变量供 lambda
函数使用
(parameters) :参数列表。与 普通函数的参数列表一致 ,如果不需要参数传递,则可以
连同 () 一起省略
mutable :默认情况下, lambda 函数总是一个 const 函数, mutable 可以取消其常量
性。使用该修饰符时,参数列表不可省略 ( 即使参数为空 )
->returntype :返回值类型 。用 追踪返回类型形式声明函数的返回值类型 ,没有返回
值时此部分可省略。 返回值类型明确情况下,也可省略,由编译器对返回类型进行推
{statement} :函数体 。在该函数体内,除了可以使用其参数外,还可以使用所有捕获
到的变量。
注意:
lambda 函数定义中, 参数列表和返回值类型都是可选部分,而捕捉列表和函数体可以为
。因此 C++11 最简单的 lambda 函数为: []{} ; lambda 函数不能做任何事情。
捕获列表说明
捕捉列表描述了上下文中那些数据可以被 lambda 使用 ,以及 使用的方式传值还是传引用
[var] :表示值传递方式捕捉变量 var
[=] :表示值传递方式捕获所有父作用域中的变量 ( 包括 this)
[&var] :表示引用传递捕捉变量 var
[&] :表示引用传递捕捉所有父作用域中的变量 ( 包括 this)
[this] :表示值传递方式捕捉当前的 this 指针
注意:
a. 父作用域指包含 lambda 函数的语句块
b. 语法上捕捉列表可由多个捕捉项组成,并以逗号分割
比如: [=, &a, &b] :以引用传递的方式捕捉变量 a b ,值传递方式捕捉其他所有变量
[& a, this] :值传递方式捕捉变量 a this ,引用方式捕捉其他变量
c. 捕捉列表不允许变量重复传递,否则就会导致编译错误
比如: [=, a] = 已经以值传递方式捕捉了所有变量,捕捉 a 重复
d. 在块作用域以外的 lambda 函数捕捉列表必须为空
f. lambda 表达式之间不能相互赋值 ,即使看起来类型相同

上面的语法大家先了解一下,下面我们用例子来讲解lambda表达式中有哪些该注意的事项:

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 } };
	sort(v.begin(), v.end(), ComparePriceLess());
	sort(v.begin(), v.end(), ComparePriceGreater());
}

以前我们对于这种自定义的类进行比较的时候都需要去单独写一个仿函数或者函数指针等,虽然能解决排序的问题但是相对较麻烦,下面我们用lambda表达式比较一下:

int main()
{
	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;
		});
	for (auto& e : v)
	{
		cout << e._name << " : " << e._evaluate << " : " << e._price << endl;
	}
	cout << "---------------------------------------" << endl;
	sort(v.begin(), v.end(), [](const Goods& g1, const Goods& g2)->bool
		{
			return g1._name < g2._name;
		});
	for (auto& e : v)
	{
		cout << e._name << " : " << e._evaluate << " : " << e._price << endl;
	}
	
}

 我们可以看到lambda表达式对于这样的情况处理起来简直太方便了,具体的语法我们下面进行讲解:

 上图中我们可以详细的看到lambda的每个部分,注意函数体内的;和语句结尾的;。就以上面的表达式为例,首先我们的返回值类型是可以省略的,如下图:

 使用起来也很简单,可以用lambda对象,也可以直接用对象名:

 注意:lambda表达式中有两个位置是一定不可以省略的,第一个是捕捉列表[],第二个是函数体{}.

下面我们讲讲捕捉列表的作用:

比如我们现在要实现一个交换的函数:

int main()
{
	int x = 10, y = 20;
	auto swap = [](int x, int y)
	{
		int tmp = x;
		x = y;
		y = tmp;
	};
	swap(x, y);
	cout << x << ":" << y << endl;
	return 0;
}

 这里为什么没有交换成功呢?因为我们lambda表达式中的参数是传值的,函数体内交换的是x和y的临时拷贝,所以我们应该用传引用:

 这次我们看到成功了,下面我们在用用捕捉列表,捕捉列表可以捕捉我们当前作用域的值:

 捕捉后我们发现不能修改,这是因为lambda捕捉列表为了防止有人直接修改作用域的值,所以捕捉过来的值不允许修改,那么如何修改呢?我们需要在后面加上mutable,这个的含义是异变:

int main()
{
	int x = 10, y = 20;
	auto swap = [x, y]()mutable
	{
		int tmp = x;
		x = y;
		y = tmp;
	};
	swap();
	cout << x << ":" << y << endl;
	return 0;
}

 我们发现捕捉后还是不能成功交换啊,这是因为默认的捕捉是传值捕捉,我们需要在捕捉变量前面加上&符号,这样就是引用捕捉了:

 下面我们两个特殊的用法:

全部引用捕捉:

全部传值捕捉:

 混合捕捉:

 上面捕捉列表的意思是:x使用传值捕捉,其他全是引用捕捉。

下面我们用一下线程来演示lambda表达式的魅力:(不知道线程相关知识的可以看我linux线程的文章)

#include <thread>

void func(int n)
{
	for (int i = 0; i < n; i++)
	{
		cout << i << " ";
	}
	cout << endl;
}
int main()
{
	thread t1(func, 10);
	thread t2(func, 5);
	t1.join();
	t2.join();
	return 0;
}

以上是传统写法,我们让两个线程去执行func函数打印,下面是结果:

 出现上面结果的原因不难解释,因为我们的线程是并发访问的,所以这个函数是很有可能被两个线程同时进入的,这就导致换行出现问题。下面我们用lambda表达式来写上面的代码:

int main()
{
	int n = 10;
	thread t1([n]()
		{
			for (int i = 0; i < n; i++)
			{
				cout << i << " ";
			}
			cout << endl;
		});
	thread t2([n]()
		{
			for (int i = 0; i < n; i++)
			{
				cout << i << " ";
			}
			cout << endl;
		});
	t1.join();
	t2.join();
	return 0;
}

 以上就是lambda表达式的所有内容了,后面我们讲解c++11线程的时候也会大量的用到lambda表达式。


总结

函数对象和lambda表达式:

函数对象,又称为仿函数,即可以想函数一样使用的对象,就是在类中重载了 operator() 运算符的
类对象。
class Rate
{
public:
 Rate(double rate): _rate(rate)
 {}
 double operator()(double money, int year)
 { return money * _rate * year;}
private:
 double _rate;
};
int main()
{
// 函数对象
 double rate = 0.49;
 Rate r1(rate);
 r1(10000, 2);
// lamber
 auto r2 = [=](double monty, int year)->double{return monty*rate*year; 
};
 r2(10000, 2);
 return 0;
}

在上述代码中,我们运行起来查看汇编代码看lambda的底层是什么:

 实际在底层编译器对于lambda表达式的处理方式,完全就是按照函数对象的方式处理的,即:如

果定义了一个 lambda 表达式,编译器会自动生成一个类,在该类中重载了 operator() 。如上大家应该明白了,实际上lambda表达式的底层就是用函数对象实现的,就像范围for一样看起来很高大上,实际上底层是迭代器。

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

RabbitMq消息堆积问题及惰性队列

消息堆积问题 当生产者发送消息的速度超过了消费者处理的速度&#xff0c;就会导致队列的消息堆积&#xff0c;知道队列存储消息达到上限。最早接受的消息&#xff0c;可能就会成为死信&#xff0c;会被丢弃&#xff0c;这就是消息堆积问题。 解决消费对接问题 1.增加更多的消…

【数据库一】MySQL数据库初体验

MySQL数据库初体验 1.数据库基本概念1.1 数据Data1.2 表1.3 数据库1.4 数据库管理系统1.5 数据库系统 2.数据库的发展3.主流的数据库介绍3.1 SQL Server&#xff08;微软公司产品&#xff09;3.2 Oracle &#xff08;甲骨文公司产品&#xff09;3.3 DB2&#xff08;IBM公司产品…

XSS—存储型xss

xss >跨站脚本攻击>前端代码注入>用户输入的数据会被当做前端代码执行。 原理&#xff1a;使用者提交的XSS代码被存储到服务器上的数据库里或页面或某个上传文件里&#xff0c;导致用户访问页面展示的内容时直接触发xss代码。 输入内容后直接在下方回显&#xff0c;回…

Linux UPS配置详解 (山特SANTAK TGBOX-850 )

目录 起因 安装NUT NUT简介 配置 ups配置 &#xff08;nut-driver&#xff09; nut-server配置 nut.conf upsd.conf upsd.users nut-client配置 upsmon.conf 设置自动启动 释疑 起因 配置了一台All in One主机&#xff0c;系统是装的PVE&#xff0c;一个linux的虚…

和鲸社区数据分析每周挑战【第九十三期:特斯拉充电桩分布分析】

和鲸社区数据分析每周挑战【第九十三期&#xff1a;特斯拉充电桩分布分析】 文章目录 和鲸社区数据分析每周挑战【第九十三期&#xff1a;特斯拉充电桩分布分析】一、前言二、数据读取和初步探索三、数据探索及可视化1、获取拥有最多充电站的 10 个国家2、一年中各月新开业数量…

微软wsl2 + ubantu + docker + 部署本地项目

windows 操作系统版本要达到要求 开启 wsl2 安装实用工具 Windows Terminal 和 Visual Studio Code 安装 Ubuntu 子系统 安装 Docker Desktop 并让 Docker Desktop 基于 wsl2 来运行 基础环境准备可以完全参照《搭建 Laravel Sail 开发环境 - Windows》来进行&#xff0c;我跟教…

微信小程序基础使用-请求数据并渲染

小程序基本使用-请求数据并渲染 小程序模板语法-数据绑定 在js中定义数据 Page({data: {isOpen: true,message: hello world!} })小程序的data是一个对象&#xff0c;不同于vue的data是一个函数 在模块中获取使用数据 小程序中使用 {{}} 实现数据与模板的绑定 内容绑定&a…

Spring Boot整合JPA

文章目录 一、Spring Boot整合JPA&#xff08;一&#xff09;创建Spring Boot项目JPADemo&#xff08;二&#xff09;创建ORM实体类1、创建评论实体类 - Comment2、创建文章实体类 - Article &#xff08;三&#xff09;创建自定义JpaRepository接口 - ArticleRepository&#…

1.数据库的基本操作

SQL句子中语法格式提示&#xff1a; 1.中括号&#xff08;[]&#xff09;中的内容为可选项&#xff1b; 2.[&#xff0c;...]表示&#xff0c;前面的内容可重复&#xff1b; 3.大括号&#xff08;{}&#xff09;和竖线&#xff08;|&#xff09;表示选择项&#xff0c;在选择…

【Axure 教程】中继器(基础篇)

一、初识中继器 中继器是 Axure 中一个比较高阶的应用&#xff0c;它可以让我们在纯静态网页中模拟出类似带有后台数据交互的增删改查的效果&#xff0c;虽然它没有真正意义上帮我们存储任何的数据&#xff0c;但是当我们在一次项目体验过程中&#xff0c;它却可以给我们带来更…

如何获取HTTP请求时间与响应时间【附源码】

文章目录 一、问题描述二、抓包观察三、查找文档四、思考尝试五、精益求精六、源码解说 一、问题描述 今日遇到了一个问题&#xff0c;要去获取HTTP报文在请求和响应的时间&#xff0c;因为没有原生的API可以调用&#xff0c;所以需要一定的技巧~ 下面主体的框架和代码&#xf…

Vue中如何进行样式绑定?

Vue中如何进行样式绑定&#xff1f; 在Vue中&#xff0c;我们可以很方便地进行样式绑定。样式绑定是将CSS样式与Vue组件中的数据进行关联的一种技术。通过样式绑定&#xff0c;我们可以根据组件的状态动态地修改其外观。本文将介绍Vue中的样式绑定&#xff0c;包括类绑定、内联…

软件外包开发项目原型图工具

项目原型图工具有非常重要的作用&#xff0c;尤其是在APP项目开发中&#xff0c;对于整体需求的表达是必不可少的工具。相比于传统的文档需求&#xff0c;图形文字的表达可以更清楚的表达需求&#xff0c;让客户清楚的明白软件功能有哪些&#xff0c;最后的界面是怎样的&#x…

Haproxy搭建Web群集

Haproxy搭建Web群集 1.Haproxy相关概念1.1 Haproxy的概述1.2 Haproxy的主要特性1.3 常见的Web集群调度器 2.常见的应用分析2.1 LVS 应用2.2 Haproxy 应用2.3 LVS、Nginx、Haproxy的区别2.4 Haproxy调度算法原理 3. Haproxy命令行详解3.1 HAProxy服务的5个域3.2 Haproxy服务器配…

【无功优化】基于改进教与学算法的配电网无功优化【IEEE33节点】(Matlab代码时候)

&#x1f4a5;&#x1f4a5;&#x1f49e;&#x1f49e;欢迎来到本博客❤️❤️&#x1f4a5;&#x1f4a5; &#x1f3c6;博主优势&#xff1a;&#x1f31e;&#x1f31e;&#x1f31e;博客内容尽量做到思维缜密&#xff0c;逻辑清晰&#xff0c;为了方便读者。 ⛳️座右铭&a…

苹果新专利曝光:AirTags可以快速找到Apple Pencil

近日&#xff0c;据外媒报道&#xff0c;苹果一项新专利提出&#xff0c;苹果手写笔可以通过“声学谐振器”来帮助用户找出手写笔的位置。根据这项专利&#xff0c;苹果试图在手写笔的笔盖上加入一个被动元件&#xff0c;以响应特定的声波频率。iPhone、iPad或Apple Watch会发出…

插入排序代码

时间复杂度O&#xff08;n&#xff09;

Nik Color Efex 滤镜详解(2/5)

交叉冲印 Cross Processing 提供多种选项来处理 C41 - E6&#xff08;用幻灯片显影液处理彩色底片&#xff09;和 E6 - C41&#xff08;用彩色底片显影液处理幻灯片&#xff09;。 方法 Method 选择预设。 强度 Strength 控制滤镜效果程度。 黑暗对比度 Dark Contrasts 使用新…

六一,用前端做个小游戏回味童年

#【六一】让代码创造童话&#xff0c;共建快乐世界# 文章目录 &#x1f4cb;前言&#x1f3af;简简单单的弹球游戏&#x1f3af;代码实现&#x1f4dd;最后 &#x1f4cb;前言 六一儿童节。这是属于孩子们的节日&#xff0c;也是属于我们大人的节日&#xff08;过期儿童&…

Intellij IDEA设置“选中变量或方法”的背景颜色、字体颜色(Mark Occurrences)

背景 IDEA 中选中一个变量就会将所有的变量相关变量标出来&#xff0c;这样就很方便知道这个变量出现的地方。Eclipse里头把这个功能叫做 Mark Occurrences&#xff0c;IDEA 里不知道怎么称呼。 我们要解决的痛点就是提示不明显&#xff0c;如下图所示&#xff0c;Macbook这么…