右值和右值引用(C++11新特性)

news/2024/10/31 3:23:59/

文章目录

  • 右值VS左值
  • 右值引用VS左值引用
    • 定义
    • move函数
    • 左值引用&&右值引用 与 函数重载
    • 模板
    • 完美转发
    • 左值引用的意义
  • 移动构造&&移动赋值
    • 默认移动构造&&赋值

右值VS左值

关于什么是右值什么是左值,我们是这样判断的:

  • 右值:不能出现在等号左边的值 且 不能取地址(包括:字面常量、内置类型表达式,内置类型返回值)
  • 左值:可以取地址(包括:变量)

下面几种表述是错误的:

  • 赋值符号左边的就是左值——与下面的同一个例子😅

  • 能修改的就是左值——自定义类型且重载了operator+

    	std::string s1("123");std::string s2("456");std::string s3("789");s1 + s2 = s3;
    

    这是因为重载了+号,传值返回会生成一个匿名对象,然后s3是对这个匿名对象赋值了,但是这个匿名对象在这行结束之后就会销毁

  • 在赋值符号右边的就是右值——左值既能出现在左边又能出现在右边

int add(int x, int y){return x + y;}int a1 = 1;                       //1int* a2 = new int[10];            //2const int a3 = 1;                 //3int a4 = a1 + a3;                 //4int a5 = add(a1, a3);             //5

1、2、4、5等号右边的都是右值
1、2、3、4、5等号左边都是左值

右值又可以细分为:

  • 纯右值:内置类型表达式
  • 将亡值:自定义类型的表达值,即将被析构

这两个是后面识别右值的判别准则

右值引用VS左值引用

定义

  • 右值引用:对右值的引用
  • 左值引用:对左值的引用

注意区分:右值 VS 右值引用左值 VS 左值引用

	//左值 和 右值int a1 = 1;int* a2 = new int[10];const int a3 = 1;int a4 = a1 + a3;int a5 = add(a1, a3);//右值引用int&& r1 = 10;int&& r2 = a1 + a3;int&& r3 = add(a1, a3); //左值引用int& l1 = a1;const int& l2 = a3;int*& l3 = a2;

注意

  • 右值是不可以取地址,但是右值引用是可以取地址,因为给右值取别名之后必然会存储到特定位置,也就是说右值引用是左值!📌
  • 左值引用既可以引用左值也可以引用右值——引用右值可以使用const int &a=1;
  • 右值引用只能引用右值,不能引用左值
  • 右值引用可以引用move之后的左值

move函数

move函数的唯一作用就是将左值强制转换成右值,但是使用move时将自定义类型转成右值的时候一定要注意:如果该类型支持移动赋值 或 移动构造,可能会产生一些意想不到的后果

	string s1("i am student");string s2(move(s1));cout << s1 << s1.size() << endl;cout << s2 << s2.size()<<endl;

在这里插入图片描述
这里s1的空间就被释放掉了,所以move自定义类型的时候一定要谨慎!

左值引用&&右值引用 与 函数重载

左值引用和右值引用可以作为函数重载的依据,

  • 如果参数是左值—— 匹配左值引用
  • 如果参数是右值——优先匹配右值引用
  • 例如const int&这种既能匹配左值又能匹配右值的,右值会优先匹配右值引用,如果没有右值引用才会匹配它。
void fun(int& a)
{cout << "int& a" << endl;
}void fun(const int& a)
{cout << "const int& a" << endl;
}void fun(int&& a)
{cout << "const int&& a" << endl;
}int main()
{int a = 1;const int b = 2;fun(a);fun(b);fun(1);
}

在这里插入图片描述

模板

template<class T>
void forward(T&& t)
{//.........
}

在函数模板的情况下,传入不同类型的参数会如何?
模板里面的函数参数不论是&还是&& 既可以实例化成左值引用,又可以实例化成右值引用。
例如如下几个例子:

int a = 1;const int  b = 1;forward(a);               //这里T 为int   实例化后的函数参数为:int & tforward(move(a));          //这里T 为int   实例化后的函数参数为:int&& tforward(b);                 //这里T 为const int   实例化后的函数参数为:int & tforward(move(b));           //这里T 为const int   实例化后的函数参数为:int&& t

完美转发

我们用模板实例化的函数不管传入的是左值还是右值,在引用之后通通变成了左值。如何让函数模板在向其他函数传递参数时该如何保留该参数的左右值属性?
看一下下面这种情况:

