【基础知识】C++左值右值

目录

1 左值与右值

2 右值

3 右值引用

4 右值引用的用处

5 move左值转右值

6 引用折叠

7 forward完美转发


1 左值与右值

C++ 增加了一个新的类型,右值引用,记作“&&

  • 左值

        是指在内存中有明确的地址,我们可以找到这块地址的数据(可取地址)

  • 右值

        只提供数据,无法找到地址(不可取地址)

  • 所有有名字的都是左值,而右值是匿名的
  • 一般情况下,位于等号左边的是左值,位于等号右边的是右值,但是也可以出现左值给左值赋值的情况。

2 右值

C++11 中右值分为两种情况:一个是将亡值,另一个是纯右值:

  • 将亡值

        非引用返回的临时变量,运算表达式产生的临时变量,原始字面量,lambda 表达式等。

  • 纯右值

        与右值引用相关的表达式,比如:T&& 类型函数的返回值,std::move() 的返回值等。

3 右值引用

        右值引用就是对右值引用的类型。因为右值是匿名的,所以我们只能通过引用的方式找到它。无论是左值引用还是右值引用,都必须初始化,因为引用类型本身并不拥有所绑定对象的内存,只是该对象的一个别名。通过右值引用,该右值所占的内存又可以被使用。

#include<iostream>
#include<vector>
using namespace std;

int&& value = 520;//右值引用,520是字面值,是右值

class Test
{
public:
	Test()
	{
		cout << "构造函数" << endl;
	}
	//const是万能引用,即可以接收左值,又能接收右值
	Test(const Test& other)//常量左值引用
	{
		cout << "拷贝构造函数" << endl;
	}
};

Test getObj()
{
	return Test();
}


int main()
{
	int a1;
	//int &&a2 = a1;			//报错,右值引用不能被左值初始化
	//Test& t1 = getObj();		//右值不能初始化左值引用
	Test&& t2 = getObj();		//函数返回的临时对象是右值,可以被引用
	const Test& t3 = getObj();	//常量左值引用是万能引用,可以接收左值、右值、常量左值、常量右值
	const int& t3 = a1;			//被左值初始化

	return 0;

}

4 右值引用的用处

        在 C++ 用对象初始化时,会调用拷贝构造,如果这个对象占用堆内存很大,那么这个拷贝的代价就是非常大的,在某些情况,如果想要避免对象的深拷贝,就可以使用右值引用进行性能的优化。

#include<iostream>
#include<vector>
using namespace std;

class Test
{
public:
	Test() : m_num(new int(100))
	{
		cout << "构造函数" << endl;
	}
	//const是万能引用,即可以接收左值,又能接收右值
	Test(const Test& other) : m_num(new int(*other.m_num))//常量左值引用
	{
		cout << "拷贝构造函数" << endl;
	}

	~Test()
	{
		delete m_num;
	}

	int* m_num;
};

Test getObj()
{
	Test t;
	return t;
}


int main()
{
	Test t = getObj();
	cout << "t.m_num = " << *t.m_num << endl;

	return 0;

}

//输出
/*
构造函数
拷贝构造函数
t.m_num = 100
*/

        这段代码在调用 Test t = getObj();的时候,调用了拷贝构造函数,对返回的临时对象进行了深拷贝得到了对象 t,在 getObj 函数中创建的对象虽然进行了内存申请操作,但是没有使用就被释放掉了。如果我们在函数结束后,仍然可以利用在函数里面申请的空间,就极大的节省了创建对象和释放对象的空间,这个操作就需要我们的右值引用来完成。

        右值引用具有移动语义,移动语义可以将堆区资源,通过浅拷贝从一个对象转移到另一个对象,这样就能减少不必要的临时对象的创建,拷贝以及销毁,大幅度提高性能。

#include<iostream>
#include<vector>
using namespace std;



class Test
{
public:
	Test() : m_num(new int(100))
	{
		cout << "构造函数" << endl;
	}
	//const是万能引用,即可以接收左值,又能接收右值
	Test(const Test& other) : m_num(new int(*other.m_num))//常量左值引用
	{
		cout << "拷贝构造函数" << endl;
	}

	//添加移动构造函数,参数是右值引用
	Test(Test&& a) :m_num(a.m_num)
	{
		a.m_num = nullptr;
		cout << "移动构造函数" << endl;
	}

	~Test()
	{
		delete m_num;
	}

	int* m_num;
};

Test getObj()
{
	Test t;
	return t;
}


int main()
{
	Test t = getObj();// 因为getObj 返回的是右值,所以调用移动构造函数
	cout << "t.m_num = " << *t.m_num << endl;

	return 0;
}


//输出
/*
构造函数
移动构造函数
t.m_num = 100
*/

        在上面的代码中添加了移动构造函数(参数为右值引用类型),这样在进行 Test t = getObj();并没有调用构造函数进行深拷贝,而是调用的(浅拷贝)移动构造,提高了性能。

        本例子中,getObj() 返回值是一个右值,在进行赋值操作的时候,如果等号右边是一个右值,那么移动构造函数就会被调用。

结论:

        需要动态申请大量的资源的类,应该设计移动构造,提高程序的效率。需要注意的是在提供移动构造的同时,一般也会提供左值引用拷贝构造函数,左值初始化新对象时会走拷贝构造函数。

