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

news2024/11/16 23:45:26

在这里插入图片描述

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

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.coloradmin.cn/o/1956086.html

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

相关文章

国防科技大学深圳地区新生欢送会圆满举行

2024年7月28日&#xff0c;第97个八一建军节来临之际&#xff0c;在这个充满希望的盛夏时节&#xff0c;深圳地区迎来了13名即将踏入国防科技大学的优秀学子。 为了庆祝这一荣耀时刻&#xff0c;并表达对新生的深切祝福&#xff0c;在国防科技大学深圳校友会黄丹会长的积极倡议…

小白也能读懂的ConvLSTM!(开源pytorch代码)

ConvLSTM 1. 算法简介与应用场景2. 算法原理2.1 LSTM基础2.2 ConvLSTM原理2.2.1 ConvLSTM的结构2.2.2 卷积操作的优点 2.3 LSTM与ConvLSTM的对比分析2.4 ConvLSTM的应用 3. PyTorch代码参考文献 仅需要网络源码的可以直接跳到末尾即可 1. 算法简介与应用场景 ConvLSTM&#x…

“手撕”MySQL的索引

目录 二、索引的作用 三、索引的缺点 四、如何使用索引 查看索引&#xff1a; 创建索引&#xff1a; ​编辑 删除索引&#xff1a; 五、索引的底层原理 那什么是B树&#xff0c;什么是B树呢&#xff1f; B树的好处&#xff1a; 总结&#xff1a; 一、什么是索引 索…

OpenCV 图像预处理—图像金字塔

文章目录 相关概念高斯金字塔拉普拉斯金字塔应用 构建高斯金字塔为什么要对当前层进行模糊&#xff1f;1. 平滑处理2. 减少混叠&#xff08;Aliasing&#xff09;3. 多尺度表示4. 图像降采样 举个栗子创建高斯金字塔和拉普拉斯金字塔&#xff0c;并用拉普拉斯金字塔恢复图像 相…

《汇编语言 基于x86处理器》- 读书笔记 - 第3章-汇编语言基础

《汇编语言 基于x86处理器》- 读书笔记 - 第3章-汇编语言基础 3.1 基本语言元素3.1.1 第一个汇编语言程序常见汇编语言调用规范 3.1.2 整数常量&#xff08;基数、字面量&#xff09;3.1.3 整型常量表达式3.1.4 实数常量十进制实数十六进制实数&#xff08;编码实数&#xff09…

使用git命令行的方式,将本地项目上传到远程仓库

在国内的开发环境中&#xff0c;git的使用是必不可少的。Git 是一款分布式版本控制系统&#xff0c;用于有效管理和追踪文件的变更历史及协作开发。本片文章就来介绍一下怎样使用git命令行的方式&#xff0c;将本地项目上传到远程仓库&#xff0c;虽然现在的IDE中基本都配置了g…

Ubuntu安装terminator教程

Terminator 是一个高级的终端仿真器,专为 Linux 和 Unix 系统设计。它的主要特点是提供了丰富的多窗口和多标签功能,使用户能够在一个窗口中管理多个终端会话。这对于系统管理员、开发人员以及需要同时运行多个命令行任务的用户来说,极为方便。 一、安装 1、更新包 sudo a…

使用Selenium爬虫批量下载AlphaFold数据库中的PDB文件

注意&#xff1a;本方法使用了python&#xff0c;下载速度一般&#xff0c;如果需要更快的大批量下载可以考虑使用其他方法&#xff0c;例如FTP Alphafold数据库其实提供了许多物种的蛋白质组&#xff1a; AlphaFold Protein Structure Database 但是如果你搜索的物种不在这个…

算法面试leadcode【经典150道】

88 合并两个有序数组 方法一 使用arraycopy排序 * 思路一&#xff1a;将nums2合并到nums1的尾部&#xff0c;再直接进行排序。* 使用arraycopy(int[]nums1,int m,int[] nums2,int n)* 方法来进行排序&#xff0c;* 从原数组的哪个位置&#xff0c;移动到原数组的哪个位置&#…

xxl-job适配达梦数据库并制作镜像、源码部署xxl-job

