C++11 - 2 - 右值引用与移动构造

C++11 - 右值引用与移动构造

  • 前言:
  • 左值和右值:
    • 定义:
      • 左值:
      • 右值:
    • 自己引用:
      • & 和 &&:
      • 右值引用变量:
    • 交叉引用:
      • 左值通过const引用右值:
      • 右值通过move()引用左值:
  • 移动构造:
    • 定义:
      • 深浅拷贝:
      • 右值在拷贝的特殊性:
    • 移动拷贝/移动赋值:
      • 图示:
      • 代码:
    • 编译器对中间变量的优化:
      • 代码举例:
      • 图示:

前言:

Vue框架:从项目学Vue
OJ算法系列:神机百炼 - 算法详解
Linux操作系统:风后奇门 - linux

左值和右值:

定义:

左值:

  • 凡是可以被取地址取到的变量,都属于左值:
  • 举例:
int a = 1;
int *p = new int{2};
int arr[]{1, 2, 3};

右值:

  • 右值分为两类:纯右值 + 将亡值
  • 纯右值:
    1. 常量,如"hello" / 101
    2. 表达式,如x+y
    3. 传值函数返回值,如:
int fmin(int x, int y){
	return x<y?x:y;
}
  • 将亡值:
    1. 多为局部变量,严格说函数内局部变量

    2. 创建后生命周期将结束的变量

    3. 如:该函数内所有的中间变量,在函数return结束后都将被释放,

      函数调用结束后,无法再取得他们的值或地址

int midfind(int l, int r, int &arr[], int target){
	int i = l, j = r;
	while(i < j){
		int mid = (i + j)>>1;
		if(arr[mid] > target)	r=mid-1;
		else i = mid;
	}
	return i;
}
  • 右值特点即是无法被取到地址

    其实常量也可以被视作是一种将亡值,毕竟只用到了赋值的一瞬间

自己引用:

& 和 &&:

  • 我们原本使用的引用基本都属于左值引用&,C++11中新增了右值引用&&:
  • 左值&引用:
int a = 0;
int &b = a;
  • 右值&&引用:
int fmin(int x, int y){
	return x<y?x:y;
}

int &&a = 10;
int &&b = 1+1;
int &&c = fmin(3, 5);

右值引用变量:

  • 上述讲解右值引用时的用例中的a b c变量都是可以取得到地址的:
int fmin(int x, int y){
	return x<y?x:y;
}

int &&a = 10;
int &&b = 1+1;
int &&c = fmin(3, 5);

cout<<"&a : "<<&a<<endl;
cout<<"&b : "<<&b<<endl;
cout<<"&c : "<<&c<<endl;
  • 说明了右值引用变量本身其实是左值

交叉引用:

左值通过const引用右值:

  • C++11未出现前,左值也有引用右值的情况,

    此时需要做变量写权限的缩减,即加关键词const:

int fmin(int x, int y){
	return x<y?x:y;
}

int x = 1, y = 2;
const int &a = 10;
const int &b = x+y;
const int &c = fmin(1, 2);
  • 没错,这里的const引用变量也是左值。

右值通过move()引用左值:

  • move():
int x = 10;
int *p = &x;
int &&a = move(x);
int &&b = move(p);
int &&c = move(*p);

移动构造:

  • C++11中,将类内默认函数由原本的6个上升到了8个:
直接构造 取地址
析构 const取地址
拷贝构造 移动构造
拷贝赋值 移动赋值

定义:

深浅拷贝:

  • 原来我们进行直接构造和拷贝构造时,需要考虑对变量采用深浅拷贝:
class A{
	private:
		int *p;
	public:
		//深拷贝:自己创建空间,仅作值的拷贝
		A(A &a){
			p = new int(*(a.p));
			cout << "深拷贝函数" << endl;
		}
		//浅拷贝:自己不创建空间,直接指向原来地址
		A(A &a){
			p = a.p;
			cout << "浅拷贝函数" << endl;
		}
};
  • 图解深浅拷贝区别:
    C++11 - 2 - 右值引用与移动构造_第1张图片

右值在拷贝的特殊性:

  • 引入了右值(尤其是将亡值)的概念后,
    我们发现对于很多再也不会使用的变量可以直接浅拷贝
    毕竟将亡值再不会被调用,所以不会造成两个对象/变量被调用的干扰问题

    再进一步,既然将亡值已经开辟好了内存空间,并且赋好了值
    那么我们直接将指针指向这块空间,直接利用这块空间内的数据即可

    这样做存在一个问题:将亡值真死亡之后,其内存空间将被释放
    所以我们再占用将亡值内存空间后,还需要将将亡值指针指向空,让其释放nullptr

  • 总结:
    在对右值拷贝时,鉴于右值中资源将要被释放,所以可以采用两步,直接使用右值资源
    1. 直接浅拷贝
    2. 右值指针指向nullptr

移动拷贝/移动赋值:

图示:

  • 所谓移动拷贝构造/移动赋值构造,指的是对将亡指中资源的移动:
    C++11 - 2 - 右值引用与移动构造_第2张图片

代码:

  • 移动构造和赋值的原理类似:基于将亡值进行类内变量构造
class A{
	public:
	/*
		移动拷贝构造:自己不创建空间,直接使用将亡值的地址空间,
		顺便把将亡值的指针指向空,防止地址空间被释放
	*/
		A(A &&a){							//注意对右值的引用采用&&
        	p = a.p;
        	a.p = nullptr;
        	cout << "移动构造函数" << endl;
    	}

	/*
		移动赋值构造:其实就是用operator =重写一下移动拷贝构造
	*/

		void operator=(A &&a){					//注意对右值的引用采用&&
			p = a.p;
			a.p = nullptr;
			cout<<"移动赋值构造"<<endl;
		}
};

编译器对中间变量的优化:

代码举例:

  • 有了移动构造的概念,对于将亡值的拷贝,我们不再需要借助中间变量了:
class A{
	private: 
		int* p;
	public:
		A(int x){
			p = new int(x);
		}
};
A func(int x){
	A a(x);
	return a;
}
int main(){
	A b = func(1);						//C++11自带移动拷贝构造和移动赋值构造
	return 0;
}

图示:

  • 有无中间变量的拷贝过程区别:
    C++11 - 2 - 右值引用与移动构造_第3张图片

你可能感兴趣的:(#,c++,c++,c++11,右值,移动构造,移动拷贝)