C++11中篇

news2025/7/19 16:51:10

文章目录

  • 1. 右值引用和移动语义
    • 1.1 左值引用和右值引用
    • 1.2 左值引用与右值引用比较
    • 1.3 右值引用使用场景和意义
    • 1.5 完美转发
  • 2. default 和delete

1. 右值引用和移动语义

1.1 左值引用和右值引用

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

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

    • 左值是一个表示数据的表达式(如变量名或解引用的指针),我们可以获取它的地址+可以对它赋值,左值可以出现赋值符号的左边,右值不能出现在赋值符号左边。 定义时const修饰符后的左值,不能给他赋值,但是可以取它的地址。左值引用就是给左值的引用,给左值取别名。
int main()
{
	vector<int > v = { 1, 2, 3, 4, 5, 6 };
	for (auto e : v)
	{
		cout << e << " ";
	}
	cout << endl;

	for (auto it = v.begin(); it != v.end(); ++it)
	{
		cout << *it << " ";
	}
}
  • 什么是右值?什么是右值引用?
    • 右值也是一个表示数据的表达式,如:字面常量、表达式返回值,函数返回值(这个不能是左值引用返回)等等,右值可以出现在赋值符号的右边,但是不能出现出现在赋值符号的左边,右值不能取地址。 右值引用就是对右值的引用,给右值取别名。
int main()
{
	double x = 1.1, y = 2.2;
	// 以下几个都是常见的右值
	10;
	x + y;
	fmin(x, y);
	// 以下几个都是对右值的右值引用
	int&& rr1 = 10;
	double&& rr2 = x + y;
	double&& rr3 = fmin(x, y);
	// 这里编译会报错:error C2106: “=”: 左操作数必须为左值
	10 = 1;
	x + y = 1;
	fmin(x, y) = 1;
	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;
}

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

  • 左值引用总结:
      1. 左值引用只能引用左值,不能引用右值。
      1. 但是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. 右值引用只能右值,不能引用左值。
      1. 但是右值引用可以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 右值引用使用场景和意义

  • 下面我们来看看左值引用的短板,右值引用是如何补齐这个短板的!
namespace Ding
{
	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;
			string tmp(s._str);
			swap(tmp);
		}
		// 赋值重载
		string& operator=(const string& s)
		{
			cout << "string& operator=(string s) -- 深拷贝" << endl;
			string tmp(s);
			swap(tmp);
			return *this;
		}
		// 移动构造
		string(string&& s)
			:_str(nullptr)
			, _size(0)
			, _capacity(0)
		{
			cout << "string(string&& s) -- 移动语义" << endl;
			swap(s);
		}
		// 移动赋值
		string& operator=(string&& s)
		{
			cout << "string& operator=(string&& s) -- 移动语义" << endl;
			swap(s);
			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
	};

}

大家发现发现多了一个移到构造和移动赋值;从此章以后默认构造函数变成八个了~

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

在这里插入图片描述

  • 左值引用的短板:

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

  • 没有移动构造和移动赋值,我们发现是两次拷贝构造
    在这里插入图片描述
namespace Ding
{
	Ding::string to_string(int value)
	{
		bool flag = true;
		if (value < 0)
		{
			flag = false;
			value = 0 - value;
		}
		Ding::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;
	}
}
int main()
{
	// 在bit::string to_string(int value)函数中可以看到,这里
	// 只能使用传值返回,传值返回会导致至少1次拷贝构造(如果是一些旧一点的编译器可能是两次拷贝构造)。
	Ding::string ret1 = Ding::to_string(1234);
	Ding::string ret2 = Ding::to_string(-1234);
	return 0;
}
  • 右值引用和移动语义解决上述问题:
    在Ding::string中增加移动构造,移动构造本质是将参数右值的资源窃取过来,占位已有,那么就不用做深拷贝了,所以它叫做移动构造,就是窃取别人的资源来构造自己。

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

