【C++】C++11介绍列表初始化右值引用和移动语义

news2025/4/1 13:05:06

个人主页 : zxctscl
如有转载请先通知

文章目录

  • 1. C++11简介
  • 2. 统一的列表初始化
    • 2.1{}初始化
    • 2.2 std::initializer_list
  • 3. 声明
    • 3.1 auto
    • 3.2 decltype
    • 3.3 nullptr
  • 4. 范围for循环
    • 4.1 范围for的语法
    • 4.2 范围for的使用条件
  • 5. STL中一些变化
  • 6. 右值引用和移动语义
    • 6.1 左值引用和右值引用
      • 6.1.1 左值引用
        • 6.1.1.1 左值
        • 6.1.1.2 左值引用
      • 6.1.2 右值引用
        • 6.1.2.1 右值
        • 6.1.2.2 右值引用
    • 6.2 右值引用意义
    • 6.3 左值引用与右值引用比较
    • 6.4 右值引用引用左值及其一些更深入的使用场景分析
    • 6.5 完美转发

1. C++11简介

在这里插入图片描述

在2003年C++标准委员会曾经提交了一份技术勘误表(简称TC1),使得C++03这个名字已经取代了C++98称为C++11之前的最新C++标准名称。不过由于C++03(TC1)主要是对C++98标准中的漏洞进行修复,语言的核心部分则没有改动,因此人们习惯性的把两个标准合并称为C++98/03标准。
从C++0x到C++11,C++标准10年磨一剑,第二个真正意义上的标准珊珊来迟。相比于C++98/03,C++11则带来了数量可观的变化,其中包含了约140个新特性,以及C++03标准中约600个缺陷的修正,这使得C++11更像是从C++98/03中孕育出的一种新语言。相比较而言,C++11能更好地用于系统开发和库开发、语法更加泛华和简单化、更加稳定和安全,不仅功能更强大,而且能提升程序员的开发效率,公司实际项目开发中也用得比较多,所以要作为一个重点去学习。C++11增加的语法特性非常篇幅非常多,我们这里没办法一 一讲解,所以主要讲解实际中比较实用的语法。

C++11链接: https://en.cppreference.com/w/cpp/11

小故事:
1998年是C++标准委员会成立的第一年,本来计划以后每5年视实际需要更新一次标准,C++国际标准委员会在研究C++ 03的下一个版本的时候,一开始计划是2007年发布,所以最初这个标准叫C++ 07。但是到06年的时候,官方觉得2007年肯定完不成C++ 07,而且官方觉得2008年可能也完不成。最后干脆叫C++ 0x。x的意思是不知道到底能在07还是08还是09年完成。结果2010年的时候也没完成,最后在2011年终于完成了C++标准。所以最终定名为C++11。

2. 统一的列表初始化

2.1{}初始化

在C语言中结构体和数组的初始化,而C++兼容长。在C++98中,标准允许使用花括号{}对数组或者结构体元素进行统一的列表初始值设定。
比如:

struct Point
{
	int _x;
	int _y;
};

int main()
{
	int array1[] = { 1, 2, 3, 4, 5 };
	int array2[5] = { 0 };
	Point p = { 1, 2 };
	return 0;
}

C++11扩大了用大括号括起的列表(初始化列表)的使用范围:

自定义类型不能像这样初始化:

class A
{
private:
	int _x;
	int _y;
};
int main()
{
	A aa1 = { 2,2 };
	return 0;
}

在这里插入图片描述
但想要也可以,利用隐式类型转换:

class A
{
public:
	A(int x, int y)
		:_x(x)
		,_y(y)
	{}
private:
	int _x;
	int _y;
};

int main()
{
	//多参数的隐式类型转换,本质就是用{2,2}去构造一个A对象,A对象再去拷贝构造。而编译器就直接优化直接构造
	A aa1 = { 2,2 };

	return 0;
}

这个就是参考单参数的构造函数的隐式类型转换:
都是构造+拷贝构造直接优化为构造

class A
{
public:
	A(int x, int y)
		:_x(x)
		, _y(y)
	{}

