23.右值引用_c++11(左值引用的使用场景、右值引用的使用场景、左值引用和右值引用的对比、移动构造、移动赋值、右值引用完美转发)

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

4.右值引用

4.1 左值引用和右值引用

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

int main()
{
	// 以下的p、b、c、*p都是左值
	int* p = new int(0);
	int b = 1;
	const int c = 2;
    
	// 以下几个是对上面左值的左值引用
	int*& rp = p;
	int& rb = b;
    
	const int& rc = c;
	int& pvalue = *p;
	return 0;
}

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

右值被分为
1.纯右值(内置类型表达式的值)
2.将亡值(自定义类型表达式的值)

  • 案例1
#include<iostream>

// 一个函数模板
template<class T>
T fmin(T  x, T y)
{
	if (x < y)
	{
		return x;
	}
	return y;	
}

int main()
{
	double x = 1.1, y = 2.2;
    
	// 以下几个都是常见的右值
	// 字面常量:10;
	// 表达式的返回值 x + y;
	// 函数返回值 fmin(x, y);
	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;
}
  • 案例2
int main()
{
	// 左值引用只能引用左值,不能引用右值。
	int a = 10;
    // ra1为a的别名,a是左值
	int& ra1 = a;  
    
    // 编译失败,因为10是右值,右值是不可以被左值引用的
	//int& ra2 = 10;   

	// const修饰的左值引用,既可引用左值,也可引用右值。
	const int& ra3 = 10;  
	const int& ra4 = a;	  

	// 右值引用只能右值,不能引用左值。
	int&& r1 = 10;

	// error C2440: “初始化”: 无法从“int”转换为“int &&”
	// message : 无法将左值绑定到右值引用
	int a = 10;
	// int&& r2 = a; // 报错

    // move()左值之后,编译器会将其识别为右值
	// 右值引用可以引用move()以后的左值
	int&& r3 = std::move(a);

	return 0;
}
  • 案例3

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

int main()
{
	double x = 1.1, y = 2.2;
    // 右值引用之后,会导致右值被存储到特定位置(此时我们就可以对rr1进行修改,不过修改的是特定位置的变量,此时我们可以认为rr1就是一个左值),且可以取到该位置的地址
	int&& rr1 = 10;
    
    //  const修饰rr2之后,则rr2不可以被修改了
	const double&& rr2 = x + y;
    
	rr1 = 20;
	// rr2 = 5.5;  // 报错
    
	return 0;
}

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

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

左值引用的意义是什么?

1.函数传参或者是函数传返回值,使用左值引用,可以减少参数的拷贝
但是左值引用并没有完全解决问题,例如以下场景

  • 场景1
// 场景1:左值引用可以解决函数传参,减少参数的拷贝
// const引用,既可以接收左值,也可以接收右值
template<class T>
void func1(const T& x)
{

}


int main()
{
	// v1为左值
	vector<int> v1(10, 0);
	func1(v1);

	// vector<int>(10,0) 是一个匿名对象,也就是一个右值(属于将亡值)
    // 出了当前这一行,这个匿名对象就会被释放
	func1(vector<int>(10, 0));

	return 0;
}
  • 场景2:
// 场景2:函数传返回值,
// x的声明周期是直到main()返回,才会被销毁
// 因此我们才可以使用左值引用返回,如果x出func2()就被销毁,那么是不可以使用左值引用返回的
template<class T>
const T& func2(const T& x)
{
	// ...假设中间还做了许多操作
	return x;
}

int main()
{
    // v1为左值
	vector<int> v1(10, 0);
    
	func2(v1);
	return 0;
}
  • 场景3
// 场景三:左值引用尚未解决的问题场景
// 当ret出func3()的函数作用于就会被销毁,那么我们是不可以使用左值引用返回的(这是因为引用的变量已经被销毁了)
// 因此,此时ret返回时,就会产生临时变量,就会增加参数ret的拷贝
// 右值引用的价值之一:就是补齐这个最后一块短板,传值返回的拷贝问题
template<class T>
T func3(const T& x)
{
	T ret;
	// ...
	return ret;
}

int main()
{
	// v1为左值
	vector<int> v1(10, 0);
    
	func3(v1);

	return 0;
}
  • 场景4
// 但是其实也可以使用左值引用来解决场景三的问题
// 就是使用输出型参数,但是这样使用起来是很别扭的
// 假设ret的类型就是int
// 使用了输出型参数就不需要进行返回了
template<class T>
void func4(const T& x, int& ret)
{
	// ...
	//return ret;
}

