C++第四十二弹---C++11新特性深度解析:让你的代码更现代、更高效(中)

news2024/11/15 10:32:16

 ✨个人主页: 熬夜学编程的小林

💗系列专栏: 【C语言详解】 【数据结构详解】【C++详解】

目录

1 右值引用和移动语义

1.1 左值引用和右值引用

1.2 左值引用与右值引用比较

1.3 右值引用使用场景和意义

1.4 右值引用引用左值及其一些更深入的使用场景分析

1.5 完美转发


1 右值引用和移动语义


1.1 左值引用和右值引用


传统的C++语法中就有引用的语法,而C++11中新增了的右值引用语法特性,所以从现在开始我们
之前学习的引用就叫做左值引用。无论左值引用还是右值引用,都是给对象取别名
 

什么是左值?什么是左值引用?

左值是一个表示数据的表达式(如变量名或解引用的指针),我们可以获取它的地址+可以对它赋值,左值可以出现赋值符号的左边右值不能出现在赋值符号左边。定义时const修饰符后的左值,不能给他赋值,但是可以取它的地址。左值引用就是给左值的引用,给左值取别名

代码演示

int main()
{
	// 左值是一个表达式,可以取地址
	// 左值和右值,能否取地址
	// 左值:能取地址
	// 右值:不能取地址

	int a = 10;
	int b = a;
	const int c = 10;
	int* p = &a;
	vector<int> v(10, 1);
	v[1];

	cout << &a << endl;
	cout << &b << endl;
	cout << &c << endl;
	cout << &(*p) << endl;
	cout << &(v[1]) << endl;
	return 0;
}

测试结果 

以上表达式均能够获取它的地址,因此均为左值。 

什么是右值?什么是右值引用?

右值也是一个表示数据的表达式,如:字面常量、表达式返回值,函数返回值(这个不能是左值引用返回)等等,右值可以出现在赋值符号的右边,但是不能出现出现在赋值符号的左边,右值不能取地址右值引用就是对右值的引用,给右值取别名

代码演示

int main()
{
    // 一下几个都是常见的右值
	// 10、string("1111")、to_string("1111")、x+y

	double x = 1.1;
	double y = 2.2;
    
    // 以下右值取地址会报错
	//cout << &10 << endl;
	//cout << &(string("1111")) << endl;
	//cout << &(to_string("1111")) << endl;
	//cout << &(x + y) << endl;
    
    // 以下几个都是对右值的右值引用
	double&& rref1 = x + y;
	string&& rref2 = string("11111");
	string&& rref3 = to_string(1111);
	int&& rref4 = 10;

	// 左值引用能否给右值取别名 ---不能,左值引用加const就能给右值取别名
	//string& ref1 = string("11111");// 错误
	
	const string& ref1 = string("11111");

	// 右值引用能否给左值取别名 ---不能,但是可以给move以后的左值取别名
	string s1("1111");
	string&& ref2 = move(s1);
	return 0;
}

测试结果 

右值引用,给值取别名

  • 纯右值(内置类型)
  • 将亡值(自定义类型) 

需要注意的是右值是不能取地址的,但是给右值取别名后,会导致右值被存储到特定位置,且可
以取到该位置的地址,也就是说例如:不能取字面量10的地址,但是rr1引用后,可以对rr1取地
址,也可以修改rr1。如果不想rr1被修改,可以用const int&& rr1 去引用,是不是感觉很神奇,
这个了解一下实际中右值引用的使用场景并不在于此,这个特性也不重要。

int main()
{
    double x = 1.1, y = 2.2;
    int&& rr1 = 10;
    const double&& rr2 = x + y;
    rr1 = 20;
    rr2 = 5.5;  // 报错
    return 0;
}

给rr2赋值结果 

屏蔽rr2赋值结果

1.2 左值引用与右值引用比较


左值引用总结:

  • 1. 左值引用只能引用左值,不能引用右值。
  • 2. 但是const左值引用既可引用左值,也可引用右值。
int main()
{
   // 左值引用只能引用左值,不能引用右值。
   int a = 10;
   int& ra1 = a;   // ra为a的别名
   //int& ra2 = 10;   // 编译失败,因为10是右值
   // const左值引用既可引用左值,也可引用右值。
   const int& ra3 = 10;
   const int& ra4 = a;
   return 0;
}