void fun(int& a)
{cout << "int& a" << endl;
}
void fun(const int& a)
{cout << "const int& a" << endl;
}
void fun(int&& a)
{cout << "int&& a" << endl;
}
void fun(const int&& a)
{cout << "const int&& a" << endl;
}
template<class T>
void forward(T&& t)
{fun(t);
}
int main()
{int a = 1;const int  b = 1;forward(a);               //这里T 为int   实例化后的函数参数为:int & tforward(move(a));          //这里T 为int   实例化后的函数参数为:int&& tforward(b);                 //这里T 为const int   实例化后的函数参数为:int & tforward(move(b));           //这里T 为const int   实例化后的函数参数为:int&& t
}

结果我们发现结果为:
在这里插入图片描述
这是由于在引用之后丢失了右值的特性,我们可以使用完美转发:

void fun(int& a)
{cout << "int& a" << endl;
}void fun(const int& a)
{cout << "const int& a" << endl;
}void fun(int&& a)
{cout << "int&& a" << endl;
}void fun(const int&& a)
{cout << "const int&& a" << endl;
}template<class T>
void forward(T&& t)
{fun(forward<T>(t));
}int main()
{int a = 1;const int  b = 1;forward(a);               //这里T 为int   实例化后的函数参数为:int & tforward(move(a));          //这里T 为int   实例化后的函数参数为:int&& tforward(b);                 //这里T 为const int   实例化后的函数参数为:int & tforward(move(b));           //这里T 为const int   实例化后的函数参数为:int&& t
}

这时传入参数的特性就可以获得很好的保留:
在这里插入图片描述

左值引用的意义

string function(string& s)
{string tmp;//.........return tmp;
}

左值引用广泛引用于函数传参,可以不用拷贝,提升了效率,但是你在函数内对s修改同时会影响到外部s。
但是返回的时候一般的情况下是需要拷贝的,如果函数返回值是引用,那么就说明返回的这个对象的生命周期要大于函数的栈帧,也就是说返回的对象出了函数作用域不能被销毁,像上文中的tmp是无法使用引用传值返回的,tmp在函数还未返回时就会调用析构函数销毁

现在有如下一个情况:如果返回的tmp经过函数内部的操作变成一个无比巨大的字符串,如果返回必然会调用拷贝构造函数,造成很大的开销。有没有什么好方法减少开销?

  • 在没学右值引用时,我们使用输出型参数,在函数作用域外将返回的对象开好,再在函数参数列表引用传入,这时就可以跳过传值返回不必要的那次拷贝构造了。

    void function(string& s,string& tmp)
    {//.........
    }
    

右值引用又为我们提供了一个新的方法

移动构造&&移动赋值

我们来重新看一下这个函数的返回过程:
在这里插入图片描述

string s1("123");
string ret=function(s1);   //4string ret;
ret=function(s1);     //5
  1. 调用拷贝构造函数将tmp深拷贝给另一个临时对象,

    • 如果输出形式为4,那么编译器会将赋值重载优化掉,也就是将tmp直接拷贝构造给外部的ret
    • 如果输出形式为5,还会调用一次赋值重载(实际上也是深拷贝)实现ret赋值
      在这里插入图片描述
  2. 走到2的位置处,函数结束,销毁函数栈帧调用string的析构函数销毁tmp

实际上tmp里面存的就是ret需要的内容,但是ret出函数栈帧需要被销毁,所以需要将其深拷贝出来,再赋值给ret。

能不能让tmp中直接赋值给ret呢?——移动构造&&移动赋值

		string(string&& s)  //右值引用   移动构造{std::swap(_str, s._str);std::swap(_size,s._size);std::swap(_capacity,s._capacity);}
		string& operator=(string && s)   //右值引用 移动赋值{swap(*this, s);return *this;}

如果我们在string中添加如上两个函数,4、5两次赋值或拷贝会发生什么变化?
函数在返回tmp时,由于tmp出函数作用域就会调用析构函数销毁,所以会被识别成将亡值。将亡值属于右值,匹配 移动构造 和 移动赋值。
实际上过程还是和上面一模一样但是所有的拷贝构造都变成了移动构造,所有的赋值重载变成了移动赋值。
在这里插入图片描述