int main()
{
	// v1为左值
	vector<int> v1(10, 0);

   	int ret = 10;
    // ret是一个输出型参数
	func4(v1,ret);

	return 0;
}

右值引用是怎样解决左值引用的短板的?

// 首先,我们先来看下面代码运行时,其底层的拷贝原理
#include<iostream>
#include<assert.h>
using namespace std;

namespace qwy
{
	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)
		{
			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()
		{
			delete[] _str;
			_str = nullptr;
		}

		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)
		{
			push_back(ch);
			return *this;
		}

	private:
		char* _str = nullptr;
		size_t _size = 0;
		size_t _capacity = 0; // 不包含最后做标识的\0
	};

	// 将整数转化为字符串
	string to_string(int value)
	{
		bool flag = true;
		if (value < 0)
		{
			flag = false;
			// 将value变为正数
			value = 0 - value;
		}

		qwy::string str;
		while (value > 0)
		{
            // 获取value的个位
			int x = value % 10;  
             // 获取value的十位及以上
			value /= 10;

			str += ('0' + x); // 将x转化为对应的ascll值,并放入str
		}

		if (flag == false)
		{
			str += '-';
		}

        // 逆转string对象中字符串的顺序
		std::reverse(str.begin(), str.end());
		return str;
	}
}
场景一
int main()
{
	// 场景1:
    // to_string()的返回值str,会先将其拷贝给一个临时变量,再将临时变量拷贝给ret
    // 但是编译器会自动对其进行优化,将两次拷贝简化为一次拷贝
    // 如下图所示
	qwy::string ret = qwy::to_string(-1234);

	return 0;
}

  • 打印结果为:string(const string& s) – 深拷贝
  • 通过打印结果我们可知,只调用了一次深拷贝,符合我们预期的结果

image-20230415141547347

场景二
// 场景2:
int main()
{
    // 对于场景二:编译器不敢将两次深拷贝优化为一次深拷贝
    // 优化的规定一般为:传参或传返回值过程中,存在连续的构造、拷贝构造、就会被优化
    // 但是具体是取决于编译器的
	  
    // 由于编译器对于如下这种情况是不进行优化的,
    // 因此to_string()的返回值str会先拷贝给临时变量,再由临时变量赋值给ret,但是赋值重载的过程中,会调用拷贝函数
    qwy::string ret;
    // 在上面创建string对象ret和下面对ret进行赋值之间,可能对ret进行了其他操作,因此编译器不敢进行优化
	ret = qwy::to_string(-1234);

	return 0;
}

注:编译器没有进行优化

第一步:拷贝给临时变量,调用了一次拷贝构造,由临时变量赋值给ret,调用了赋值重载,赋值重载的函数内部调用了一次拷贝构造

打印结果为:

string(const string& s) – 深拷贝

string& operator=(string s) – 深拷贝

string(const string& s) – 深拷贝

4.4移动构造和移动赋值

// 移动构造
// 所谓的移动构造就是将string对象的右值引用s与将要构造的对象进行资源交换
// 这样是不需要在移动构造内部创建新的对象
string(string&& s)
{
	cout << "string(const string& s) -- 移动拷贝" << endl;

	swap(s);
}

// 移动赋值
string& operator=(string&& s)
{
	cout << "string& operator=(string s) -- 移动赋值" << endl;
	swap(s);

	return *this;
}		   
#include<iostream>
#include<assert.h>
using namespace std;

namespace qwy
{
	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)
		{
			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;
		}

		// 移动构造
        // 所谓的移动构造就是将s与将要构造的对象进行资源交换
		string(string&& s)
		{
			cout << "string(const string& s) -- 移动拷贝" << endl;
			
			swap(s);
		}

		// 移动赋值
		string& operator=(string&& s)
		{
			cout << "string& operator=(string s) -- 移动赋值" << endl;
			swap(s);

			return *this;
		}


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

		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)
		{
			push_back(ch);
			return *this;
		}

	private:
		char* _str = nullptr;
		size_t _size = 0;
		size_t _capacity = 0; // 不包含最后做标识的\0
	};

	// 将整数转化为字符串
	string to_string(int value)
	{
		bool flag = true;
		if (value < 0)
		{
			flag = false;
			// 将value变为正数
			value = 0 - value;
		}

		qwy::string str;
		while (value > 0)
		{
			int x = value % 10;  // 获取value的个位
			value /= 10; // 获取value的十位及以上

			str += ('0' + x); // 将x转化为对应的ascll值,并放入str
		}

		if (flag == false)
		{
			str += '-';
		}

		std::reverse(str.begin(), str.end());
		return str;
	}
}
场景一:右值引用解决了拷贝构造问题(只是对于右值传参拷贝的问题)
//  使用移动拷贝进一步提升构造的效率(移动构造的效率的大于拷贝构造的)
//  这就是右值引用解决了拷贝构造问题(只是对于右值传参拷贝的问题)
int main()
{
	qwy::string s1("hello world");
	
	// 用s1来构造s2
    // s1是左值,因此编译器会默认调用左值引用传参的拷贝构造
	qwy::string s2(s1);

	// 左值被move()之后,编译器会将其看作是右值
	// move(s1)是右值,编译器会默认调用右值引用传参的移动构造
	// 如果没有右值引用传参的移动构造,move(s1)也是可以被左值引用传参的拷贝构造调用
	qwy::string s3(move(s1));

	return 0;
}