	A(int x)
		:_x(x)
		, _y(x)
	{}
private:
	int _x;
	int _y;
};
int main()
{
		// 单参数的隐式类型转换
		A aa2 = 1;
	
		// 多参数的隐式类型转换
		A aa1 = { 2,2 };
	
	//不加const,就不行。因为类型转化,中间会生成一个临时对象,而临时对象具有常性,就得加const
		const A& aa3 = { 2,2 };
				
	return 0;
}

也可以加explicit,此时就不会发生隐式类型转换:
在这里插入图片描述
而C++11对单参数也可以一切皆可列表初始化:

class A
{
public:
	//explicit A(int x, int y)
	A(int x, int y)
		:_x(x)
		, _y(y)
	{}

	A(int x)
		:_x(x)
		, _y(x)
	{}
private:
	int _x;
	int _y;
};

int main()
{
		// 单参数的隐式类型转换
		A aa2 = 1;
		A aa4 = { 1 };

	return 0;
}

在这里插入图片描述

使其可用于所有的内置类型和用户自定义的类型,使用列表初始化时,可添加等号(=),也可不添加。

class A
{
public:
	//explicit A(int x, int y)
	A(int x, int y)
		:_x(x)
		, _y(y)
	{}

	A(int x)
		:_x(x)
		, _y(x)
	{}
private:
	int _x;
	int _y;
};

int main()
{
	int x1 = 1;
	int x2{ 2 };

	int array1[]{ 1, 2, 3, 4, 5 };
	int array2[5]{ 0 };
	Point p{ 1, 2 };

		// 单参数的隐式类型转换
		A aa2 = 1;
	
		A aa4 = { 1 };
		A aa5 { 1 };
	
		// 多参数的隐式类型转换
		A aa1 = { 2,2 };
		A aa6 { 2,2 };
	
	return 0;
}

内置类型也能用:
在这里插入图片描述

在这里插入图片描述
创建对象时也可以使用列表初始化方式调用构造函数初始化

class Date
{
public:
	Date(int year, int month, int day)
		:_year(year)
		, _month(month)
		, _day(day)
	{
		cout << "Date(int year, int month, int day)" << endl;
	}
private:
	int _year;
	int _month;
	int _day;
};
int main()
{
	Date d1(2022, 1, 1); // old style
	// C++11支持的列表初始化,这里会调用构造函数初始化
	Date d2{ 2022, 1, 2 };
	Date d3 = { 2022, 1, 3 };
	return 0;
}

一切皆可用列表初始化,可以省略=

2.2 std::initializer_list

为了支持容器的列表初始化,就有了initializer_list
在这里插入图片描述

std::initializer_list的介绍文档: https://cplusplus.com/reference/initializer_list/initializer_list/

int main()
{
	vector<int> v1;
	vector<int> v2(10, 1);

    auto il1 = { 10, 20, 30 };
	initializer_list<int> il2 = { 10, 20, 30 };
	cout << typeid(il1).name() << endl;

	return 0;
}

std::initializer_list是什么类型:

int main()
{
	auto il1 = { 10, 20, 30 };
	initializer_list<int> il2 = { 10, 20, 30 };
	cout << typeid(il1).name() << endl;

	return 0;
}

在这里插入图片描述
它有一个first和last
在这里插入图片描述
first指向第一个位置,也就是10,last指向最后一个位置的下一个:
在这里插入图片描述
它底层就是两个指针,一个指向开始位置,一个指向结束的位置:
在这里插入图片描述

所以initializer_list支持迭代器,迭代器返回的就是const T*
在这里插入图片描述

所以遍历initializer_list就可以用范围for遍历。

容器想要不固定的{ }数据个数初始化:

vector(const T& x1)
vector(const T& x1, const T& x2)
vector(const T& x1, const T& x2, const T& x3)
vector(const T& x1, const T& x2, const T& x3, const T& x4)
// ...