在这里插入图片描述

  • 再运行上面Ding::to_string的两个调用,我们会发现,这里没有调用深拷贝的拷贝构造,而是调用了移动构造,移动构造中没有新开空间,拷贝数据,所以效率提高了。
  • 这里运行后,我们看到调用了一次移动构造和一次移动赋值。因为如果是用一个已经存在的对象
    接收,编译器就没办法优化了。Ding::to_string函数中会先用str生成构造生成一个临时对象,但是
    我们可以看到,编译器很聪明的在这里把str识别成了右值,调用了移动构造。然后在把这个临时
    对象做为Ding::to_string函数调用的返回值赋值给ret1,这里调用的移动赋值。

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); // const 左值
	PerfectForward(std::move(b)); // const 右值
	return 0;
}

在这里插入图片描述
我们发现,最终全是左值引用,我们把这种现象称之为折叠(&&->&)

  • std::forward 完美转发在传参的过程中保留对象原生类型属性
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; }

// std::forward<T>(t)在传参的过程中保持了t的原生类型属性。
template<typename T>
void PerfectForward(T&& t)
{
	Fun(std::forward<T>(t));
}

int main()
{
	PerfectForward(10); // 右值
	int a;
	PerfectForward(a); // 左值
	PerfectForward(std::move(a)); // 右值
	const int b = 8;
	PerfectForward(b); // const 左值
	PerfectForward(std::move(b)); // const 右值
	return 0;
}

在这里插入图片描述

  • 总结
    只有模板可以使用完美转发。

2. default 和delete

  • 强制生成默认函数的关键字default:
    • C++11可以让你更好的控制要使用的默认函数。假设你要使用某个默认的函数,但是因为一些原因这个函数没有默认生成。比如:我们提供了拷贝构造,就不会生成移动构造了,那么我们可以使用default关键字显示指定移动构造生成。
class Person
{
public:
	Person(const char* name = "", int age = 0)
		:_name(name)
		, _age(age)
	{}
	Person(const Person& p)
		:_name(p._name)
		, _age(p._age)
	{}
		Person(Person && p) = default;
private:
	Ding::string _name;
	int _age;
};
int main()
{
	Person s1;
	Person s2 = s1;
	Person s3 = std::move(s1);
	return 0;
}
  • 禁止生成默认函数的关键字delete:
    • 如果能想要限制某些默认函数的生成,在C++98中,是该函数设置成private,并且只声明补丁已,这样只要其他人想要调用就会报错。在C++11中更简单,只需在该函数声明加上=delete即可,该语法指示编译器不生成对应函数的默认版本,称=delete修饰的函数为删除函数。
class Person
{
public:
	Person(const char* name = "", int age = 0)
		:_name(name)
		, _age(age)
	{}
	Person(const Person& p) = delete;
private:
	Ding::string _name;
	int _age;
};
int main()
{
	Person s1;
	Person s2 = s1;
	Person s3 = std::move(s1);
	return 0;
}
  • 继承和多态中的final与override关键字
    • 这个我们在继承和多态章节已经进行了详细讲解这里就不再细讲。final,不让派生类继承;override,检查是否重写成功。
      _c++多态_轻松入门
      c++中的继承

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

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

相关文章

Java_抽象类

目录 1.抽象类语法 2.抽象类特性 3.抽象类的作用 抽象类概念&#xff1a;在面向对象的概念中&#xff0c;所有的对象都是通过类来描绘的&#xff0c;但是反过来&#xff0c;并不是所有的类都是用来描绘对象的&#xff0c;如果 一个类中没有包含足够的信息来描绘一个具体的对…

RabbitMQ远程访问

文章目录1. 创建用户2. 关闭防火墙开启端口3. 测试远程访问参考链接背景问题 guest这个用户只能使用localhost登录&#xff0c;而不能使用IP地址登录&#xff1a; amqp://guest:guest127.0.0.1:5672/rabbitVHost 远程无法连接amqp://guest:guest10.10.10.128:5672/rabbitVHost …