打印结果为:
string(const string& s) – 深拷贝
string(const string& s) – 移动拷贝

image-20240506135630271

场景二:利用右值引用解决赋值问题
int main()
{
	
    // 根据优化的规则:传参或传返回值过程中,存在连续的构造、拷贝构造、就会被优化
    // 因此对于如下的情况,编译器并不会进行优化
    // 1.编译器会将to_string()的返回值默认识别为右值,并将其移动拷贝给临时变量,
    // 2.再将临时变量移动赋值给ret
    
    qwy::string ret;
	ret = qwy::to_string(-1234);

	return 0;
}

打印结果:

string(const string& s) – 移动拷贝

string& operator=(string s) – 移动赋值

场景三:
int main()
{
	list<qwy::string> lt;
	qwy::string s1("111111");
    
    // s1是左值,调用拷贝构造
	lt.push_back(s1);

    // "222222" 是右值(字面常量),调用移动拷贝
    // 如果我们没有实现移动拷贝,那么就会调用拷贝构造
	lt.push_back(qwy::string("222222"));
	
    // "333333" 会先构造一个string的对象,再调用移动拷贝
    // 如果我们没有实现移动拷贝,那么就会调用拷贝构造
	lt.push_back("333333");

	return 0;
}

打印结果
string(const string& s) – 深拷贝
string(const string& s) – 移动拷贝
string(const string& s) – 移动拷贝

总结

右值引用和左值引用减少拷贝的原理不太一样

1.左值引用是取别名,直接起作用。

2.右值引用是间接起作用,实现移动构造和移动赋值,在拷贝的场景中,如果是右值(将亡值),转义资源

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

  • 场景1:
#include<iostream>
using namespace std;

void func1(int& x) 
{ 
	cout << "void func1(int& x)" << endl; 
}

void func2(int&& x) 
{ 
	cout << "void func2(int&& x)" << endl; 
}

int main()
{
	int x = 1;
    // 左值调用对应的func1()
	func1(x);
    
    // 右值调用对应的func2()
	func2(2);

	return 0;
}
// 打印结果为:
// void func1(int& x)
// void func2(int&& x)

  • 场景二
void func2(int&& x) 
{ 
	cout << "void func2(int&& x)" << endl; 
}

int main()
{
	int x = 1;
    func2(2);
	func1(x); // 此处会报错,右值引用无法引用左值

	return 0;
}   
  • 模板中的&&万能引用
// 使用模板中的&&万能引用就可以解决场景二中:左值不可以被右值引用的问题了

// 模板的右值引用我们可以认为是万能引用,
// 既可以引用右值,也可以引用左值
template<typename T>
void PerfectForward(T&& t)
{
    // t是否可以被加加,详情请看下图
	// t++;
}

int main()
{
	perfectforward(10);           // 右值

	int a;
	perfectforward(a);            // 左值
	perfectforward(std::move(a)); // 右值
    
    // 且万能引用既可以引用const的左值,也可以引用const的右值
    const int b = 8;
	PerfectForward(b);		      // const 左值
	PerfectForward(std::move(b)); // const 右值
	return 0;
}    

image-20230415225029477

  • 万能引用存在的问题
// 万能引用还存在如下的问题:
#include<iostream>
using namespace std;

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)
{
	// T&& t  不论是对左值引用,还是对右值引用,t本身都是一个左值
    // 因此调用Fun()函数,始终会调用参数为左值引用的Fun()函数
	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;
}
//  打印结果为:
// 左值引用
// 左值引用
// 左值引用
// const 左值引用
// const 左值引用
  • 解决万能引用存在的问题