他就这个构造一劳永逸的解决了问题,不用像上面那样麻烦的方式解决

 vector(initializer_list<T> il);
	// 构造
	vector<int> v5({ 1,2,3,4,5 });

    //隐式类型转换
	// X自定义 = Y类型 ->隐式类型转换 X(Y mm)  X支持Y为参数类型构造就可以
	vector<int> v3 = {1,2,3,4,5};
	vector<int> v4{ 10,20,30};

容器想要不固定的{ }数据个数初始化,initializer_list支持

在这里插入图片描述

所有容器都支持initializer_list,像map:
在这里插入图片描述

int main()
{
	pair<string, string> kv1("sort", "排序");
	pair<string, string> kv2("insert", "插入");
	map<string, string> dict1 = {kv1, kv2};

	// 1、pair多参数隐式类型转换
	// 2、initializer_list<pair>的构造
	map<string, string> dict2 = { {"sort", "排序"}, {"insert", "插入"} };

	return 0;
}

在这里插入图片描述

3. 声明

c++11提供了多种简化声明的方式,尤其是在使用模板时。

3.1 auto

在C++98中auto是一个存储类型的说明符,表明变量是局部自动存储类型,但是局部域中定义局部的变量默认就是自动存储类型,所以auto就没什么价值了。C++11中废弃auto原来的用法,将其用于实现自动类型推断。这样要求必须进行显示初始化,让编译器将定义对象的类型设置为初始化值的类型。

int main()
{
	int i = 10;
	auto p = &i;
	auto pf = strcpy;
	cout << typeid(p).name() << endl;
	cout << typeid(pf).name() << endl;
	
	
	map<string, string> dict = { {"sort", "排序"}, {"insert", "插入"} };
	//map<string, string>::iterator it = dict.begin();
	auto it = dict.begin();


	return 0;
}

这里x加加,不影响i:

int main()
{
	int i = 0;
	auto x = i;
	x++;
	
	return 0;
}

在这里插入图片描述
但是如果用引用,此时x就是i的别名,那么值就都加加
在这里插入图片描述

int main()
{
	int i = 0;
	auto x = i;
	x++;

	int& j = i;
	auto y = j;

	return 0;
}

这里y不是引用,他们地址不一样,j是i的引用,y是一个拷贝:
在这里插入图片描述

y想要变成引用,还得加&
在这里插入图片描述

3.2 decltype

关键字decltype将变量的类型声明为表达式指定的类型。

int main()
{
	list<int>::iterator it1;

	// typeid推出时一个单纯的字符串
	cout << typeid(it1).name() << endl;

	return 0;
}

在这里插入图片描述

typeid不能用来定义对象在这里插入图片描述

decltype可以用来定义对象:

int main()
{
	list<int>::iterator it1;

	// typeid推出时一个单纯的字符串
	cout << typeid(it1).name() << endl;
	
	// 可以用来定义对象
	decltype(it1) it2;
	cout << typeid(it2).name() << endl;

	return 0;
}

auto也可以自动推导:

	list<int>::iterator it1;

	// typeid推出时一个单纯的字符串
	cout << typeid(it1).name() << endl;

	// 可以用来定义对象
	decltype(it1) it2;
	cout << typeid(it2).name() << endl;

	auto it3 = it1;
	cout << typeid(it3).name() << endl;

在这里插入图片描述

但是下面这样呢?

template<class T>
class B
{
public:
	T* New(int n)
	{
		return new T[n];
	}
};

auto func1()
{
	list<int> lt;
	auto ret = lt.begin();

	return ret;
}

int main()
{
	auto ret3 = func1();
	B<decltype(ret3)> bb1;

	return 0;
}

decltype推导出来的类型,不仅仅可以帮助定义对象,也可以做模版的传参。

template<class T>
class B
{
public:
	T* New(int n)
	{
		return new T[n];
	}
};

auto func1()
{
	list<int> lt;
	auto ret = lt.begin();

	return ret;
}

int main()
{
	auto ret3 = func1();
	B<decltype(ret3)> bb1;

	map<string, string> dict2 = { {"sort", "排序"}, {"insert", "插入"} };
	auto it4 = dict2.begin();

	B<decltype(it4)> bb2;
	B<std::map<std::string, std::string>::iterator> bb2;
	// auto和decltype有些地方增加代码读起来难度

	return 0;
}