背景&#xff1a;因项目需要信创&#xff0c;需将原本的mysql数据库&#xff0c;改成达梦数据库 一、部署达梦数据库 1.1 部署达梦数据库服务 可参考&#xff1a;Docker安装达梦数据库_达梦数据库docker镜像-CSDN博客 PS&#xff1a;部署达梦数据库时&#xff0c;需加上大小…

Java | Leetcode Java题解之第300题最长递增子序列

题目&#xff1a; 题解&#xff1a; class Solution {public int lengthOfLIS(int[] nums) {int len 1, n nums.length;if (n 0) {return 0;}int[] d new int[n 1];d[len] nums[0];for (int i 1; i < n; i) {if (nums[i] > d[len]) {d[len] nums[i];} else {int…

19. Revit API: Parameter(参数)

一、前言 我们在前面或多或少提到也用到参数了&#xff0c;这篇便细讲一下。 首先&#xff0c;我们知道好多信息都藏在参数里&#xff0c;或者说可以从参数中获取。我们还能够通过调整参数的值&#xff0c;改变模型的形态&#xff0c;即族的参变。 其次&#xff0c;有时族上…

【CAN通讯系列4】CAN通讯如何传递信号?

在【CAN通讯系列3】如何学习CAN通讯&#xff1f;中举了一个例子&#xff1a;新能源汽车要实现驱动功能&#xff0c;先需要整车控制器VCU计算目标转速或扭矩请求等信号&#xff0c;再通过CAN通讯传递给电机控制器MCU&#xff0c;就这个例子继续探讨CAN通讯的基础问题。 1 CAN数据…

入门 PyQt6 看过来(案例)08~ 页面布局

主题&#xff1a;学习页面布局控件以及布局容器的使用&#xff08;理论知识&#xff09; 1 布局控件 PyQt6的布局方式包括绝对布局、水平布局、垂直布局、网格布局和表单布局。 绝对布局&#xff1a;直接设置控件对象在参考坐标中的位置水平布局&#xff1a;对加入的控件对象从…

引用的项目“xxxx/tsconfig.node.json”可能不会禁用发出。

vue3 报错&#xff1a; 引用的项目“xxxx/tsconfig.node.json”可能不会禁用发出。 解决&#xff1a; 进入对应的 json 文件&#xff1a; 修改&#xff1a; "noEmit": false 当 noEmit 设置为 false 时&#xff0c;TypeScript 编译器将根据项目配置生成相应的输出文…

【数据结构初阶】单链表经典算法题十道(详解+图例)—得道飞升(终篇)

hi &#xff01; 目录 9、 环形链表 || 10、随机链表的复制 终章 9、 环形链表 || 【图解】 /*** Definition for singly-linked list.* struct ListNode {* int val;* struct ListNode *next;* };*/typedef struct ListNode ListNode; struct ListNode *detectCy…

Live800:客户服务中的情感智能,建立深厚客户关系的秘诀

在当今竞争激烈的市场环境中&#xff0c;客户服务已成为企业脱颖而出的关键因素之一。而情感智能&#xff0c;作为客户服务中的重要组成部分&#xff0c;更是建立深厚客户关系、提升客户满意度的秘诀所在。优秀的客户服务不仅关乎问题的解决&#xff0c;更在于情感的交流与共鸣…

物联网云盒多路开关量模拟量转无线MQTT钡铼技术S275

物联网云盒多路开关量模拟量转无线MQTT技术在现代工业自动化和远程监测中扮演着关键角色。钡铼第四代RTU S275作为一款先进的物联网数据监测采集控制短信报警终端&#xff0c;集成了多种先进技术和功能&#xff0c;旨在提升远程数据采集与控制的效率和可靠性。 钡铼第四代RTU …

在Ubuntu 12.10上安装和使用tmux的方法

前些天发现了一个巨牛的人工智能学习网站&#xff0c;通俗易懂&#xff0c;风趣幽默&#xff0c;忍不住分享一下给大家。点击跳转到网站。 关于 tmux tmux 是一个终端复用工具。它允许您使用多个虚拟终端访问 tmux 终端。 tmux 利用了客户端-服务器模型&#xff0c;这使您可…