// 使用完美转发来解决上述的问题
// 具体如下:
#include<iostream>
using namespace std;

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)
{
	// 完美转发,保持t的本源属性,如果t是对左值的引用,那么就保留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;
}
// 打印结果为:
// 右值引用
// 左值引用
// 右值引用
// const 左值引用
// const 右值引用

对list类的改造(移动构造、移动赋值)(内部使用了完美转发)

// 关于list的封装
namespace qwy
{
    // 链表节点的类模板
	template<class T>
	struct list_node
	{
		list_node* _next;
		list_node* _prev;
		T _data;

		list_node(const T& x)
			:_next(nullptr)
			, _prev(nullptr)
			, _data(x)
		{}

        // 此处右值传参之后,x是左值,为了需要保证x的原生类型属性,因此我们需要使用完美转发
        // 来保证x的原生类型属性(此处使用了完美转发)
		list_node(T&& x)
			:_next(nullptr)
			, _prev(nullptr)
			, _data(std::forward<T>(x))
		{}
	};

    // 迭代器的类模板
	template<class T, class Ref, class Ptr>
	struct __list_iterator
	{
		typedef list_node<T> node;
		typedef __list_iterator<T, Ref, Ptr> Self;
		node* _pnode;

		__list_iterator(node* p)
			:_pnode(p)
		{}

		Ptr operator->()
		{
			return &_pnode->_data;
		}


		Ref operator*()
		{
			return _pnode->_data;
		}

		Self& operator++()
		{
			_pnode = _pnode->_next;
			return *this;
		}

		Self operator++(int)
		{
			Self tmp(*this);
			_pnode = _pnode->_next;
			return tmp;
		}

		Self& operator--()
		{
			_pnode = _pnode->_prev;
			return *this;
		}

		Self operator--(int)
		{
			Self tmp(*this);
			_pnode = _pnode->_prev;
			return tmp;
		}

		bool operator!=(const Self& it) const
		{
			return _pnode != it._pnode;
		}

		bool operator==(const Self& it) const
		{
			return _pnode == it._pnode;
		}
	};


	template<class T>
	class list
	{
		typedef list_node<T> node;
	public:
		typedef __list_iterator<T, T&, T*> iterator;

		iterator begin()
		{
			return iterator(_head->_next);
		}

		iterator end()
		{
			return iterator(_head);
		}

		void empty_initialize()
		{
			_head = new node(T());
			_head->_next = _head;
			_head->_prev = _head;

			_size = 0;
		}

		list()
		{
			empty_initialize();
		}

		void swap(list<T>& lt)
		{
			std::swap(_head, lt._head);
			std::swap(_size, lt._size);
		}

		// 拷贝构造
		list(const list<T>& lt)
		{
			empty_initialize();

			list<T> tmp(lt.begin(), lt.end());
			swap(tmp);
		}

		size_t size() const
		{
			return _size;
		}

		bool empty() const
		{
			return _size == 0;
		}

		~list()
		{
			clear();

			delete _head;
			_head = nullptr;
		}

		void clear()
		{
			iterator it = begin();
			while (it != end())
			{
				it = erase(it);
			}
		}

		// 左值引用的push_back
		void push_back(const T& x)
		{
			insert(end(), x);
		}

		// 右值引用的push_back
		void push_back(T&& x)
		{
            // 此处右值传参之后,x是左值,为了需要保证x的原生类型属性,因此我们需要使用完美转发
            // 来保证x的原生类型属性(此处,使用了完美转发)
			insert(end(), std::forward<T>(x));
		}

		// 左值引用的insert
		iterator insert(iterator pos, const T& x)
		{
			node* newnode = new node(x);
			node* cur = pos._pnode;
			node* prev = cur->_prev;

			prev->_next = newnode;
			newnode->_prev = prev;
			newnode->_next = cur;
			cur->_prev = newnode;

			++_size;

			return iterator(newnode);
		}

		// 右值引用的insert
		iterator insert(iterator pos, T&& x)
		{
            // 此处右值传参之后,x是左值,为了需要保证x的原生类型属性,因此我们需要使用完美转发
            // 来保证x的原生类型属性(此处使用了完美转发)
			node* newnode = new node(std::forward<T>(x));
			node* cur = pos._pnode;
			node* prev = cur->_prev;

			prev->_next = newnode;
			newnode->_prev = prev;
			newnode->_next = cur;
			cur->_prev = newnode;

			++_size;

			return iterator(newnode);
		}