3.3 nullptr

由于C++中NULL被定义成字面量0,这样就可能回带来一些问题,因为0既能指针常量,又能表示整形常量。所以出于清晰和安全的角度考虑,C++11中新增了nullptr,用于表示空指针。

#ifndef NULL
#ifdef __cplusplus
#define NULL 0
#else
#define NULL ((void *)0)
#endif
#endif

4. 范围for循环

4.1 范围for的语法

在C++98中如果要遍历一个数组,可以按照以下方式进行:

void TestFor()
{
	int array[] = { 1, 2, 3, 4, 5 };
	for (int i = 0; i < sizeof(array) / sizeof(array[0]); ++i)
		array[i] *= 2;
	for (int* p = array; p < array + sizeof(array) / sizeof(array[0]); ++p)
		cout << *p << endl;
}

对于一个有范围的集合而言,由程序员来说明循环的范围是多余的,有时候还会容易犯错误。因此C++11中引入了基于范围的for循环。for循环后的括号由冒号“ :”分为两部分:第一部分是范围内用于迭代的变量,第二部分则表示被迭代的范围。


void TestFor()
{
	int array[] = { 1, 2, 3, 4, 5 };
	for (auto& e : array)
		e *= 2;
	for (auto e : array)
		cout << e << " ";
	return 0;
}

注意:与普通循环类似,可以用continue来结束本次循环,也可以用break来跳出整个循环。

4.2 范围for的使用条件

  1. for循环迭代的范围必须是确定的
    对于数组而言,就是数组中第一个元素和最后一个元素的范围;对于类而言,应该提供begin和end的方法,begin和end就是for循环迭代的范围。
    注意:以下代码就有问题,因为for的范围不确定
void TestFor(int array[])
{
	for (auto& e : array)
		cout << e << endl;
}
  1. 迭代的对象要实现++和==的操作。

5. STL中一些变化

新容器
用橘色圈起来是C++11中的一些几个新容器,但是实际最有用的是unordered_map和unordered_set。这两个前面已经进行了分享,有需要点这个链接: 【C++】unordered系列关联式容器及其底层结构 其他了解一下即可。

在这里插入图片描述
array相比于静态数组,检查越界更好。
但不如vector好用:

int main()
{
	array<int, 10> a1;
	vector<int> a2(10, 0);

	return 0;
}

在这里插入图片描述
forward_list最大的优点就是只头插、头删的时候用:
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

在这里插入图片描述

在这里插入图片描述
在这里插入图片描述

容器中的一些新方法:
如果我们再细细去看会发现基本每个容器中都增加了一些C++11的方法,但是其实很多都是用得比较少的。
比如提供了cbegin和cend方法返回const迭代器等等,但是实际意义不大,因为begin和end也是可以返回const迭代器的,这些都是属于锦上添花的操作.
实际上C++11更新后,容器中增加的新方法最后用的插入接口函数的右值引用版本:

https://cplusplus.com/reference/vector/vector/emplace_back/
https://cplusplus.com/reference/vector/vector/push_back/
https://cplusplus.com/reference/map/map/insert/
https://cplusplus.com/reference/map/map/emplace/

6. 右值引用和移动语义

6.1 左值引用和右值引用

6.1.1 左值引用

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

6.1.1.1 左值

简单而言就是:
左值:可以取地址;
右值:不可以取地址。

此时a,b,c都是左值:

int main()
{
	// 左值和右值,能否取地址
	// 左值:可以取地址的
	// 右值:不可以取地址的
	int a = 10;
	int b = a;
	const int c = 10;


	cout << &a << endl;
	cout << &b << endl;
	cout << &c << endl;

	return 0;
}

在这里插入图片描述

左值不一定是个值,也可以是表达式:

int main()
{
	// 左值是一个表达式,可以取地址的
	int* p = &a;

	cout << &(*p) << endl;
	return 0;
}

在这里插入图片描述

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

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