右值引用总结:

  • 1. 右值引用只能右值,不能引用左值。
  • 2. 但是右值引用可以move以后的左值。
int main()
{
    // 右值引用只能右值,不能引用左值。
    int&& r1 = 10;
    // error C2440: “初始化”: 无法从“int”转换为“int &&”
    // message : 无法将左值绑定到右值引用
    int a = 10;
    int&& r2 = a;
    // 右值引用可以引用move以后的左值
    int&& r3 = std::move(a);
    return 0;
}

1.3 右值引用使用场景和意义


前面我们可以看到左值引用既可以引用左值和又可以引用右值,那为什么C++11还要提出右值引用呢?是不是化蛇添足呢?下面我们来看看左值引用的短板,右值引用是如何补齐这个短板的!

我们通过自己实现的string类来验证!!!

namespace lin
{
	class string
	{
	public:
		typedef char* iterator;
		iterator begin()
		{
			return _str;
		}

		iterator end()
		{
			return _str + _size;
		}

		string(const char* str = "")
			:_size(strlen(str))
			, _capacity(_size)
		{
			cout << "string(char* str) -- 构造" << endl;

			_str = new char[_capacity + 1];
			strcpy(_str, str);
		}

		// s1.swap(s2)
		void swap(string& s)
		{
			::swap(_str, s._str);
			::swap(_size, s._size);
			::swap(_capacity, s._capacity);
		}

		// 拷贝构造
		// 左值
		string(const string& s)
			:_str(nullptr)
		{
			cout << "string(const string& s) -- 深拷贝" << endl;

			_str = new char[s._capacity + 1];
			strcpy(_str, s._str);
			_size = s._size;
			_capacity = s._capacity;
		}

		// 赋值重载
		string& operator=(const string& s)
		{
			cout << "string& operator=(string s) -- 深拷贝" << endl;
			char* tmp = new char[s._capacity + 1];
			strcpy(tmp, s._str);

			delete[] _str;
			_str = tmp;
			_size = s._size;
			_capacity = s._capacity;

			return *this;
		}

		~string()
		{
			delete[] _str;
			_str = nullptr;
		}

		char& operator[](size_t pos)
		{
			assert(pos < _size);
			return _str[pos];
		}

		void reserve(size_t n)
		{
			if (n > _capacity)
			{
				char* tmp = new char[n + 1];
				strcpy(tmp, _str);
				delete[] _str;
				_str = tmp;

				_capacity = n;
			}
		}

		void push_back(char ch)
		{
			if (_size >= _capacity)
			{
				size_t newcapacity = _capacity == 0 ? 4 : _capacity * 2;
				reserve(newcapacity);
			}

			_str[_size] = ch;
			++_size;
			_str[_size] = '\0';
		}

		//string operator+=(char ch)
		string& operator+=(char ch)
		{
			push_back(ch);
			return *this;
		}

		const char* c_str() const
		{
			return _str;
		}
	private:
		char* _str;
		size_t _size;
		size_t _capacity; // 不包含最后做标识的\0
	};
	lin::string to_string(int value)
	{
		bool flag = true;
		if (value < 0)
		{
			flag = false;
			value = 0 - value;
		}

		lin::string str;
		while (value > 0)
		{
			int x = value % 10;
			value /= 10;
			str += ('0' + x);
		}
		if (flag == false)
		{
			str += '-';
		}
		std::reverse(str.begin(), str.end());

		return str;
	}
}

左值引用的使用场景:

做参数和做返回值都可以提高效率。

void func1(lin::string s)
{}
void func2(const lin::string& s)
{}
int main()
{
	lin::string s1("hello world");
	// func1和func2的调用我们可以看到左值引用做参数减少了拷贝,提高效率的使用场景和价值
	func1(s1);
	func2(s1);
	// string operator+=(char ch) 传值返回存在深拷贝
	// string& operator+=(char ch) 传左值引用没有拷贝提高了效率
	s1 += '!';
	return 0;
}

左值引用的短板:

