C++ 左值右值引用

 借鉴于施磊老师:(21条消息) C++11 - 右值引用_大秦坑王的博客-CSDN博客

目录

左值引用:

右值引用:

引用折叠:

std::move移动语义

 std::forward完美转发


左值引用:

        左值引用使用&符号进行声明,例如int&const T&,其中T是任意类型。

1. 左值引用可以做函数参数:

void increment(int& num) {
    num++;
}

int main() {
    int value = 10;
    increment(value);  // 传递左值给函数
    // 现在value的值变为11
    return 0;
}

在这个示例中,increment函数接受一个左值引用参数,并对其进行递增操作。

2. 左值引用可以用于对象成员变量:

class MyClass {
public:
    int& getValue() {
        return value;
    }

private:
    int value = 10;
};

int main() {
    MyClass obj;
    obj.getValue() = 20;  // 使用左值引用返回的对象成员变量进行赋值
    // 现在obj中的value变为20
    return 0;
}

在这个示例中,getValue函数返回一个左值引用,允许我们直接修改对象的成员变量。 

3.  用于修改被引用对象,除非引用本身是const的

int main() {
    int value = 10;
    int& ref = value;  // 引用类型的变量ref绑定到value的左值
    ref = 20;  // 修改ref的值,也会修改value的值
    // 现在value的值变为20
    return 0;
}

在这个示例中,我们声明了一个引用类型的变量ref,将其绑定到value的左值,并可以通过引用修改value的值。 

         在汇编指令上看,定义一个左值引用在汇编指令上和定义一个指针是没有任何区别的,定义一个引用变量int& ref = value, 是必须初始化的,因为指令上需要把右边value的地址放到左边的ref的内存里(相当于定义了一个指针的内存),当给引用变量ref赋值时,指令从ref里面取出value的地址,并更改内容。所以在汇编指令层面,引用和指针的操作没有任何区别

int &b = 20; //无法执行

        上述代码无法执行,因为需要从20取地址放到b的地址中,但是20是数字没有在内存上的存储,因此无法取地址,但是可以:

const int &b = 20;

        用常引用可以解决,这时内存上产生了一个临时变量保存了20,b现在引用的是这个临时变量,所以可行,但是这样就只能读取数据无法修改数据

 综上所述,左值引用必须要求右边的值必须能够取地址,如果无法取地址,可以用常引用,但是用了常引用只能读取无法修改,这时需要右值引用。

         const int &b=20和int &&b=20在底层指令上是一模一样的,都需要产生临时量,然后保存临时量的地址,没有任何区别,不同的是,通过右值引用变量,可以进行读操作,也可以进行写操作有地址的用左值引用,没有地址的用右值引用;有变量名字的用左值引用,没有变量名字的(比如临时量没有名字)用右值引用

右值引用:

         C++11提供带右值引用参数的拷贝构造函数operator=赋值重载函数。使用这两个函数不涉及内存的开辟和数据拷贝,临时对象马上要析构了,直接把临时对象持有的资源拿过来就行了。临时量都会自动匹配右值版本的成员方法,提高内存的资源使用效率。

        带右值引用参数的拷贝构造和赋值重载函数,又叫移动构造函数移动赋值函数,这里的移动指的是把临时量的资源移动给了当前对象,临时对象就不持有资源,为nullptr了,实际上没有进行任何的数据移动,没发生任何的内存开辟和数据拷贝。

引用折叠:

在模板编程中,处理参数传递和类型推导

int main()
{
	int a = 10;
	int &b = a;
	//int &&c = a; // 错误,无法将左值a绑定到右值引用c
	//int &&d = b; // 错误,无法将左值b绑定到右值引用d
	int &&e = 20;  // 正确,20是一个右值(没地址没名字),可以绑定到右值引用e上
	//int &&f = e; // 错误,无法将左值e绑定到右值引用f,因为e有名字,有地址,本身也是左值
	int &g = e;    // 正确,e本身有名字,有地址,是一个左值,可以被g引用
	return 0;
}

 右值引用变量e本身是一个左值

template
void func(T&& val)
{
	cout << "01 val:" << val << endl;
	T tmp = val;
	tmp++;
	cout << "02 val:" << val << " tmp:" << tmp << endl;
}
int main()
{
	int a = 10;
	int &b = a;
	int &&c = 10;

	cout << "func(10):" << endl;
	func(10);// 10是右值,引用类型是int&&,T&&推导过程是int&&+&&折叠成int&&,所以T是int,下同
	cout << "func(a):" << endl;
	func(a);// a是左值,不可能用右值引用来引用,所以func推导T为int&,那么T&&->int&+&&折叠成int&
	cout << "func(std::move(a)):" << endl;
	func(std::move(a)); // std::move(a)是把a转成右值类型,右值引用类型是int&&,所以func推导T为int
	cout << "func(b):" << endl;
	func(b);// b是左值,不可能用右值引用来引用,所以func推导T为int&,那么T&&->int&+&&折叠成int&
	cout << "func(c):" << endl;
	func(c);// c是左值,不可能用右值引用来引用,所以func推导T为int&,那么T&&->int&+&&折叠成int&
	
	return 0;
}

代码运行打印如下

func(10): //T tmp = val; T是int
01 val:10
02 val:10 tmp:11
func(a): //T tmp = val; T是int&
01 val:10
02 val:11 tmp:11
func(std::move(a)): //T tmp = val; T是int
01 val:11
02 val:11 tmp:12
func(b): //T tmp = val; T是int&
01 val:11
02 val:12 tmp:12
func(c): //T tmp = val; T是int&
01 val:10
02 val:11 tmp:11

        引用折叠,就是int && + &&折叠成int&&,除此之外,都折叠成int&,如int& + &&折叠成int&.

std::move移动语义

        move就是返回传入的实参的右值引用类型,做了一个类型强转,move代码:

template
	_NODISCARD constexpr remove_reference_t<_Ty>&&
		move(_Ty&& _Arg) noexcept   
	{	// forward _Arg as movable
	return (static_cast&&>(_Arg));
	}

std::forward完美转发

        如果直接将参数传递给其他函数,参数的值类别(左值还是右值)可能会发生改变,导致无法实现完美传递。

template
void addBack(_Ty &&val)
{
	/*
	这里使用std::forward,可以获取val引用的实参的引用类型,
	是左引用,还是右引用,原理就是根据“引用折叠规则”
	int&+&&->int&     int&&+&&->int&&
	*/
	mvec[mcur++] = std::forward<_Ty>(val);
}

int main()
{
	Vector vec;
	A a;
	vec.push_back(a); // 调用A的左值引用的赋值函数
	vec.push_back(A()); // 理应调用A的右值引用参数的赋值函数,却调用了左值引用的赋值函数
	return 0;
}
//打印结果:
operator=
operator=(A&&)

 std::forward函数的作用就是根据输入参数的值类别,将参数以相同的值类别转发给其他函数。如果输入参数是左值,那么转发时将保持左值,如果输入是右值,那么转发时是右值引用。

        C++库里面提供的两个forward重载函数,分别接收左值和右值引用类型,进行一个类型强转,(static_cast<_Ty&&>(_Arg)), 如果实参类型是int& + && -> int&就保持了实参的左值引用类型,如果实参类型是int&& + && -> int&&就保持了实参的右值引用类型。

【总结】:std::move是获取实参的右值引用类型;std::forward是在代码实现过程中,保持实参的原有的引用类型(左引用或者右引用类型)。 

你可能感兴趣的:(C++11,c++,开发语言)