int main()
{
	// 左值是一个表达式,可以取地址的
	vector<int> v(10, 1);
	v[1];

	cout << &(v[1]) << endl;

	return 0;
}

在这里插入图片描述

int main()
{
// 以下的p、b、c、*p都是左值
int* p = new int(0);
int b = 1;
const int c = 2;

return 0;
}
6.1.1.2 左值引用

左值引用:

// 以下几个是对上面左值的左值引用
int*& rp = p;
int& rb = b;
const int& rc = c;
int& pvalue = *p;

6.1.2 右值引用

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

6.1.2.1 右值

常量、匿名对象、临时对象这些都是右值:

int main()
{
	// 10、string("1111")、to_string(123), x+y

	cout << &10 << endl;
	cout << &string("1111") << endl;
	cout << &to_string(123) << endl;


	return 0;
}

在这里插入图片描述

像下面这样也是右值:

    int x = 1, y = 2;
	cout << &(x + y) < endl;
int main()
{
	double x = 1.1, y = 2.2;
	// 以下几个都是常见的右值
	10;
	x + y;
	fmin(x, y);
		
	// 这里编译会报错:error C2106: “=”: 左操作数必须为左值
	10 = 1;
	x + y = 1;
	fmin(x, y) = 1;

// 以下几个都是对右值的右值引用
	int&& rr1 = 10;
	double&& rr2 = x + y;
	double&& rr3 = fmin(x, y);
	
	return 0;
}

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

6.1.2.2 右值引用

右值引用:

	// 以下几个都是对右值的右值引用
	int&& rr1 = 10;
	double&& rr2 = x + y;
	double&& rr3 = fmin(x, y);

那么左值引用能否给右值引用起别名?
不可以,但是const左值引用可以。

	const string& ref1 = string("1111");
	const int& ref2 = 10;

右值引用能否给左值取别名?
不可以,但是可以给move以后的左值区别

int main()
{
	// 右值引用能否给左值取别名 -- 不可以,但是可以给move以后的左值区别
	string s1("1111");
	string&& rref5 = move(s1);

	return 0;
}

在这里插入图片描述

6.2 右值引用意义

前面我们可以看到左值引用既可以引用左值和又可以引用右值,那为什么C++11还要提出右值引用呢?是不是化蛇添足呢?

首先得知道引用的意义是减少拷贝,提高效率

左值引用的场景:

void func1(const string& s);
string& func2();//传引用返回,减少拷贝,提高效率

左值引用返回值的问题没有彻底解决,如果返回值是func2中局部对象,不能用引用返回。

来看看下面这段代码:

namespace bit
{
	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(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()
		{
			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 str;
	}
}

int main()
{
	bit::string ret1 = bit::to_string(1234);

	return 0;
}

这里是构造加构造,两次拷贝构造
在这里插入图片描述

但是编译器在这里做了个优化:合二为一,直接构造:

在这里插入图片描述

将ret1这样写,这里就完全没有优化了:先构造,构造完了还要拷贝构造,就有两次构造任何传值返回。这样拷贝构造生成一个对象,拷贝构造对象也没有部分合二为一,因为下一个是赋值。