		iterator erase(iterator pos)
		{
			assert(pos != end());

			node* prev = pos._pnode->_prev;
			node* next = pos._pnode->_next;

			prev->_next = next;
			next->_prev = prev;

			delete pos._pnode;
			--_size;

			return iterator(next);
		}

	private:
		node* _head;
		size_t _size;
	};
}

image-20230416102036977

使用list::push_back
int main()
{
	qwy::list<qwy::string> lt;
	qwy::string s1("111111");
	lt.push_back(s1);

	lt.push_back(qwy::string("222222"));

	lt.push_back("333333");

	return 0;
}

打印结果为
string(const string& s) – 移动拷贝
string(const string& s) – 深拷贝
string(const string& s) – 移动拷贝
string(const string& s) – 移动拷贝

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

AI助力制造行业探索创新路径

近期&#xff0c;著名科技作家凯文凯利&#xff08;K.K.&#xff09;来到中国&#xff0c;发表了一场演讲,给广大听众带来了深刻的启示。他在演讲中强调了人工智能&#xff08;AI&#xff09;对全球经济的重大影响&#xff0c;并提出了AI发展的多个观点&#xff1a; AI的多样性…

Llama 3 超级课堂

https://github.com/SmartFlowAI/Llama3-Tutorial/tree/main 第一节作业 streamlit run web_demo.py /root/share/new_models/meta-llama/Meta-Llama-3-8B-Instruct

计算机网络-IPv6地址配置

前面我们学习了IPv6地址的类型、ICMPv6在IPv6中的应用&#xff0c;现在来看下具体到IPv6的地址配置方式与路由配置。 一、IPV6地址配置过程 前面我们知道单播地址包括&#xff1a;全球单播地址、唯一本地地址、链路本地地址等&#xff0c;一般情况下日常使用较多的是链路本地地…

4000定制网站,因为没有案例,客户走了

接到一个要做企业站点的客户&#xff0c;属于定制开发&#xff0c;预算4000看起来是不是还行的一个订单&#xff1f; 接单第一步&#xff1a;筛客户 从客户询盘的那一刻开始就要围绕核心要素&#xff1a;预算和工期&#xff0c;凡是不符合预期的一律放掉就好了&#xff0c;没必…

基于springboot的校园食堂订餐系统

文章目录 项目介绍主要功能截图&#xff1a;部分代码展示设计总结项目获取方式 &#x1f345; 作者主页&#xff1a;超级无敌暴龙战士塔塔开 &#x1f345; 简介&#xff1a;Java领域优质创作者&#x1f3c6;、 简历模板、学习资料、面试题库【关注我&#xff0c;都给你】 &…

基于参数化建模的3D产品组态实现

我们最近为荷兰设计师家具制造商 KILO 发布了基于网络的 3D 配置器的第一个生产版本。我们使用了 Salsita 3D 配置器&#xff0c;这是一个内部 SDK&#xff0c;使新的 3D 配置器的实施变得轻而易举。虽然它给我们带来了巨大帮助&#xff0c;但我们仍然面临一些有趣的挑战。 NSD…

新一代智慧音视频平台,企业必备新基建

随着5G、云计算、实时音视频、多模态、大模型、数字人等前沿技术的发展&#xff0c;企业与客户的交互方式正加速趋于移动化、视频化。 国家有关部门也相继出台系列政策法规&#xff0c;确保线上业务安全合规&#xff0c;以保障消费者权益。如&#xff0c;针对保险、银行、证券…

思维导图怎么画?一文掌握绘制技巧

思维导图怎么画&#xff1f;你是不是还在为不知道怎么绘制思维导图而困惑&#xff1f;别担心&#xff0c;看完这篇文章就可以掌握绘制思维导图的基础步骤了。一起来看看吧&#xff01; 一、思维导图的基本结构 思维导图通常由中心节点、分支节点和子节点组成。中心节点是思维导…

【基于 PyTorch 的 Python深度学习】5 机器学习基础(1)

前言 文章性质&#xff1a;学习笔记 &#x1f4d6; 学习资料&#xff1a;吴茂贵《 Python 深度学习基于 PyTorch ( 第 2 版 ) 》【ISBN】978-7-111-71880-2 主要内容&#xff1a;根据学习资料撰写的学习笔记&#xff0c;该篇主要介绍了机器学习的基本任务、机器学习的一般流程&…