但是当函数返回对象是一个局部变量,出了函数作用域就不存在了,就不能使用左值引用返回,只能传值返回。例如:lin::string to_string(int value)函数中可以看到,这里只能使用传值返回,传值返回会导致至少1次拷贝构造(如果是一些旧一点的编译器可能是两次拷贝构造)。

代码演示

int main()
{
	// 只调用了一次C字符串构造,编译器直接优化结果,VS2022环境
	lin::string ret = lin::to_string(1234);
	cout << ret.c_str() << endl;
	// 调用两次C字符串构造 + 赋值重载
	lin::string ret1;
	ret1 = lin::to_string(1478);

	return 0;
}

测试结果 

右值引用和移动语义解决上述问题:

在lin::string中增加移动构造,移动构造本质是将参数右值的资源窃取过来,占位已有,那么就不用做深拷贝了,所以它叫做移动构造,就是窃取别人的资源来构造自己。

// 移动构造
// 右值(将亡值)
string(string&& s)
	:_str(nullptr)
{
	cout << "string(string&& s) -- 移动构造" << endl;
	swap(s);
}

不仅仅有移动构造,还有移动赋值:

在lin::string类中增加移动赋值函数,再去调用lin::to_string(1234),不过这次是将
lin::to_string(1234)返回的右值对象赋值给ret1对象,这时调用的是移动构造。

1.4 右值引用引用左值及其一些更深入的使用场景分析


按照语法,右值引用只能引用右值,但右值引用一定不能引用左值吗?因为:有些场景下,可能真的需要用右值去引用左值实现移动语义。当需要用右值引用引用一个左值时,可以通过move函数将左值转化为右值。C++11中,std::move()函数位于 #include<utility> 头文件中,该函数名字具有迷惑性,它并不搬移任何东西,唯一的功能就是将一个左值强制转化为右值引用,然后实现移动语义。

代码演示

int main()
{
	// 构造
	lin::string s1("111111111111111");
	// 赋值重载
	lin::string s2 = s1;
	// 移动构造
	lin::string s3 = move(s1);
	return 0;
}

监视窗口 

测试结果 

注意:使用move()函数会将原来对象的数据转移走,请谨慎使用。 

1.5 完美转发

模板中的 && 万能引用

代码演示

// 模板中的&&不代表右值引用,而是万能引用,其既能接收左值又能接收右值。
// 模板的万能引用只是提供了能够接收同时接收左值引用和右值引用的能力,
void Fun(int& x) { cout << "左值引用" << endl; }
void Fun(const int& x) { cout << "const 左值引用" << endl; }

void Fun(int&& x) { cout << "右值引用" << endl; }
void Fun(const int&& x) { cout << "const 右值引用" << endl; }

// 实参传左值,就推成左值引用
// 实参传右值,就推成右值引用
template<typename T>
void PerfectForward(T&& t)
{
	Fun(t);// 直接使用参数传递,全部都是左值
}

// 实现传什么参数,调用对应的函数
int main()
{
	PerfectForward(10);           

	int a;
	PerfectForward(a);            
	PerfectForward(std::move(a)); 

	const int b = 8;
	PerfectForward(b);		      
	PerfectForward(std::move(b)); 
	return 0;
}

测试结果 

引用类型的唯一作用就是限制了接收的类型,后续使用中都退化成了左值,因此如果直接使用参数传递,得到的结果就是全部打印左值引用。 

 std::forward 完美转发在传参的过程中保留对象原生类型属性。

代码演示

将PerFectForward函数的函数体加上完美转发,则可以解决上面问题。

template<typename T>
void PerfectForward(T&& t)
{
	Fun(forward<T>(t));// 使用完美转发,传什么值调用什么函数
}

测试结果

使用完美转发可以达到传什么参数,调用什么函数。 

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

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

相关文章

wpf prism 《1》、区域 、模块化

安装prism.DryIoc 修改app.xaml <prism:PrismApplication x:Class"WpfApp3.App"xmlns"http://schemas.microsoft.com/winfx/2006/xaml/presentation"xmlns:x"http://schemas.microsoft.com/winfx/2006/xaml"xmlns:local"clr-namespace:W…

算法的学习笔记—最小的 K 个数(牛客JZ40)

