c++11(二)

news2024/11/16 14:55:27

一、右值引用

1、区分左值和右值

语法定义,左值可以取地址,右值无法取地址(右值肯定有地址,但是为了和左值区分,语法上不让取地址)

左值:一个表示数据的表达式(变量名或解引用指针)

右值:一个表示数据的表达式(字面常量,临时对象,匿名对象)

下图的右值无法取地址

注意:

左值引用不能给右值取别名,本质右值无法改变,所以 const 左值引用可以。

const int& a = 10;

右值引用无法给左值取别名,但是move之后的左值可以,相当于把左值变成右值。

int&& b = move(10);

2、引用解决的问题

引用的意义减少拷贝,提高效率

(1)左值引用解决的问题和遗留的问题

引用传参防止拷贝,传引用返回减少拷贝

但如果是局部对象传引用返回,由于局部对象出函数销毁,导致必须经历深拷贝构造新的临时对象返回,最后没有达到引用返回的目的。

(2)右值引用解决左值引用问题的原理

右值分类和解决原理

右值分为两大类:纯右值(内置类型),将亡值(自定义类型,包括生命周期一行的匿名对象和生命周期是表达式的临时对象

内置类型不作考虑,因为本身拷贝代价就极低。主要考虑自定义类型的拷贝构造和赋值构造能否基于右值特性进行优化。

我们发现不论是匿名对象还是临时对象都有一个特点,他们有我们构造对象所需要的资源,但是占有资源的主体生命周期短,是临时的很快就销毁(所以叫将亡值),所以此时我们可以直接交换资源,把我新创建的对象指向的资源和将亡值指向的资源进行交换,这样代价就比左值返回代价小的多。

上文提到可以通过改造左值引用的拷贝构造和赋值构造来解决问题。用右值特性改造之后的函数叫移动构造和移动赋值,听名字移动成本就很低。

举例
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& ss)
	{
		::swap(_str, ss._str);
		::swap(_size, ss._size);
		::swap(_capacity, ss._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(string&& s)
		:_str(nullptr)
	{
		cout << "string(string&& s) -- 移动构造" << endl;
		swap(s);
	}

	// 赋值重载 左值
	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& operator=(string&& s)
	{
		cout << "string(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
};

bit::string to_string(int value)
{
	bool flag = true;
	if (value < 0)
	{
		flag = false;
		value = 0 - value;
	}

	bit::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 move(str);
	return str;
}

这是一个自己写的string类,里面就有加上移动构造和移动赋值。

移动构造解析

首先要用右值引用来接受参数,让编译器知道如果有右值要构造用移动构造函数。

内部让 this 指针指向的对象和将亡值进行交换资源就达到了构造新对象的目的。

复制构造解析

移动赋值也是如此,你将亡值里面有我对象所需要的资源,我就直接交换资源,函数结束后不久将亡值就会带着交换后的没用资源一起被释放。

代码案例
int main()
{
	//bit::string ret1;
	//ret1 = bit::to_string(1234);
	bit::string ret1 = bit::to_string(1234);
	cout << ret1.c_str() << endl;

	return 0;
}

没有移动构造和移动赋值:

可以看到需要两次深拷贝,代价很大。

有移动构造和移动赋值:

还可以再优化:

内存级理解:

int main()
{
	bit::string ret1;
	ret1 = bit::to_string(1234);
	//bit::string ret1 = bit::to_string(1234);
	//cout << ret1.c_str() << endl;

	return 0;
}

没有移动构造和移动赋值:

依然两次深拷贝。

有移动构造和移动赋值:

内存级理解:

3、右值引用总结

首先内置类型没有移动语义,因为他们的创建代价极低。

右值引用针对的是深拷贝的自定义类型,因为如果只是单纯的左值引用无法解决深拷贝。

右值引用本身存在效率问题,只是函数匹配时编译器会识别到右值,此时右值是一个将亡值,又具有我们需要的资源,此时的移动构造和移动赋值可以直接转移资源提高效率。

其次右值引用本身是左值,只有这样移动构造和移动赋值才能交换资源(为了逻辑自洽)

所以让右值变成左值的方法两种:右值引用,强转。

二、完美转发

1、函数模板中的万能引用

template<typename T>
void PerfectForward(T&& t)
{}

参数 T&& t,不是代表右值引用,因为在模板中,所以是万能引用,即实参传左值推算成左值引用,实参传右值推算成右值引用。

所以我们写一个函数对万能引用进行测试。

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;
}

理论上我们经过万能引用会得到main函数中注释的结果。

但是要知道右值被万能引用函数捕捉后的右值引用是左值,这样做导致运行结果全是左值。

所以我们该如何解决函数传参时上述的属性退化问题呢?

2、完美转发理解

要想在参数传递过程中保持其右值属性,就需要使用 forward 函数,也就是完美转发。

forward 是一个带有参数模板的函数,主要在传参时使用: 如果参数原本是右值,但在右值引用后失去了右值属性,使用 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; 
}