活动预约小程序源码系统 自定义预约表单+收费项目 带完整的安装代码包以及系统部署教程

数字化时代的快速发展&#xff0c;活动预约管理已经成为许多企业和个人不可或缺的一部分。为满足这一需求&#xff0c;我们特别开发了一款活动预约小程序源码系统&#xff0c;该系统不仅具备自定义预约表单的功能&#xff0c;还支持收费项目&#xff0c;旨在为用户提供更加便捷…

Garden Planner for Mac/win:打造您专属的绿意空间

随着城市化进程的加速&#xff0c;绿色空间对于现代人来说愈发珍贵。为满足人们对美好生活的追求&#xff0c;我们特推出了一款功能强大的园林绿化设计软件——Garden Planner for Mac/win。这款软件将帮助您轻松规划和设计您的花园、菜园或庭院&#xff0c;让绿意成为您生活的…

刷代码随想录有感(59):二叉搜索树的最近公共祖先

题干&#xff1a; 代码&#xff1a; class Solution {递归实现 public:TreeNode* traversal(TreeNode* root, TreeNode* p, TreeNode* q){if(root NULL)return NULL;if(root->val > p->val && root->val > q->val){TreeNode* left traversal(root…

高速开箱机价格与性能解析:如何挑选适合您的开箱解决方案?

随着电商和物流行业的迅猛发展&#xff0c;高效、自动化的包装设备成为了提升工作效率、减少人工成本的必备利器。高速开箱机作为其中的重要一环&#xff0c;其性能与价格成为了许多企业和个人关注的焦点。星派将深入探讨高速开箱机的价格与性能之间的关系&#xff0c;帮助您在…

视频封面一键提取:从指定时长中轻松获取您想要的帧图片

在数字媒体时代&#xff0c;视频已成为人们获取信息、娱乐和沟通的主要形式之一。而一个好的视频封面&#xff0c;往往能够吸引观众的眼球&#xff0c;增加视频的点击率和观看量。然而&#xff0c;对于很多视频创作者和编辑者来说&#xff0c;如何从视频中快速、准确地提取出合…

代码随想录算法训练营第二十天:二叉树成长

代码随想录算法训练营第二十天&#xff1a;二叉树成长 110.平衡二叉树 力扣题目链接(opens new window) 给定一个二叉树&#xff0c;判断它是否是高度平衡的二叉树。 本题中&#xff0c;一棵高度平衡二叉树定义为&#xff1a;一个二叉树每个节点 的左右两个子树的高度差的绝…

俄罗斯副总理暗示欧佩克+或增加原油产量,亚洲早盘油价小幅下跌

在俄罗斯副总理亚历山大诺瓦克暗示欧佩克可能采取行动增加原油产量后&#xff0c;亚洲早盘的油价出现小幅下跌。这一消息引起了市场对原油供给增加的担忧&#xff0c;导致油价走低。 City Index和FOREX.com的市场分析师Fawad Razaqzada表示&#xff0c;虽然原油价格在技术上尚…

C语言例题37、输入三组数字,按照从小到大的顺序排列输出

#include<stdio.h>int main() {int num[3];printf("请输入3组数字&#xff1a;");for (int i 0; i < 3; i)scanf("%d", &num[i]);for (int i 0; i < 2; i) { //每次选出最小值放在数组前面for (int j i 1; j < 3; j) {if (num[j] …

day_21

很简单&#xff0c;两个指针&#xff0c;指向1和n依次输出&#xff0c;然后自加自减即可。这样可以保证任意非两边的数同时大于或小于左邻和右邻的数。 看代码 #include <iostream> using namespace std;int main() {int n;cin >> n;int i 1, j n;while(i <…

带你快速掌握Spring Task

Spring Task ⭐Spring Task 是Spirng框架提供的任务调度工具&#xff0c;可以按照约定的时间自动执行某个代码逻辑 &#x1f4cc;一款定时任务框架 应用场景 信用卡信息银行贷款信息火车票信息 只要是需要定时处理的场景都可以使用Spring Task 只要有定时&#xff0c;就会有…

QT7_视频知识点笔记_2_对话框,布局,按钮,控件(查看帮助文档找功能函数)

第二天&#xff1a; 对话框&#xff0c;布局&#xff0c;按钮 QMainWindow&#xff1a;菜单下拉框添加之后可通过ui->actionXXX&#xff08;自定义的选项名&#xff09;访问&#xff0c;用信号triggered发出信号&#xff0c;槽函数可以使用lambda表达式进行 //菜单栏&am…