&#x1f600;前言 在编程面试中&#xff0c;找出一个数组中最小的K个数是一个常见的问题。虽然看似简单&#xff0c;但要在高效性方面有所保证却并不容易。本文将介绍两种有效解决该问题的算法&#xff1a;基于堆的解法和快速选择算法。我们将详细讲解它们的实现方式、时间复杂…

基于3U PXIe总线架构的4路250MSPS中频信号采集处理存储系统

标准3U PXIE规格1个FMC AD子卡&#xff0c;支持4通道250MSPS 16位 AD采集板载 1 片 XC7K325T FPGA处理器支持x8 PCIE主机接口&#xff0c;系统带宽4GByte/s支持板间同步支持多系统同步 基于PXI Express总线架构的中频信号采集处理存储系统&#xff0c;该系统由1块PXIe规格FMC载…

自注意力,多头注意力,交叉注意力与因果注意力复习

“自注意力机制中有三个重要的输入矩阵&#xff1a;查询矩阵Q&#xff08;query&#xff09;、键矩阵K&#xff08;key&#xff09;和值矩阵V&#xff08;value&#xff09;。这三个矩阵都是由输入序列经过不同的线性变换得到的。然后&#xff0c;查询矩阵Q与键矩阵K的乘积经过…

ComfyUI - 在 ComfyUI 中配置 Flux + LoRA 的组合流程优化图像属性

欢迎关注我的CSDN&#xff1a;https://spike.blog.csdn.net/ 本文地址&#xff1a;https://spike.blog.csdn.net/article/details/141638928 免责声明&#xff1a;本文来源于个人知识与公开资料&#xff0c;仅用于学术交流&#xff0c;欢迎讨论&#xff0c;不支持转载。 在 Dif…

【计算机组成原理】五、中央处理器:2.数据通路、控制器(单总线结构、专用数据通路、硬布线控制器、微程序控制器)

3.数据通路 文章目录 3.数据通路3.1单总线结构3.2专用数据通路 4.控制器4.1硬布线控制器4.1.1基本结构4.1.2设计步骤微操作总结1&#xff09;分析每个阶段的微操作序列2&#xff09;安排微操作时序的原则3&#xff09;电路设计 4.1.3特点 4.2微程序控制器微指令包含关系4.2.1基…

大模型全量微调和 LoRA 微调:一看就懂_lora微调

在模型微调领域&#xff0c;全量微调和LoRA微调是我们经常听到的技术术语。 首先&#xff0c;我们需要了解什么是模型微调。模型微调本质上是因为有时我们发现模型在某个方面的性能不足。因此&#xff0c;我们希望通过一些训练方法来更新模型&#xff0c;使更新后的模型在某些…

达梦到达梦(dm-dm)创建dblink,报错:dblink连接丢失

原因 1&#xff1a;参考如下链接配置完dmmal.ini文件后&#xff0c;需要重启服务。 如果文件配置错误&#xff0c;重启服务会有很明显的报错。 https://eco.dameng.com/document/dm/zh-cn/sql-dev/practice-dblink.html [mal_inst1] mal_inst_name DMSERVER mal_host …

飞翔的马鞍 <收纳 No.2> 菱与欣桐画展8月18~31日在798举办

2024年8月18日下午三点&#xff0c;“菱与欣桐 收纳No.2”双人展开幕式在北京市朝阳区798国际艺术交流中心展厅举行。 艺术是永恒的&#xff0c;艺术家的生命力通过作品得以延续。开幕式上艺术家张菱分享了与女儿李欣桐在创作中的点滴记忆&#xff0c;以及她对艺术的执着与热爱…

Django框架自动化测试

【图书介绍】《Django 5企业级Web应用开发实战&#xff08;视频教学版&#xff09;》_django 5企业级web应用开发实战(视频教学版)-CSDN博客 《Django 5企业级Web应用开发实战&#xff08;视频教学版&#xff09;》(王金柱)【摘要 书评 试读】- 京东图书 (jd.com) 本节介绍关…

2024.8.27(Dokerfile的应用、私有仓库)

