怎么制作营销网站福田网站建设推荐
怎么制作营销网站,福田网站建设推荐,wordpress小红心插件,编程app下载目录 前言#xff1a;一、左值引用和右值引用1.1 什么是左值和左值引用1.2 什么是右值和右值引用 二、左值引用和右值引用比较三、右值引用使用场景3.1 传值返回使用场景3.2 移动构造3.3 移动赋值3.4 STL容器接口也增加右值引用3.5 完美转发 前言#xff1a;
引用是给对象取… 目录 前言一、左值引用和右值引用1.1 什么是左值和左值引用1.2 什么是右值和右值引用 二、左值引用和右值引用比较三、右值引用使用场景3.1 传值返回使用场景3.2 移动构造3.3 移动赋值3.4 STL容器接口也增加右值引用3.5 完美转发 前言
引用是给对象取别名本质是为了减少拷贝。以前我们学习的引用都是左值引用右值引用是C11新增的语法它们的共同点都是给对象取别名。既然如此有了左值引用为什么还要有右值引用右值引用具体是怎样的以及它有哪些应用场景接下来会详细分析~~
一、左值引用和右值引用
1.1 什么是左值和左值引用
左值是一个表示数据的表达式可以是变量名、解引用的指针和前置。左值可以取地址和赋值它出现在赋值符号的左边。如果定义的左值被const修饰那么它就不能被赋值但是可以取地址。
//左值
int a 10;
const int b 20;
int* p new int(0);前置是左值是因为该运算符先进行自增再使用返回值还是它自己所以是左值 左值引用就是给左值的引用给左值取别名
//左值引用
int c a;
const int d b;
int* pp p;1.2 什么是右值和右值引用
右值也是一个表示数据的表达式可以是常量、表达式、函数返回值不能是左值引用返回和后置。右值不可以被赋值和取地址它出现在赋值符号的右边。
int x 1, y 2;
//右值
10;//常量
x y;//表达式
func(x, y);//函数返回值后置是右值是因为该运算符先使用再即它会返回当前没有自增的临时变量然后再自己 右值引用就是给右值的引用给右值取别名
//右值引用
int r1 10;
int r2 x y;
int r3 func(x,y);总结 左值是具有存储性质的对象是要占内存空间的右值是没有存储性质的对象也就是临时对象 判断是左值还是右值不能以是否可以赋值来确定右值是不可以赋值的左值没有const时可以有const时不行所以左值和右值的本质区别是能否取地址左值可以取地址右值不可以取地址。 二、左值引用和右值引用比较
前面说过左值引用是给左值取别名右值引用是给右值取别名那么有个小问题左值引用能给右值取别名吗右值引用又能否给左值取别名呢答案是可以的这里作了特殊处理
const左值引用可以给右值取别名右值引用可以给move(左值)取别名
const int a 10;
int p move(x);move函数的作用是强制把左值转换为右值 我们知道引用的最主要的作用是给对象取别名减少拷贝。既然左值引用都可以给左值和右值取别名那右值引用的出现有什么意义
先来看下左值引用有哪些应用场景
解决函数传参的拷贝问题。函数传参时如果没有左值引用就要进行拷贝有左值引用不需要拷贝。解决部分返回对象拷贝问题。返回对象出了函数作用域还在没有问题如果出了作用域就销毁了就有问题。
1️⃣函数传参
string operator(const string s)2️⃣返回的对象出了作用域还在
// 赋值重载
string operator(const string s)
{string tmp(s);swap(tmp);return *this;//this指针指向的成员变量的作用域在整个类中
}3️⃣返回的对象是局部的出了作用域就销毁
int Func()
{int b 10;return b;
}第一个和第二个没问题第三个就有问题返回对象是一个局部对象出了作用域就销毁用其他变量接收会出问题。
从这里可以发现函数返回一个对象时用左值引用在某些场景是不适合的但把左值引用去掉只能传值返回要拷贝。对上面的例子返回的是一个int类型的对象没有多大的消耗但是如果返回的对象消耗很大就影响效率比如
yss::string to_string(int value)
{bool flag true;if (value 0){flag false;value 0 - value;}yss::string str;//是局部对象while (value 0){int x value % 10;value / 10;str (0 x);}if (flag false){str -;}std::reverse(str.begin(), str.end());return str;//出了函数作用域就会销毁
}既然左值引用返回不行传值返回有拷贝存在那换成右值引用返回呢其实也是不行的。 因为就算把要返回的对象转换为右值还是避免不了返回对象出了作用域就销毁的情况。
三、右值引用使用场景
3.1 传值返回使用场景
怎么解决前面的问题呢先来看看传值返回的场景 在编译器没有作优化的情况下要返回的对象是局部的出了作用域就会销毁所以拷贝构造给临时对象临时对象是右值临时对象再拷贝构造给ret1。整个过程拷贝构造了两次拷贝了就算了第一次拷贝构造后str销毁了第二次拷贝构造后临时对象销毁了。也就是说产生的临时空间用完就将被销毁这样是不是太浪费资源了。所以编译器一般都会作优化处理尽可能的减少拷贝次数。先看下运行结果 只调用了一次拷贝构造
3.2 移动构造
有了编译器的优化拷贝的次数减少但还是不够。因此右值引用的就有它的用武之地了。先说明一下在前面的例子中用右值引用作返回值是不行因为没有解决局部对象出作用域就销毁的根本问题也就是说右值引用并不是像左值引用那样你用了就直接起作用右值引用是间接起作用的。
右值引用是怎么间接起作用的呢对比以下两个函数
//函数1
void func(const int x)
{cout void func(const int x) endl;
}
//函数2
void func(int x)
{cout void func(int x) endl;
}int main()
{int x 2;func(x);func(10);return 0;
}函数2是函数1的重载函数1的参数是左值引用函数2的是右值引用先注释掉函数2运行一下 第一次调用传入参数X第二次调用传入参数10都可以调用函数1这里其实也顺便验证了左值引用既可以引用左值参数x也可以引用右值常数10特殊处理的要记得带const。
取消注释函数1和函数2都在的情况下如何 传入参数x调用函数1参数为10调用函数2说明调用哪个函数是根据传的参数是左值还是右值决定的也就是哪个更合适用哪个。
在上面例子的基础上可以对拷贝构造进行重载变成移动构造移动构造的作用窃取别人的资源来构造自己。下面是拷贝构造和移动构造
// 拷贝构造
string(const string s)
{cout string(const string s) -- 深拷贝 endl;string tmp(s._str);//调用构造函数swap(tmp);
}// 移动构造
string(string s)
{cout string(string s) -- 移动构造 endl;swap(s);
}
/
yss::string ret1 yss::to_string(1234);在拷贝构造函数和移动构造函数都在的情况下运行只有移动构造也就是说没有拷贝了这得益于编译器的优化。
在编译器没有优化的情况下
编译器有优化的情况下:
对比下拷贝构造和移动构造
根据函数调用匹配原则如果传入的参数是左值调用的是拷贝构造如果传入的参数是右值调用的是移动构造。拷贝构造(深拷贝)是比较浪费资源的产生的临时对象tmp用完就销毁了移动构造只需将被拷贝对象的资源占为己有不需要深拷贝提高了效率。如果没有移动构造不管是左值还是右值都会调用拷贝构造也就是前面例子中返回对象有两次拷贝构造的情况假设没有优化
是不是所有的类都要有移动构造呢 首先要清楚的是移动构造是为了减少拷贝。也不是所有的拷贝都需要移动构造来解决如果是要开空间的(深拷贝)比如string类list等就要移动构造减少拷贝否则拷贝的消耗很大。如果是不需要开空间的(浅拷贝)比如日期类成员变量都是int类型像这样的内置类型直接拷贝即可。
总结
浅拷贝的类不需要移动构造深拷贝的类需要移动构造
3.3 移动赋值
右值引用不仅可以用在移动构造还可以用在移动赋值。如果一个对象已经存在调用的函数返回值赋值给这个对象就会调用移动赋值。
比如
yss::string ret1;
ret1 yss::to_string(1234);拷贝赋值(赋值重载)和移动赋值
// 赋值重载
string operator(const string s)
{cout string operator(string s) -- 深拷贝 endl;string tmp(s);//调用拷贝构造swap(tmp);return *this;
}// 移动赋值
string operator(string s)
{cout string operator(string s) -- 移动赋值 endl; swap(s);return *this;
}运行一下
接下来作几组对比 1️⃣没有移动构造和移动赋值
函数返回值是拷贝构造出来的临时对象再赋值给已经存在的对象ret1赋值的过程中调用赋值构造赋值构造里又有拷贝构造总共两次拷贝。
2️⃣有移动构造没有移动赋值 返回对象时是移动构造没有拷贝但是赋值时要调用拷贝构造
3️⃣有移动构造有移动赋值 返回对象时没有拷贝str是出了作用域就销毁直接给返回值返回值也是临时对象直接给ret1不需要拷贝减少了资源浪费效率提高。
拷贝赋值与移动赋值对比
如果没有移动赋值那么无论是左值还是右值都会调用拷贝复制这点与拷贝构造与移动构造相同根据函数调用匹配原则参数是左值调用拷贝赋值参数是右值调用移动赋值拷贝赋值会先调用拷贝构造再进行资源交换交换后那个临时的对象用完就销毁了整个过程比较浪费资源。移动赋值直接将自己的资源与临时对象的资源进行交换交换后自己原来的资源只需交给临时对象处理销毁
注有可能要赋值的对象不是临时对象即不是右值有可能是左值那么情况就会有变化(对应函数调用匹配原则)下面来看看是左值的
yss::string ret1;
yss::string ret2;//左值
ret1 ret2;运行
3.4 STL容器接口也增加右值引用
有了右值引用STL容器接口也作出了调整。以list的构造为例 不仅是构造函数在其他接口也有增加与右值引用相关的功能。通过STL中list的尾插函数来看
listyss::string lt;
yss::string s1(1111);lt.push_back(s1);//有名对象
cout ----------------- endl;
lt.push_back(yss::string(2222));//匿名对象
cout ----------------- endl;
lt.push_back(3333);//隐式类型转换有名对象是左值调用拷贝构造匿名对象和隐式类型转换构造拷贝构造-》构造是右值调用移动构造。当然在调用对应的构造函数前尾插函数传参的过程需要先看下 注有名对象——左值可以通过move转换为右值但是不要轻易使用因为一旦使用这个左值的资源将会被拿走 上面的list是C标准库中的list用我们之前模拟实现的list试下看有没有同样的效果。 第一个有两次拷贝构造是因为定义空的链表时也有拷贝构造第一个下面的深拷贝才是按照图示走的所以第一个上面的深拷贝暂时先忽略掉 发现全是深拷贝因为我们没有重载拷贝构造函数的传参为右值引用重载后再运行看看
ListNode(const T x T()):_prev(nullptr), _next(nullptr), _val(x)
{}ListNode(T x):_prev(nullptr), _next(nullptr), _val(x)
{}
//
//尾插
void push_back(const T x)
{insert(end(), x);
}
void push_back(T x)
{insert(end(), x);
}
///
//pos位置插入
iterator insert(iterator pos, const T x)
{Node* newnode new Node(x);//创建新节点//......
}
iterator insert(iterator pos, T x)
{Node* newnode new Node(x);//创建新节点//......
}为什么还全是深拷贝呢先来看一小段代码 右值引用接收右值常量10右值引用r可以自增也就是说右值引用r的属性是左值。根据这点所以前面的代码用右值引用参数接收后它的属性变成了左值左值再调用到下一个函数接收的是左值引用。这里需要修改下代码传参时move下让它的参数进入右值引用的变成左值后再重新变成右值
ListNode(T x):_prev(nullptr), _next(nullptr), _val(move(x))
{}
///
void push_back(T x)
{insert(end(), move(x));
}
///
iterator insert(iterator pos, T x)
{Node* newnode new Node(move(x));//创建新节点//......
}运行一下正是我们想要的结果。
那为什么右值引用后它的属性要变成左值呢 因为只有右值引用的属性是左值可以被改变资源才可以转移。
3.5 完美转发
模板中的万能引用—— 作用可以接收左值也可以接收右值
templateclass T
void PerfectForward(T t)
{cout void PerfectForward(T t) endl;
}int main()
{PerfectForward(10); // 右值int a 1;PerfectForward(a);// 左值PerfectForward(move(a)); // 右值return 0;
}注意万能引用虽然和右值引用都是两个取地址符但是要有所区分。右值引用接收右值或者是move后的左值万能引用左、右值都能接收包括const左值和const右值
const int b 8;
PerfectForward(b);// const 左值
PerfectForward(std::move(b)); // const 右值这样来看好像万能引用很不错但其实还是有些局限
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; }templatetypename T
void PerfectForward(T t)
{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;
}以上代码中我们的思路是传入右值在PerfectForward函数中调用的函数打印右值引用传入左值在PerfectForward函数中调用的函数打印左值引用传入const右值在PerfectForward函数中调用的函数打印const右值引用传入const左值在PerfectForward函数中调用的函数打印const左值引用。
运行结果
发现都是左值为什么因为万能引用只是接收了而已对后面该引用是左值引用还是右值引用就不归它管了。前面提过左值经过左值引用后还是左值右值经过右值引用后属性改变为左值。所以这段代码里无论左值进来还是右值进来最后都是调用左值引用的函数const对应const的。
既然这样那么在调用Fun函数时把参数move下行不行呢
Fun(move(t));全都是右值引用了……
为了解决该问题有一新语法完美转发——std::forward 作用在传参的过程中保留对象原生类型属性
Fun(std::forwardT(t));对比move和forward
move就是简单粗暴的把左值属性变成右值属性forward是保持原来的属性。如果本身是左值就不变如果本身是右值右值引用后属性会变成左值但是这里面的过程相当于被move了又变成了右值
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/pingmian/90343.shtml
如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!