Elasticsearch simple(2)ElasticSearch windows本地环境搭建(V8.5.1)

切记安装Kibana的版本要和所安装的elasticsearch版本号完全一致。 这里&#xff1a; elasticsearch-8.5.1 kibana-8.5.1 elasticsearch默认端口为9200&#xff1b; Kibana默认端口为5601。 1 elasticsearch的下载和安装 下载官网&#xff1a;https://www.elastic.co/cn/down…

ArcGIS影像TIF拼接

ArcGIS影像TIF拼接 我们要使用的工具是Mosaic To New Raster 这个工具通过下面的路径找到&#xff1a; &#xff08;我用的是ArcGIS 10.8&#xff09; 输入影像 在这里输入多张待拼接的TIF 输出文件和路径 Output Loacation是输出文件的路径&#xff0c;可以是文件夹名称或…

MATLAB | MATLAB海洋、气象数据colormap配色补充包(NCL color tables)

众所周知&#xff0c;海洋、气象、水文等一系列学科绘制图像时&#xff0c;因很多时候涉及要将陆地海洋分开或者应对一些非常特殊的情况&#xff0c;因此有很多非常特别的colormap&#xff0c;我之前虽然做过一些常用colormap的补充包但是依旧是无法完全应对各种特殊的colormap…

C语言-基础

C-基础参考变量/常量左值/右值&#xff1f;&#xff1f;&#xff1f;字面量/常量变量作用域全局变量与局部变量作用域扩展 python数据与地址存储类autoregisterstaticextern数据类型基本类型整数类型进制数原码/补码/反码int 与 char数值溢出浮点类型字符型字符与整数字符与进制…

Selenium隐藏浏览器和元素截屏实践

文章目录隐藏浏览器介绍示例代码浏览器截图介绍示例代码对整个浏览器页面进行截屏指定特定HTML元素进行截屏隐藏浏览器 介绍 在使用Selenium采集网页数据时&#xff0c;需要不断地调用浏览器。实际上&#xff0c;通过对Selenium的设置&#xff0c;可以达到隐藏浏览器的效果。…

堆 (带图详解)

文章目录1.堆的基本概念1. 概念2.性质1.必须为完全二叉树2.满足大堆/小堆成立的条件3.存储方式1.逻辑结构2.物理结构4. 孩子与父亲之间下标的关系2.堆的基本实现1.push——插入1.代码2. 情况分析情况1情况23. 向上调整算法1.过程分析2. 临界条件的判断2. pop—— 删除1.代码2. …

[附源码]计算机毕业设计JAVA火车票订票管理系统

[附源码]计算机毕业设计JAVA火车票订票管理系统 项目运行 环境配置&#xff1a; Jdk1.8 Tomcat7.0 Mysql HBuilderX&#xff08;Webstorm也行&#xff09; Eclispe&#xff08;IntelliJ IDEA,Eclispe,MyEclispe,Sts都支持&#xff09;。 项目技术&#xff1a; SSM myba…

【HDU No. 4006】 第k 大的数 The kth great number

【HDU No. 4006】 第k 大的数 The kth great number 杭电OJ 题目地址 【题意】 小明和小宝正在玩数字游戏。游戏有n轮&#xff0c;小明在每轮中都可以写一个数&#xff0c;或者问小宝第k 大的数是什么&#xff08;第k 大的数指有k -1个数比它大&#xff09;。 游戏格式为&am…

运维开发实践 - Docker - 容器实现原理

1.Docker容器是什么 按照Docker官网&#xff0c;容器是运行在宿主机上的一个进程&#xff0c;但与宿主机上的其他进程相隔离&#xff1b; 2.容器实现原理 这种隔离机制使用了内核中的namespace和cgroups功能&#xff1b; 2.1.Linux namespace Linux通过将系统的资源放置在…