一、Dockerfile应用 1、通过dockerfile创建⼀个在启动容器时&#xff0c;就可以启动httpd服务的镜像 [rootdocker ~]# mkdir httpd0 [rootdocker ~]# cd httpd0 [rootdocker httpd0]# vim abc.sh [rootdocker httpd0]# ls abc.sh [rootdocker httpd0]# echo " httpd serv…

C语言重难点总结(2)-指针操作与结构体、动态内存

本节学习内容 1.指针操作与结构体 2.函数&#xff08;递归函数&#xff09; 3.动态内存 一、指针操作与结构体 指针可以作为结构体的内部成员使用&#xff0c;也可以使用结构体指针操作结构体空间。 二、函数 1.指针函数 &#xff08;1&#xff09;什么是指针函数&#x…

企业家必看的十种让利的商业模式解析!

在当今的商业领域&#xff0c;众多创业者正面临前所未有的挑战。市场竞争激烈&#xff0c;价格战频繁&#xff0c;吸引投资者和推广产品都变得异常艰难。然而&#xff0c;问题的关键在于对“人心”的洞察。人们天生追求利益&#xff0c;因此&#xff0c;掌握如何点燃市场需求和…

运维有必要学编程吗?应该学哪种编程语言?

在以往的观念中&#xff0c;运维的工作内容是不涉及编程、开发的&#xff0c;因此以前也没用学编程的需求。然而随着互联网的发展和变化&#xff0c;现如今&#xff0c;运维也需要开始接触开发&#xff0c;接触编程了。 当然有很多运维朋友&#xff0c;本身是不认可的。可&…

IDE之vscode:连接远程服务器代码(亲测OK),与pycharm链接服务器做对比(亲自使用过了)。

文章目录 前言一、链接服务器vscode和pycharm的对比1、pycharm2、vscode3、总结 二、VS Code的安装与下载三、链接远程服务器1、安装远程插件&#xff1a;Remote-SSH2、写ssh配置文件3、链接服务器4、登录成功&#xff0c;打开文件夹 前言 大模型开发肯定要在服务器了&#xff…

使用PyTorch AlexNet预训练模型对新数据集进行训练及预测

在 https://blog.csdn.net/fengbingchun/article/details/112709281 中介绍了AlexNet网络&#xff0c;这里使用PyTorch中提供的AlexNet预训练模型对新数据集进行训练&#xff0c;然后使用生成的模型进行预测。主要包括三部分&#xff1a;新数据集自动拆分、训练、预测 1.新数据…

C++竞赛初阶L1-14-第六单元-数组(31~33课)543: T456473 年龄与疾病

题目内容 某医院进行一项研究,想知道某项疾病是否与年龄有关。因此对以往的诊断记录进行整理,统计 0-18 、 19-35 、 36-60、 61 及以上这四个年龄段的患者人数占总患者人数的比例。 输入格式 输入共 2 行。 第一行包含一个整数 N(0<n≤100),表示总患者人数。 第二…

OpenCV绘图函数(1)绘制带箭头的直线函数arrowedLine()的使用

操作系统&#xff1a;ubuntu22.04 OpenCV版本&#xff1a;OpenCV4.9 IDE:Visual Studio Code 编程语言&#xff1a;C11 算法描述 绘制一个从第一个点指向第二个点的箭头线段。 cv::arrowedLine 函数在图像中绘制一个从 pt1 到 pt2 的箭头。另见 line 函数。 函数原型 void c…

Android Auto推出全新Google助手设计

智能手机与汽车的无缝整合已成为现代驾驶的重要组成部分&#xff0c;而 Android Auto 一直在这一领域处于领先地位。谷歌通过不断推出新功能和更新&#xff0c;体现了其致力于提升 Android Auto 体验的决心。最近&#xff0c;Android Auto 引入了 Google助手的全新设计。 当系…

vue3 使用vue-masonry加载更多,重新渲染

在使用 van-list做上拉加载更多&#xff0c;加载下一页的时候&#xff0c;会出现瀑布图重叠&#xff0c;原因是布局没有重新更新&#xff0c;所以需要 调用 vue-masonry更新布局的方法。 看了源码才知道可以这样用&#xff0c;api都没写&#xff0c;隐藏太深了。。。 vue3中通…