	bit::string ret1;
	ret1 = bit::to_string(1234);

在这里插入图片描述

要返回str的别名,但是str已经销毁了:
在这里插入图片描述

在这里插入图片描述

不用引用返回:
这里的栈帧图:在main里有个ret1对象,to_string里面有一个str,str指向一个空间,如果编译器不优化,中间还会产生一个临时变量,会先拷贝这个临时对象,销毁str指向的空间,再用这个临时对象,再去拷贝构造ret1的。编译器优化之后,在str返回之前,就构造一个给ret1,再销毁str的,此时str也跟着一起销毁。

在这里插入图片描述

用右值引用,还是返回它的别名,没有拷贝。打破中间的优化,没有办法将两次构造合二为一。
左值引用和右值引用结果都一样,str这个对象已经销毁了。
在这里插入图片描述

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

再运行上面bit::to_string的两个调用,我们会发现,这里没有调用深拷贝的拷贝构造,而是调用了移动构造,移动构造中没有新开空间,拷贝数据,所以效率提高了
在这里插入图片描述

不仅仅有移动构造,还有移动赋值:
在bit::string类中增加移动赋值函数,再去调用bit::to_string(1234),不过这次是将bit::to_string(1234)返回的右值对象赋值给ret1对象,这时调用的是移动构造。
在这里插入图片描述

这里运行后,我们看到调用了一次移动构造和一次移动赋值。因为如果是用一个已经存在的对象接收,编译器就没办法优化了。bit::to_string函数中会先用str生成构造生成一个临时对象,但是我们可以看到,编译器很聪明的在这里把str识别成了右值,调用了移动构造。然后在把这个临时对象做为bit::to_string函数调用的返回值赋值给ret1,这里调用的移动赋值。
在这里插入图片描述
在这里插入图片描述

STL中的容器都是增加了移动构造和移动赋值: https://cplusplus.com/reference/string/string/string/
https://cplusplus.com/reference/vector/vector/vector/

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

左值引用总结:

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

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

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

template<class _Ty>
inline typename remove_reference<_Ty>::type&& move(_Ty&& _Arg) _NOEXCEPT
{
	// forward _Arg as movable
	return ((typename remove_reference<_Ty>::type&&)_Arg);
}
int main()
{
	bit::string s1("hello world");
	// 这里s1是左值,调用的是拷贝构造
	bit::string s2(s1);
	// 这里我们把s1 move处理以后, 会被当成右值,调用移动构造
	// 但是这里要注意,一般是不要这样用的,因为我们会发现s1的
	// 资源被转移给了s3,s1被置空了。
	bit::string s3(std::move(s1));
	return 0;
}

https://cplusplus.com/reference/list/list/push_back/
http://www.cplusplus.com/reference/vector/vector/push_back/

在这里插入图片描述

void push_back (value_type&& val);
int main()
{
	list<bit::string> lt;
    bit::string s1("1111");
   // 这里调用的是拷贝构造
   lt.push_back(s1);
   
   // 下面调用都是移动构造
   lt.push_back("2222");
   lt.push_back(std::move(s1));
	
	
	return 0;
}

s1是一个左值,就老实拷贝。如果是右值,就转移资源:
在这里插入图片描述
上面效率提升,针对的是自定义类型的深拷贝的类,因为深拷贝的类才有转移资源的移动系列函数
对于内置类型,和浅拷贝自定义类型,没有移动系列函数

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

完美转发实际中的使用场景:

template<class T>
struct ListNode
{
	ListNode* _next = nullptr;
	ListNode* _prev = nullptr;
	T _data;
};
template<class T>
class List
{
	typedef ListNode<T> Node;
public:
	List()
	{
		_head = new Node;
		_head->_next = _head;
		_head->_prev = _head;
	}
	void PushBack(T&& x)
	{
		//Insert(_head, x);
		Insert(_head, std::forward<T>(x));
	}
	void PushFront(T&& x)
	{
		//Insert(_head->_next, x);
		Insert(_head->_next, std::forward<T>(x));
	}
	void Insert(Node* pos, T&& x)
	{
		Node* prev = pos->_prev;
		Node* newnode = new Node;
		newnode->_data = std::forward<T>(x); // 关键位置
		// prev newnode pos
		prev->_next = newnode;
		newnode->_prev = prev;
		newnode->_next = pos;
		pos->_prev = newnode;
	}
	void Insert(Node* pos, const T& x)
	{
		Node* prev = pos->_prev;
		Node* newnode = new Node;
		newnode->_data = x; // 关键位置
		// prev newnode pos
		prev->_next = newnode;
	    newnode->_prev = prev;
		newnode->_next = pos;
		pos->_prev = newnode;
	}
private:
	Node* _head;
};
int main()
{
	List<bit::string> lt;
	lt.PushBack("1111");
	lt.PushFront("2222");
	return 0;
}

有问题请指出,大家一起进步!!!

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

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

相关文章

搜广推校招面经六十一

美团推荐算法 一、ANN算法了解么&#xff1f;说几种你了解的ANN算法 ANN 近似最近邻搜索&#xff08;Approximate Nearest Neighbor Search&#xff09;算法 1.1. KD-Tree&#xff08;K-Dimensional Tree&#xff0c;K 维树&#xff09; 类型: 空间划分数据结构适用场景: 低…

人工智能与软件工程结合的发展趋势

AI与软件工程的结合正在深刻改变软件开发的流程、工具和方法&#xff0c;其发展方向涵盖了从代码生成到系统维护的整个生命周期。以下是主要的发展方向和技术趋势&#xff1a; 1. 软件架构体系的重构 从“面向过程”到“面向目标”的架构转型&#xff1a; AI驱动软件设计以目标…

nacos 外置mysql数据库操作(docker 环境)

目录 一、外置mysql数据库原因&#xff1a; 二、数据库准备工作 三、构建nacos容器 四、效果展示 一、外置mysql数据库原因&#xff1a; 想知道nacos如何外置mysql数据库之前&#xff0c;我们首先要知道为什么要外置mysql数据库&#xff0c;或者说这样做有什么优点和好处&am…

【数电】半导体存储电路

组合逻辑电路输入和输出之间是确定关系&#xff0c;与之前的历史记录没有任何关系。时序逻辑电路则有相应的存储元件&#xff0c;要把之前的状态保存起来。 要构成时序逻辑电路&#xff0c;必须要有相应的存储元件&#xff0c;第五章讲述相应的存储元件 一、半导体存储电路概…

Jenkins插件安装失败如何解决

问题&#xff1a;安装Jenkins时候出现插件无法安装的情况。 测试环境&#xff1a; 操作系统&#xff1a;Windows11 Jenkins&#xff1a;2.479.3 JDK&#xff1a;17.0.14&#xff08;21也可以&#xff09; 解决办法一&#xff1a; 更换当前网络&#xff0c;局域网、移动、联通…

postman测试文件上传接口详解

&#x1f345; 点击文末小卡片&#xff0c;免费获取软件测试全套资料&#xff0c;资料在手&#xff0c;涨薪更快 postman是一个很好的接口测试软件&#xff0c;有时候接口是Get请求方式的&#xff0c;肯定在浏览器都可以测了&#xff0c;不过对于比较规范的RestFul接口&#x…

什么是贴源库

贴源库的定义与核心概念 贴源库&#xff08;Operational Data Store, ODS&#xff09;是数据架构中的基础层&#xff0c;通常作为数据仓库或数据中台的第一层&#xff0c;负责从业务系统直接抽取、存储原始数据&#xff0c;并保持与源系统的高度一致性。其核心在于“贴近源头”…

UE5中开启ACES工作流程

首先要开启OCIO插件 OpenColorIO 创建配置 下载ACES https://github.com/colour-science/OpenColorIO-Configs/tree/feature/aces-1.2-config 加载ACES的ocio 选择Srgb 选择ACES 参考链接: https://zhuanlan.zhihu.com/p/534357694 https://www.youtube.com/watch?vBo3Bvh…

基于springboot+vue的农产品电商平台

开发语言&#xff1a;Java框架&#xff1a;springbootJDK版本&#xff1a;JDK1.8服务器&#xff1a;tomcat7数据库&#xff1a;mysql 5.7&#xff08;一定要5.7版本&#xff09;数据库工具&#xff1a;Navicat11开发软件&#xff1a;eclipse/myeclipse/ideaMaven包&#xff1a;…

解决Dubbo3调用Springcloud接口报No provider available from registry RegistryDirectory

解决Dubbo调用Springcloud接口报No provider available from registry RegistryDirectory 问题发现问题解决 问题发现 在学习Dubbo过程中&#xff0c;Dubbo官网有一篇文章《微服务最佳实践&#xff0c;零改造实现 Spring Cloud & Apache Dubbo 互通》&#xff0c;跟着示例…

2023第十四届蓝桥杯大赛软件赛国赛C/C++ 大学 B 组(真题题解)(C++/Java题解)

本来想刷省赛题呢&#xff0c;结果一不小心刷成国赛了 真是个小迷糊〒▽〒 但&#xff0c;又如何( •̀ ω •́ )✧ 记录刷题的过程、感悟、题解。 希望能帮到&#xff0c;那些与我一同前行的&#xff0c;来自远方的朋友&#x1f609; 大纲&#xff1a; 一、子2023-&#xff…

第十四章:JSON和CSV格式详解及Python操作

在数据处理和开发工作中&#xff0c;JSON和CSV是两种非常常见的数据格式。它们各有特点&#xff0c;适用于不同的场景。本文将分别介绍这两种格式的产生原因、应用场景&#xff0c;并结合Python讲解如何操作这两种文件格式&#xff0c;最后用表格总结它们的常用操作及特性。资源…

双磁条线跟踪控制

1问题 同学反馈小车跟随磁力线&#xff0c;双轮差速小车&#xff0c;左右侧各有2个磁条传感器和各1条磁条线&#xff0c;需要控制小车跟随磁条线轨迹。 2 方法 &#xff08;1&#xff09;普通小车可能没有速度反馈&#xff0c;则不考虑转弯半径&#xff0c;仅考虑一个控制关…

树莓派超全系列文档--(7)RaspberryOS播放音频和视频

播放音频和视频 播放音频和视频VLC 媒体播放器vlc GUIvlc CLI使用 cvlc 在没有图形用户界面的情况下播放媒体 在 Raspberry Pi OS Lite 上播放音频和视频指定音频输出设备指定视频输出设备同时指定音频和视频输出设备提高数据流播放性能 文章来源&#xff1a; http://raspberr…

chrome浏览器下载和Chrome浏览器的跨域设置

Chrome浏览器的跨域设置 下载chrome浏览器设置chrome跨域 下载chrome浏览器 点击官方下载&#xff0c;然后逐步安装即可 设置chrome跨域 1、然后在D盘创建个文件夹命名为ChromeDevSession。 2、右击chrome浏览器选择属性。 3、在目标编辑栏的最后加上&#xff1a;–disabl…

【高并发内存池】第六弹---深入理解内存管理机制:ThreadCache、CentralCache与PageCache的回收奥秘

✨个人主页&#xff1a; 熬夜学编程的小林 &#x1f497;系列专栏&#xff1a; 【C语言详解】 【数据结构详解】【C详解】【Linux系统编程】【Linux网络编程】【项目详解】 目录 1、threadcache回收内存 2、centralcache回收内存 3、pagecache回收内存 1、threadcache回收内…

累积分布策略思路

一种基于概率密度和累积分布函数的量化交易策略&#xff0c;主要应用于期货市场。该策略通过计算价格数据的概率密度和累积分布函数&#xff08;CDF&#xff09;&#xff0c;结合移动平均线和ATR&#xff08;平均真实范围&#xff09;等技术指标&#xff0c;实现多空交易的自动…

【JavaScript】九、JS基础练习

文章目录 1、练习&#xff1a;对象数组的遍历2、练习&#xff1a;猜数字3、练习&#xff1a;生成随机颜色 1、练习&#xff1a;对象数组的遍历 需求&#xff1a;定义多个对象&#xff0c;存数组&#xff0c;遍历数据渲染生成表格 let students [{ name: 小明, age: 18, gend…

RAG、大模型与智能体的关系

一句话总结&#xff1a; RAG&#xff08;中文为检索增强生成&#xff09; 检索技术 LLM 提示。 RAG、大模型与智能体的关系解析 1. 核心概念定义 RAG&#xff08;检索增强生成&#xff09; 是一种结合信息检索与生成式模型的框架&#xff0c;通过从外部知识库&#xff08;如…

Linux中《进程状态--进程调度--进程切换》详细介绍

目录 进程状态Linux内核源代码怎么说运行&&阻塞&&挂起内核链表 进程状态查看Z(zombie)-僵尸进程僵尸进程危害孤儿进程 进程优先级进程切换Linux2.6内核进程O(1)调度队列 进程状态 Linux内核源代码怎么说 为了弄明白正在运⾏的进程是什么意思&#xff0c;我们…