Redis数据类型之string

文章目录stringⅠ. 增删查改Ⅱ. 加减操作Ⅲ. 数据生命周期Ⅳ. 业务场景 - 高频数据Ⅴ. 注意事项提示&#xff1a;以下是本篇文章正文内容&#xff0c;Redis系列学习将会持续更新 string ● 存储的数据&#xff1a;单个数据&#xff0c;最简单的数据存储类型&#xff0c;也是最常…

Paket在Linux下使用问题

1. 系统软件版本 Linux系统&#xff1a;Debian 10&#xff0c;需安装mono-devel Paket版本&#xff1a;7.1.5 关于Paket的介绍&#xff1a;https://github.com/fsprojects/Paket Paket的发布版本&#xff1a;https://github.com/fsprojects/Paket/releases 2. 使用方法 将…

原来背后都是商业利益,看到网易和暴雪的解约之后,原来是要定以后的KPI,坐地起价,但是一个时代已经结束了,都留在了记忆之中

1&#xff0c;大瓜新闻&#xff0c;2023年1月暴雪游戏中国将不会续约&#xff1f;&#xff1f; 2&#xff0c;原因是主要坐地起价&#xff0c;提高分成设置KPI 还好网易有自研游戏&#xff0c;估计早知道会有现在这样的情况。 提前做好了准备。还记得有个公司叫 九城吗&#x…

Linux操作系统~带你理解文件系统与软硬链接

目录 1.C语言中的FILE和文件描述符对应的file 2.Linux的EXT系列的文件系统 &#xff08;1&#xff09;.block group中六个部分的内容 inode索引结点相关 Q&#xff1a;这两个inode有什么不同&#xff1f; &#xff08;2&#xff09;.一个文件的inode和对应的block如何关联…

off-by-one (b00ks)

前言 个人简略记录&#xff0c;过程不详细 gdb开始调试 vmmap查看程序基地址为 0x555555400000 继续运行&#xff0c;输入auth search hollk关键字&#xff0c;auth存放的地址可以找出为&#xff1a;0x555555602040 圈起来的是溢出的 \x00 创建两个books 因为图书的结构体指…

数据库以及数据库常用概念、ER模型相关概念

目录 1.我们为什么要学习数据库&#xff1f; 2.数据库的相关概念&#xff1a; 2.关系型数据库和非关系型数据库的区别 A.关系型数据库&#xff1a; B.非关系型数据库&#xff1a; C.非关系型数据库的相关类别有以下几种&#xff1a; 3.关系型数据库设计规则 4.表、记录、…

华为数据中心VS技术理论讲解

目录 VS之间的关系 VS的配置管理权限 VS之间、VS与外部通信 VS资源分配 VS与其它技术的联合部署 VS的创建和删除 VS&#xff08;Virtual System&#xff09;虚拟系统是指将一台物理设备虚拟成多个相互隔离的逻辑设备 逻辑设备之间软硬件隔离&#xff0c;互不影响&#xff…

立体式校验保护,让你的系统避免 90% 以上的 bug

1. 概览 在实际开发过程中&#xff0c;数据校验是最为重要的一环&#xff0c;问题数据一旦进入系统&#xff0c;将对系统造成不可估量的损失。轻者&#xff0c;查询时触发空指针异常&#xff0c;导致整个页面不可用&#xff1b;重者&#xff0c;业务逻辑错误&#xff0c;造成流…

为了买个硬盘,我专门写了篇笔记

文章目录SSD是固态硬盘的一些参数插槽接口总线类型传输协议插槽接口/总线/协议关联参考连接HDD是机械硬盘&#xff1b;SSD是固态硬盘。 SSD是固态硬盘的一些参数 插槽接口 外观不同而已&#xff1b;插槽接口不影响传输速度&#xff0c;但是插槽接口不适配的话是肯定插不到电脑…