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

news2024/11/15 13:36:28

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一样看起来很高大上,实际上底层是迭代器。

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

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

相关文章

dubbo 3.2.0 的filterChain 简要分析

dubbo 3.2.0 的filterChain 的核心类是DefaultFilterChainBuilder 。 Builder public class DefaultFilterChainBuilder implements FilterChainBuilder {的buildInvokerChain函数 对于consumer refer Overridepublic <T> Invoker<T> buildInvokerChain(final I…

Java自动化测试(web自动化测试框架 )

测试数据 测试地址 http://120.78.128.25:8765/ 投资人 13323234545 lemon123456 借款人 13323234444 lemonbest 后台地址 http://120.78.128.25:8765/Admin/Index/login.html lemon7 lemonbest Page Object PO简介 https://www.selenium.dev/documentation/en/g…

如何把在线K歌“玩起来”——专访撕歌音视频架构师程乐

编者按&#xff1a;在线K歌的业务已经发展了十年&#xff0c;程乐在音视频领域也闯荡了十年&#xff0c;甚至更久。为什么选择在线K歌领域&#xff1f;如何走过“漫长的季节”&#xff0c;迎来新的风景&#xff1f;如何在“在线K歌”这块难啃的骨头里分点肉&#xff1f;在这一连…

【存储】cache memory、primary memory and secondary memory

一、提要二、计算机的存储结构三、高速缓存&#xff1a;cache memory四、主存&#xff1a;Primary memory4.1 RAM4.11 SRAM 和 DRAM的概念4.12 SRAM 和 DRAM的应用场景 4.2 ROM4.21 PROM4.22 EPROM▶ EEPROM▶ UVEPROM 五、辅助存储器&#xff1a;secondary memory六、单片机的…

redis的4种模式,单机,哨兵、主从复制、集群

为了redis叫做redis服务器&#xff1f; 因为在运行时&#xff0c;在进行工作时是一个被注册一个进程(服务)&#xff0c;我们把他叫做一个redis服务器。(就是个应用程序而已。) 1.单机模式 我们安装redi,启动服务之后&#xff0c;默认就这个&#xff0c;只有一个redis服务器(…

编译原理笔记(哈工大编译原理)(持续更新)

文章目录 前言概论语言与文法基本概念字母表串字母表与串的联系 文法语言推导和规约句型与句子语言与字母表 文法的分类CFG的分析树 前言 说实话&#xff0c;我不是很想上这门课&#xff0c;确实没什么大用&#xff0c;虽然我觉得这门课学一学也挺好&#xff0c;但是我觉得弄8…

架构师必备项目管理方法-关键路径法

在《架构思维的六要素》中提到成本、规划、需求、维护、人员和质量是要考量的留个维度。咱们在日常工作中多少都会接触一些相关的管理方法&#xff0c;但是似乎是不知道也不影响干活&#xff0c;所以很多人也没有去深究。但实际上很可能知道了做事就更能抓住重点。 关键路径法是…

预测神经胶质瘤基因型的多模态学习

文章目录 Multi-modal learning for predicting the genotype of glioma摘要本文方法多模态数据生成Brain networks construction via self-supervised NNsMulti-modal learning for image, geometrics and brain networksBi-level multi-modal contrastive loss Population gr…

html实现好看的个人介绍,个人主页模板3(附源码)

文章目录 1.设计来源1.1 主界面1.2 关于我界面1.3 教育成就界面1.4 项目演示界面1.5 联系我界面 2.效果和源码2.1 动态效果2.2 源代码2.2 源代码目录 源码下载 作者&#xff1a;xcLeigh 文章地址&#xff1a;https://blog.csdn.net/weixin_43151418/article/details/131263195 …

新手Maven入门(一)

Mavenue介绍和基本概念 一、什么是Maven1.1 Maven的组成1.2 安装和配置Maven1.2.1 下载1.2.2 安装 二、Maven 的基本概念2.1 标准的目录结构2.2 POM 大纲2.2.1 pom大纲展示 2.3 构件2.3.1 什么是maven的构建 2.4 POM 文件的用例2.5 GAV 坐标 三、依赖 一、什么是Maven Maven 是…

2023年前端面试汇总-计算机网络

1. HTTP协议 1.1. GET和POST的请求的区别 Post 和 Get 是 HTTP 请求的两种方法&#xff0c;其区别如下&#xff1a; 1. 应用场景 GET 请求是一个幂等的请求&#xff0c;一般 Get 请求用于对服务器资源不会产生影响的场景&#xff0c;比如说请求一个网页的资源。而 Post 不是…

如何在Ubuntu上安装MongoDB?

一、Ubuntu安装MongoDB MongoDB安装很简单&#xff0c;无需下载源文件&#xff0c;可以直接用apt-get命令进行安装。 打开终端&#xff0c;输入以下命令 sudo apt-get install mongodb这时装好以后应该会自动运行mongod程序&#xff0c;通过命令查看进程是否已经启动 pgrep …

Spring 实现AOP常见的两种方式(注解或者自定义注解)

第一种 导入AOP相关坐标(依赖冲突解决办法&#xff0c;将依赖中版本号删除&#xff0c;springboot会自动匹配合适的版本 ) <dependencies><!--spring核心依赖&#xff0c;会将spring-aop传递进来--><dependency><groupId>org.springframework</gr…

自动化测试必会之数据驱动测试

数据驱动测试 在实际的测试过程中&#xff0c;我们会发现好几组用例都是相同的操作步骤&#xff0c;只是测试数据的不同&#xff0c;而我们往往需要编写多次用例来进行测试&#xff0c;此时我们可以利用数据驱动测试来简化该种操作。 参数化&#xff1a; 输入数据的不同从而产…

C语言:输入两个升序排列的序列,将两个序列合并为一个有序序列并输出。

题目&#xff1a; 描述 输入两个升序排列的序列&#xff0c;将两个序列合并为一个有序序列并输出。 输入描述&#xff1a; 输入包含三行&#xff0c; 第一行包含两个正整数n, m&#xff0c;用空格分隔。n表示第二行第一个升序序列中数字的个数&#xff0c;m表示第三…

C++ 教程(12)——循环

C 循环 有的时候&#xff0c;可能需要多次执行同一块代码。一般情况下&#xff0c;语句是顺序执行的&#xff1a;函数中的第一个语句先执行&#xff0c;接着是第二个语句&#xff0c;依此类推。 编程语言提供了允许更为复杂的执行路径的多种控制结构。 循环语句允许我们多次…

朋友圈功能合集来咯!定时发朋友圈,查看朋友圈,朋友圈跟圈,一键转发朋友圈,延迟评论

&#x1f30a;发布朋友圈 功能介绍&#xff1a;可使用已登录在系统上的微信号发送朋友圈。支持发送图片、文字、视频和公众号链接等几种类型的内容。 &#xff08;1)朋友圈内容编辑&#xff1a;可以在输入框中输入要发送的文本&#xff0c;并在浮窗中选择表情。上传图片可以点…

【c++11】c++11特性

c11 c11简介列表初始化std::initializer_list autodecltypenullptr 结语 c11简介 从C0x到C11&#xff0c;C标准10年磨一剑&#xff0c;第二个真正意义上的标准珊珊来迟。相比于C98/03&#xff0c;C11则带来了数量可观的变化&#xff0c;其中包含了约140个新特性&#xff0c;以…

QT QTreeView\QTreeWidget控件 使用详解

本文详细的介绍了QTreeView、QTreeWidget控件的各种操作&#xff0c;例如&#xff1a;新建界面、QTreeWidget、QTreeView、控件布局、设置列、设置宽高、设置列表头、设置复选框、设置图标、添加树、删除树、查找树、修改树、设置选中、树排序、事件、信号、槽函数、添加节点、…

【玩转Docker小鲸鱼叭】MacOS系统安装Docker

安装Docker Mac 系统安装 Docker 其实很简单&#xff0c;我们在官方文档下载安装一下就可以了&#xff0c;但是需要注意 Docker 官方建议 MacOS 必须是版本 11 或更高版本&#xff0c;如果版本较低&#xff0c;建议先升级 MacOS 版本。 可以通过左上角的小  图片查看系统版…