5 move左值转右值

        C++11 添加了右值引用,却不能左值初始化右值引用,在一些特定的情况下免不了需要左值初始化右值引用(用左值调用移动构造),如果想要用左值初始化一个右值引用,想要借助 std::move() 函数,move() 函数可以将左值转换为右值

#include<iostream>
#include<vector>
using namespace std;


class Test
{
public:

	int a = 3;
	int* m_num = &a;

	Test() : m_num(new int(100))
	{
		cout << "构造函数" << endl;
	}
	

	Test(const Test& other) : m_num(new int(*other.m_num))//常量左值引用
	{
		cout << "拷贝构造函数" << endl;
	}

	//添加移动构造函数,参数是右值引用
	Test(Test&& a) :m_num(a.m_num)
	{
		a.m_num = nullptr;
		cout << "移动构造函数" << endl;
	}

	~Test()
	{
		delete m_num;
		cout << "析构函数" << endl;
	}

};

Test getObj()
{
	Test t;
	return t;
}


int main()
{
	Test t = getObj();
	Test t1 = move(t);//此处调用移动构造,因为move是一个右值
	cout << "t1.m_num = " << *t1.m_num << endl;

	return 0;

}

//输出
/*
构造函数
移动构造函数
析构函数
移动构造函数
t1.m_num = 100
析构函数
析构函数
*/

6 引用折叠

        C++中,并不是所有情况下 && 都代表右值引用,在模板和自动类型推导(auto)中,如果是模板参数,想要指定为 T&&,如果是自动类型推导,需要指定为 auto&&,这两种情况下,&& 被称作“未定的引用类型”。另外 const T&& 表示一个右值引用,不是未定引用类型。

template<typename T>
void fun(T&& param)
{
    work(forward<T>(param))
}
int main()
{
	fun(10); //对于 f(10) 来说传入的实参 10 是右值,因此 T&& 表示右值引用
	int x = 1;
	fun(x);//对于 f(x) 来说传入的实参是 x 是左值,因此 T&& 表示左值引用
	return 0;
}

        因为 T&& 或者 auto&& 这种未定引用类型作为参数时,有可能被推导成右值引用,也有可能被推导为左值引用,在进行类型推导时,右值引用会发生变化,这种变化被称作引用折叠。折叠规则如下:

  • 提供右值推导 T&& 或者 auto&& 得到的是一个右值引用类型,const T&& 表示一右值引用
  • 通过非右值(右值引用、左值、左值引用、常量右值引用、常量左值引用)推导 T&& 绘制 auto&& 得到的是一个左值引用类型。
int main()
{
	
	int&& a1 = 1;			//右值				推导为			右值引用
	auto&& bb = a1;			//右值引用			推导为			左值引用
	auto&& bb1 = 2;			//右值				推导为			右值引用

	int a2 = 1;
	int& a3 = a2;			//左值				推导为			左值引用
	auto&& cc = a3;			//左值引用			推导为			左值引用
	auto&& cc1 = a2;		//左值				推导为			左值引用

	const int& s1 = 1;		//常量左值引用
	const int&& s2 = 1;		//常量右值引用
	auto&& dd = s1;			//常量左值引用		推导为			左值引用
	auto&& ee = s2;			//常量右值引用		推导为			左值引用

	return 0;

}

7 forward完美转发

        右值引用类型是独立于值的,一个右值引用作为函数的形参时,在函数内部转发该参数给内部其他参数时,他就变成了一个左值(当右值被命名是编译器认为他是个左值),并不是原来的类型了。如果按照参数原来的类型转发到另一个函数,可以使用 C++11 的 std::forward() 函数,该函数实现的功能称之为完美转发

// 函数原型
template <class T> T&& forward(typename remove_reference<T>::type& t) noexcept;

template <class T> T&& forward(typenameremove_reference<T>::type&& t) noexcept;

// 精简之后的样子
std::forward<T>(t);
  • std::forward<T>(t);
  • 当 T 为左值引用类型时,t 将会被转换为左值
  • 当 T 不是左值引用类型时,t 将会被转换为 T 类型的右值
#include <iostream>
using namespace std;

template<typename T>
void printValue(T& t)
{
	cout << "左值引用: " << t << endl;
}

template<typename T>
void printValue(T&& t)
{
	cout << "右值引用:" << t << endl;
}

template<typename T>
void testForward(T && v)
{
	printValue(v);
	printValue(move(v));
	printValue(forward<T>(v));
	cout << endl;
}

int main()
{
	testForward(520);
	int num = 1314;
	testForward(num);
	testForward(forward<int>(num));//int不是左值引用,则num被推导为右值
	testForward(forward<int&>(num));//int&是左值引用,则num被推导为左值
	testForward(forward<int&&>(num));
	return 0;
}


//输出
/*
左值引用: 520
右值引用:520
右值引用:520

左值引用: 1314
右值引用:1314
左值引用: 1314

左值引用: 1314
右值引用:1314
右值引用:1314

左值引用: 1314
右值引用:1314
左值引用: 1314

左值引用: 1314
右值引用:1314
右值引用:1314
*/

  • 8
    点赞
  • 60
    收藏
    觉得还不错? 一键收藏
  • 3
    评论
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值