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

这篇具有很好参考价值的文章主要介绍了【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后,我们发现不能再使用拷贝构造了,如下图:

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

 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打印的时候一定是...在括号外面,实际上...就代表参数包,下面我们看看运行结果是否正确:

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

 那么如果解析这个可变参数包呢?解析参数包的意思就是拿到参数包的内容,比如第二个参数包的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;
}

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

这种展开参数包的方式,不需要通过递归终止函数,是直接在 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语言中的可变参数列表变化而来的。

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

 下面我们看看参数包对应的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系列用了可变参数包,这里用了万能引用,也就是左值引用和右值引用都可以使用,下面我们运行起来看看区别:

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

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

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

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

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

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

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

 我们可以看到,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;
	}
	
}

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

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

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

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

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

 使用起来也很简单,可以用lambda对象,也可以直接用对象名:【C++】C++11新特性重点:可变参数+lambda

【C++】C++11新特性重点:可变参数+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;
}

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

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

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

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

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

 捕捉后我们发现不能修改,这是因为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;
}

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

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

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

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

全部引用捕捉:

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

全部传值捕捉:

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

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

 上面捕捉列表的意思是: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函数打印,下面是结果:

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

 出现上面结果的原因不难解释,因为我们的线程是并发访问的,所以这个函数是很有可能被两个线程同时进入的,这就导致换行出现问题。下面我们用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;
}

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

 以上就是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的底层是什么:

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

 实际在底层编译器对于lambda表达式的处理方式,完全就是按照函数对象的方式处理的,即:如 文章来源地址https://www.toymoban.com/news/detail-490174.html

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

到了这里,关于【C++】C++11新特性重点:可变参数+lambda的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • 【C++】C++11可变参数模板

    👀 樊梓慕: 个人主页  🎥 个人专栏: 《C语言》 《数据结构》 《蓝桥杯试题》 《LeetCode刷题笔记》 《实训项目》 《C++》 《Linux》 《算法》 🌝 每一个不曾起舞的日子,都是对生命的辜负 目录 前言 可变参数模板的定义方式 可变参数模板的使用  编译时递归展开参数包

    2024年04月10日
    浏览(42)
  • 【C++】C++11语法 ~ 可变参数模板

    (꒪ꇴ꒪(꒪ꇴ꒪ )🐣,我是 Scort 目前状态:大三非科班啃C++中 🌍博客主页:张小姐的猫~江湖背景 快上车🚘,握好方向盘跟我有一起打天下嘞! 送给自己的一句鸡汤🤔: 🔥真正的大师永远怀着一颗学徒的心 作者水平很有限,如果发现错误,可在评论区指正,感谢🙏 🎉🎉

    2024年02月19日
    浏览(38)
  • 【C++杂货铺】C++11新特性——lambda

    在 C++98 中,如果要对一个数据集合中的元素进行排序,可以使用 std::sort 方法,下面代码是对一个整型集合进行排序。 小Tips :上面的 greater 是一个仿函数,这里传递仿函数是用来控制大小比较的。关于仿函数,在前面的文章中已经多次使用,在【C++杂货铺】优先级队列的使

    2024年02月03日
    浏览(37)
  • 【C++】c++11新特性(二)--Lambda函数及function(包装器)

    目录 Lambda函数 基本概念 基本语法 lambda 捕获(capture) 1. 不捕获任何变量 2. 按值捕获 3. 按引用捕获 4. 混合捕获 5. 捕获this 指针 包装器 function 基本概念 使用场景 1. 给function对象赋值  2. 作为函数参数和返回值   3. 存储在容器中 4. 绑定成员函数和带参数的函数        la

    2024年04月27日
    浏览(42)
  • 【C++干货铺】C++11新特性——lambda表达式 | 包装器

    ========================================================================= 个人主页点击直达:小白不是程序媛 C++系列专栏:C++干货铺 代码仓库:Gitee ========================================================================= 目录 C++98中的排序 lambda表达式 lambda表达式语法 表达式中的各部分说明 lambda表达式的使

    2024年01月21日
    浏览(45)
  • C++11新特性lambda 表达式

    Lambda 表达式的基本语法是:[] (参数列表) - 返回值类型 {函数体}。 方括号([])表示捕获列表,用来指定在 lambda 表达式中可以访问的外部变量。 参数列表和返回值类型与普通函数的参数列表和返回值类型相同。 函数体则是实际的代码逻辑。 不接受任何参数:[] { 函数体 } 接受

    2024年02月14日
    浏览(39)
  • C11新特性之Lambda表达式

    优点:  1.可以定义简短的函数。 2.使用lambda表达式使代码更紧凑,可读性更好。 语法: [] 表示不捕获任何变量 [this] 表示值传递方式捕捉当前的 this 指针  [] 表示引用传递方式捕捉所有父作用域的变量(包括 this ) [var] 表示 引用传递 捕捉变量 var [=] 表示值传递方式捕获所

    2023年04月22日
    浏览(43)
  • 【C++11】移动赋值 | 新的类功能 | 可变参数模板

    C++11中,string中的operator= 包含 参数为右值的版本 C++98中 没有移动赋值和移动构造 , 只有参数为左值 的赋值重载(operator=)和拷贝构造 本来只有两次深拷贝,但是由于调用拷贝赋值时,内部又进行一次拷贝构造,所以导致最终进行三次深拷贝 这里编译器是不能优化的, 因为优

    2024年02月08日
    浏览(45)
  • C++11『右值引用 ‖ 完美转发 ‖ 新增类功能 ‖ 可变参数模板』

    ✨个人主页: 北 海 🎉所属专栏: C++修行之路 🎃操作环境: Visual Studio 2022 版本 17.6.5 自从C++98以来,C++11无疑是一个相当成功的版本更新。它引入了许多重要的语言特性和标准库增强,为C++编程带来了重大的改进和便利。C++11的发布标志着C++语言的现代化和进步,为程序员

    2024年02月05日
    浏览(49)
  • 【C】【C++】可变参数、不定参函数的使用

    C 语言中的可变参数写法: ... 1.1 可变宏函数 以日志举例,我们写入日志时只需要输入关键信息,行号文件等由宏函数补全 这其中,我们需要输入的信息是格式不定的,需要用到可变参数 输出结果: C语言库中的宏 __FILE__ :字符串,记录当前文件名 __LINE__ :整型,记录当前

    2024年02月09日
    浏览(39)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包