//forward<T>(t)在传参过程中保持t的原生类型属性,叫做完美转发


template<typename T>
void PerfectForward(T&& t)
{
	//Fun(t);
	Fun(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;
}

所以此时的运行结果就i是我们想要的了。

3、完美转发使用场景

完美转发在实际开发中会经常用到,前面说过,在 C++11 之后,所有的类都可以新增一个移动构造以规避无意义的低效拷贝行为,并且由于大部分类中会涉及模板的使用,保持右值属性就是一个必备的技巧,如果没有完美转发,那么移动构造顶多也就减少了一次深拷贝

通俗来说就是不是每一层函数的传参都能保证参数的右值属性,这是就有必要用到完美转发。

三、lambda表达式

1、仿函数

借助类和 operator() 函数重载来创建函数对象,用于各种特定函数传参。

struct cmpLess
{
	bool operator()(int n1, int n2)
	{
		return n1 < n2;
	}
};

struct cmpGreater
{
	bool operator()(int n1, int n2)
	{
		return n1 > n2;
	}
};

int main()
{
	vector<int> arr = { 8,5,6,7,3,1,1,3 };

	sort(arr.begin(), arr.end(), cmpLess()); // 升序

	sort(arr.begin(), arr.end(), cmpGreater()); // 降序

	return 0;
}

例如sort()函数会要求传递一个比较函数用于排序,可以使用默认或库里面自带的仿函数,但是也可以自己实现一个仿函数来实现特定的排序需求。这时我们就要会写仿函数来实现。

不仅是sort函数,优先级队列也需要自己写仿函数传参。

2、lambda表达式作用

上面的仿函数写起来不免有点麻烦,lamdba表达式功能与仿函数类似,但是更简洁。

landba表达式会生成匿名函数对象,在编译时会生成仿函数,即调用opreator(),这些时编译器干的活。

3、lamdba表达式语法

格式:[capture-list] (parameters) mutable -> return-type { statement }

1、[capture-list] : 捕捉列表,该列表总是出现在lambda函数的开始位置,编译器根据[]来 判断接下来的代码是否为lambda函数,捕捉列表能够捕捉上下文中的变量供lambda 函数使用。

捕捉类型

[value]:传值捕捉value

[&value]:传引用捕捉value

[=]:传值捕捉所有变量,包括this指针

[&]:传引用捕捉所有变量,包括this指针

[&,value1,value2]:混合捕捉,除value1,value2以外的变量以传引用来捕捉

2、(parameters):参数列表。与普通函数的参数列表一致,如果不需要参数传递,则可以 连同()一起省略

3、mutable:默认情况下,lambda函数总是一个const函数,mutable可以取消其常量 性。使用该修饰符时,参数列表不可省略(即使参数为空)。

4、->returntype:返回值类型。用追踪返回类型形式声明函数的返回值类型,没有返回 值时此部分可省略。返回值类型明确情况下,也可省略,由编译器对返回类型进行推导。

5、{statement}:函数体。在该函数体内,除了可以使用其参数外,还可以使用所有捕获到的变量。

其中参数,mutable,->returntype 可以省略,捕捉列表和函数体内容可以为空,但是不能省略

4、lamdba表达式应用

int main()
{
	int a = 1;
	int b = 2;

	//参数表是引用捕捉,最后结果a = 2 b = 1
	auto swap1 = [](int& a, int& b)
		{
			int tmp = a;
			a = b;
			b = tmp;
		};

	//本质是捕捉拷贝,最后结果是a = 1 b = 2
	auto swap2 = [a, b]() mutable
		{
			int tmp = a;
			a = b;
			b = tmp;
		};
	
	//引用捕捉,最后结果是a = 2 b = 1
	auto swap3 = [&a, &b]()
		{
			int tmp = a;
			a = b;
			b = tmp;
		};

	return 0;
}

由于lamdba表达式返回值类型只有编译器知道,本质产生的是对象,所以用 auto 接受。

四、深入理解c++11之后的默认成员函数

c++11之后新增了移动构造函数和移动赋值运算符重载,这两个都是默认成员函数。

规则

1、若没有实现移动构造或移动赋值,并且没有实现析构函数,拷贝构造,复制构造的任意一个函数,编译器就会默认生成移动构造或移动赋值。

注意:虽然是说 “没有实现析构函数,拷贝构造,复制构造的任意一个函数” 其实这三个函数是一起的,你写了析构函数就证明一定有深拷贝的成员变量需要释放,那么此时拷贝构造,复制构造就是有必要写的。

2、默认生成的移动构造或移动赋值函数,对于内置类型按字节拷贝,自定义类型看他是否有移动构造或移动赋值函数,如果有就调用,没有就走拷贝构造或赋值重载函数。

3、如果提供了移动构造或移动赋值,编译器不会提供拷贝构造或赋值重载函数。

4、所以写了析构函数,拷贝构造,复制构造的任意一个函数,编译器就不会默认生成移动构造或移动赋值,我们只能自己写移动构造和移动赋值,此时根据结论3,我们不写拷贝构造或赋值重载函数就会报错。

5、根据结论4总结就是我们可以只写析构函数,拷贝构造,复制构造,但是如果要写移动构造或移动赋值,就必须析构函数,拷贝构造,复制构造三个函数一个不少。

6、函数如果不期望被调用就在该函数声明加上=delete 即可。

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

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

相关文章

如何配置ESXI主机的IP地址管理

&#x1f3e1;作者主页&#xff1a;点击&#xff01; &#x1f427;Linux基础知识(初学)&#xff1a;点击&#xff01; &#x1f427;Linux高级管理防护和群集专栏&#xff1a;点击&#xff01; &#x1f510;Linux中firewalld防火墙&#xff1a;点击&#xff01; ⏰️创作…

通过python脚本查询自己阿里云账号里的某个域名的A记录解析情况,以及测拨,用于排查未使用的解析

安装sdk pip install aliyun-python-sdk-alidns代码全文 import json import requests from aliyunsdkcore.client import AcsClient from aliyunsdkalidns.request.v20150109 import DescribeDomainRecordsRequest# 替换为你的阿里云 AccessKey ID 和 AccessKey Secret acce…

栈与队列 - 逆波兰表达式求值

150. 逆波兰表达式求值 方法一&#xff1a;栈 /*** param {string[]} tokens* return {number}*/ var evalRPN function(tokens) {const stack [];for (const token of tokens) {if (isNaN(Number(token))) { // 非数字const n2 stack.pop(); // 出栈两个数字const n1 s…

假期作业--数据结构

1、顺序表实现学生管理系统&#xff08;参照顺序表技能&#xff09;写出菜单界面switch选择&#xff0c;功能1创建顺序表&#xff08;堆区&#xff09;&#xff0c;2录入学生信息&#xff0c;3插入一个学生信息&#xff0c;4删除一个学生信息&#xff0c;5按照位置修改一个学生…

javaEE中自定义注解以及注解的解析

注解&#xff1a; 就是java代码里的特殊标记&#xff0c;比如Override、Test,作用是&#xff1a;让其它程序根据注解信息来决定怎么执行程序。 自定义注解&#xff1a;自己定义注解 Public interface 注解名称{ Public 属性类型 属性名&#xff08;&#xff09; default 默认…

写字楼/办公室为什么要建设智慧公厕?有哪些价值?@卓振思众

智慧公厕是指利用先进技术和设备对公共厕所进行智能化管理的系统。这些技术包括物联网&#xff08;IoT&#xff09;、传感器技术、大数据分析和自动化系统等。【卓振思众】智慧公厕不仅提升了公厕的使用体验&#xff0c;还实现了更高效的管理和维护。 写字楼/办公室智慧公厕的定…

揭秘RAG与大模型对接:深入探讨9大隐藏挑战

前一段时间&#xff0c;各个大模型在争斗&#xff1a;谁能携带更长、更大的上下文 Prompt&#xff0c;比如 Kimi 说 200 万字&#xff0c;阿里通义千问又说自己能达 1000 万字&#xff1b;大家都知道 Prompt 很重要&#xff0c;但是 RAG 和 长的上下文文本携带 是两个不同的技术…

mac查看jdk安装目录

打开终端&#xff0c;直接输入命令&#xff1a; /usr/libexec/java_home终端即会输出jdk的安装目录&#xff1a;

8.17日学习打卡---Spring Cloud Alibaba(四)

8.17日学习打卡 目录&#xff1a; 8.17日学习打卡分布式流量防护什么是服务雪崩解决方案内部异常治理外部流量控制 SentinelSentinel 基本概念Sentinel 的主要特性Sentinel 是如何工作的Sentinel 与 Hystrix、resilience4j 的对比安装Sentinel控制台将应用接入Sentinel流量控制…

python使用flask实现自动根据url寻找对应目录/文件/方法,实现动态路由

例如访问:/user/index/index_config 则自动访问/user 目录里 index.py文件里的 index_config 方法 实现代码 from flask import Flask,jsonifyapp Flask(__name__)def reg_func(code, data, msg, debugNone, showFalse):return jsonify({code: code,data: data,msg: msg,time…

iPhone照片怎么导入电脑?一键导入毫不费力

随着智能手机的普及&#xff0c;我们越来越依赖手机来记录生活的点点滴滴。iPhone作为其中的佼佼者&#xff0c;其高质量的摄像头为用户捕捉了无数珍贵瞬间。然而&#xff0c;随着照片数量的增多&#xff0c;手机存储空间可能会变得捉襟见肘&#xff0c;此时将照片导入电脑既能…

UniAD_面向规划的自动驾驶

Planning-oriented Autonomous Driving 面向规划的自动驾驶 https://github.com/OpenDriveLab/UniAD Abstract Modern autonomous driving system is characterized as modular tasks in sequential order, i.e., perception, prediction, and planning. In order to perfor…

Python 如何创建和管理虚拟环境?

Python虚拟环境是一个独立的运行环境&#xff0c;能够与系统的全局Python环境相隔离。它允许你在不影响系统其他项目的前提下&#xff0c;为每个项目创建独立的Python环境&#xff0c;并在该环境中安装特定版本的包和依赖项。这在开发多个项目时非常有用&#xff0c;尤其是当这…

EmguCV学习笔记 VB.Net 4.1 颜色变换

版权声明&#xff1a;本文为博主原创文章&#xff0c;转载请在显著位置标明本文出处以及作者网名&#xff0c;未经作者允许不得用于商业目的。 教程VB.net版本请访问&#xff1a;EmguCV学习笔记 VB.Net 目录-CSDN博客 教程C#版本请访问&#xff1a;EmguCV学习笔记 C# 目录-CSD…

使用Python实现B站自动答题机器人

文章目录 1. 写在前面2. 接口分析3. 点选验证分析4. Python程序实现 【&#x1f3e0;作者主页】&#xff1a;吴秋霖 【&#x1f4bc;作者介绍】&#xff1a;擅长爬虫与JS加密逆向分析&#xff01;Python领域优质创作者、CSDN博客专家、阿里云博客专家、华为云享专家。一路走来长…

解密Linux中的通用块层:加速存储系统,提升系统性能

通用块层 通用块层是Linux中的一个重要组件&#xff0c;用于管理不同块设备的统一接口&#xff0c;减少不同块设备的差异带来的影响。它位于文件系统和磁盘驱动之间&#xff0c;类似于Java中的适配器模式&#xff0c;让我们无需关注底层实现&#xff0c;只需提供固定接口即可。…

【13】即时编译(JIT)

概念 即时编译是用来提升应用运行效率的技术。代码会先在JVM上解释执行&#xff0c;之后反复执行的热点代码会被即时翻译成为机器码&#xff0c;直接运行在底层硬件上。 分层编译模式 HotSpot包含多个即时编译器&#xff1a;C1、C2和Graal&#xff08;Java 10&#xff0c;实验…

Native开发与逆向第一篇-字符串

开发 Android studio新建项目 Android studio新建一个Native C项目。 默认代码就是调用Native 方法stringFromJNI 返回一个字符串。 public native String stringFromJNI();C 代码 stringFromJNI 函数的代码&#xff0c;默认使用的是静态注册的方式&#xff0c;静态注册是函…

【数据结构】链式结构实现:二叉树

二叉树 一.快速创建一颗二叉树二.二叉树的遍历1.前序、中序、后序遍历&#xff08;深度优先遍历DFS&#xff09;2.层序遍历&#xff08;广度优先遍历BFS&#xff09; 三.二叉树节点的个数四.二叉树叶子节点的个数五.二叉树的高度六.二叉树第k层节点个数七.二叉树查找值为x的节点…

C++ //练习 17.23 编写查找邮政编码的正则表达式。一个美国邮政编码可以由五位或九位数字组成。前五位数字和后四位数字之间可以用一个短横线分隔。

C Primer&#xff08;第5版&#xff09; 练习 17.23 练习 17.23 编写查找邮政编码的正则表达式。一个美国邮政编码可以由五位或九位数字组成。前五位数字和后四位数字之间可以用一个短横线分隔。 环境&#xff1a;Linux Ubuntu&#xff08;云服务器&#xff09; 工具&#x…