但是我们发现移动构造 和 移动拷贝 只是交换了资源,并没有开辟新的资源,将即将销毁的tmp中的内容与别的对象交换,从而实现资源的转移。比传统的深拷贝要节约了不少资源。

默认移动构造&&赋值

上面介绍了两个新的函数——移动构造函数 和 移动赋值函数,这两个也属于类的默认成员函数。

  • 默认移动构造
    生成条件

    1. 没有自己实现移动构造
    2. 没有实现析构函数、拷贝构造、拷贝赋值重载

    那么编译器会默认生成一个 ,对于内置类型会按字节拷贝,对于自定义类型会调用该类型的移动构造(如果存在的话,不存在的话就是拷贝构造)

  • 默认移动赋值
    生成条件

    1. 没有自己实现移动赋值
    2. 没有实现析构函数、拷贝构造、拷贝赋值重载

    那么编译器会默认生成一个 ,对于内置类型会按字节拷贝,对于自定义类型会调用该类型的移动赋值重载(如果存在的话,不存在的话就是普通赋值重载)


http://www.ppmy.cn/news/32512.html

相关文章

替代notepad++,notepad--介绍及插件cmake编译

Notepad 是一个文本编辑器小软件&#xff0c;用来替代windows自带的记事本。然而Notepad软件的作者是台湾省人&#xff0c;其具有明显的gd/jd/td倾向&#xff0c;如果你不赞同他的观点&#xff0c;Notepad将会在你的源码里面插入随机字符。推荐一款国产的开源跨平台软件NDD(not…

运筹系列67:大规模TSP问题的EAX遗传算法

1. 算法介绍 EAX是edge assembly crossover 算子的缩写。本算法有Y nagata教授公布&#xff0c;目前在VLSI最大的几个案例上获得了best的成绩。另外目前MonoLisa 100K问题的最优解也是由其公布&#xff0c;若能得到更优解&#xff0c;可以获得1000美元奖励。 算法步骤如下&…

第十四届蓝桥杯三月真题刷题训练——第 13 天

目录 第 1 题&#xff1a;特殊日期 问题描述 答案提交 运行限制 代码&#xff1a; 思路&#xff1a; 第 2 题&#xff1a;重合次数 问题描述 答案提交 运行限制 代码&#xff1a; 第 3 题&#xff1a;左移右移 问题描述 输入格式 输出格式 样例输入 样例输出…

四、HikariCP 源码分析之初始化分析一

HikariDataSource 的初始化 HikariDataSource是 HikariCP 开放给用户使用连接池的主要操作类。所以,我们创建一个 HikariCP 的连接池,其实就是构造一个HikariDataSource。 两个构造函数 它有两个构造函数:

HTTP 3.0来了,UDP取代TCP成为基础协议,TCP究竟输在哪里?

TCP 是 Internet 上使用和部署最广泛的协议之一&#xff0c;多年来一直被视为网络基石&#xff0c;随着HTTP/3正式被标准化&#xff0c;QUIC协议成功“上位”&#xff0c;UDP“取代”TCP成为基础协议&#xff0c;TCP究竟“输”在哪里&#xff1f; HTTP/3 采用了谷歌多年探索的基…

GPT-4,终于来了!

就在昨天凌晨&#xff0c;OpenAI发布了多模态预训练大模型GPT-4。 这不昨天一觉醒来&#xff0c;GPT-4都快刷屏了&#xff0c;不管是在朋友圈还是网络上都看到了很多信息和文章。 GPT是Generative Pre-trained Transformer的缩写&#xff0c;也即生成型预训练变换模型的意思。…

【华为OD机试真题 JAVA】GPU算力问题

标题:GPU算力问题 | 时间限制:1秒 | 内存限制:262144K | 语言限制:不限 为了充分发挥Gpu算力, 需要尽可能多的将任务交给GPU执行, 现在有一个任务数组, 数组元素表示在这1s内新增的任务个数, 且每秒都有新增任务, 假设GPU最多一次执行n个任务, 一次执行…

蓝桥杯第五天刷题

第一题&#xff1a;数的分解题目描述本题为填空题&#xff0c;只需要算出结果后&#xff0c;在代码中使用输出语句将所填结果输出即可。把 2019 分解成 3 个各不相同的正整数之和&#xff0c;并且要求每个正整数都不包含数字 2和 4&#xff0c;一共有多少种不同的分解方法&…