淮安市建设银行网站,北京公司注册哪个园区免费,韩国能否出线,wordpress 用户当你在学习语言的时候#xff0c;是否经常听到过一种说法,左边的叫做左值#xff0c;右边的叫做右值。这句话对吗#xff1f;从某种意义上来说#xff0c;这句话只是说对了一部分。---前言一、什么是左右值?通常认为:左值是一个表示数据的表达式(…当你在学习语言的时候是否经常听到过一种说法,左边的叫做左值右边的叫做右值。这句话对吗从某种意义上来说这句话只是说对了一部分。---前言一、什么是左右值?通常认为:左值是一个表示数据的表达式(如变量名或解引用的指针)我们可以获取它的地址可以对它赋值(使用空间)左值可以出现赋值符号的左边也可以出现在等号右边。右值也是一个表示数据的表达式如字面常量、表达式返回值函数返回值等等。右值可以出现在赋值符号的右边但是不能出现在赋值符号的左边右值不能取地址。    //x \ y 都是左值 都可以取地址double x  1.1, y  2.2;int a  10,b  20;//以下都是右值 都不用取地址10;x  y;func();二、左右值引用(1)左值引用type x;在我们学习引用的时候一定会和C语言的指针联系到一起。我们来看看下面的swap代码吧。void SwapByPtr(int* a,int* b)
{int tmp  *a;*a  *b;*b  tmp;
}void SwapByVal(int a, int b)
{int tmp  a;a  b;b  a;
}结果我想你一定知道的函数传值与函数传地址是不同的一个是一份拷贝一个是记录的地址可以访问原变量。但是我们知道指针是有它的缺陷如果不是一位资深程序员甚至你是也得对指针的使用报以敬畏之心。因此在C中引入了新的语法引用。虽然它底层仍然是用指针实现的但是却比指针用起来更加方便。void SwapByRef(int a,int b)
{int tmp  a;a  b;b  a;
}(2)右值引用我们时常说引用,引用其实都是左值引用。为了区别左值引用呢右值引用的语法格式上是这样的。type ;    int a  10;int ra  a;         //左值引用int rra  10;      //右值引用(3)左右值引用的特性左值引用:①只能引用左值不能引用右值②但是const左值引用 可以引用右值也可以引用左值    int a  10;int ra  a;          //只能引用左值int rb1  10;        //不能引用右值 ×//既可以引用左值、也可以引用右值const int rb2  a;const int rb2  10; 右值引用:①右值引用只能引用右值不能引用左值②标准库中提供move()函数可以将一个左值变为右值    int a  10;int rra1  10;    //只能引用右值 int rra2  a;     //不能引用左值 ×//move后可以 a变成了右值int rra3  std::move(a);右值不能取地址但是右值引用能够取地址!!右值当然没有地址但是我们给右值取引用时那么这个右值引用就该有它的地址并且可以对它引用的对象进行修改。如果你不想允许让对右值引用的值发生改变请给它const吧。 为什么这么设计呢这和右值引用的场景有关也就是我们之后要细讲的。当然这很符合我们的预期。三、左右值引用的应用场景也许你会疑问已经有了左值引用为什么还需要右值引用呢右值引用一定有它存在必要的场景。在此之前我们就先来列举列举左值引用的使用场景吧。左值引用场景:①函数传参防拷贝。②函数返回值 引用返回。//函数传参防拷贝
vectorint Func(vectorint ret)
{ret.push_back(1);//...//函数引用返回值return ret;
}当要进行左值引用返回时唯一一个条件时该对象出了作用域仍然存在那如果该对象就是在函数体内创建的出了作用域它就会销毁但其拷贝的代价又很大。遇到这样的情况我们应该怎么处理呢(1)移动赋值与移动构造我们首先实现一个to_string的函数用来将一个数字转换为自定义字符串。//to_string函数string to_string(int value){bool flag  true;if (value  0){flag  false;value  0 - value;}dy::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;}//自定义string 类class string{public:typedef char* iterator;iterator begin(){return _str;}iterator end(){return _str  _size;}string(const char* str  ):_size(strlen(str)), _capacity(_size){_str  new char[_capacity  1];strcpy(_str, str);}// s1.swap(s2)void swap(string s){::swap(_str, s._str);::swap(_size, s._size);::swap(_capacity, s._capacity);}// 拷贝构造string(const string s){cout  string(const string s) -- 深拷贝  endl;string tmp(s._str);swap(tmp);}// 赋值重载string operator(const string s){cout  string operator(string s) -- 深拷贝  endl;string tmp(s);swap(tmp);return *this;}~string(){delete[] _str;_str  nullptr;}char operator[](size_t pos){assert(pos  _size);return _str[pos];}void reserve(size_t n){if (n  _capacity){char* tmp  new char[n  1];strcpy(tmp, _str);delete[] _str;_str  tmp;_capacity  n;}}void push_back(char ch){if (_size  _capacity){size_t newcapacity  _capacity  0 ? 4 : _capacity * 2;reserve(newcapacity);}_str[_size]  ch;_size;_str[_size]  \0;}//string operator(char ch)string operator(char ch){push_back(ch);return *this;}const char* c_str() const{return _str;}private:char* _str  nullptr;size_t _size  0;size_t _capacity  0; // 不包含最后做标识的\0};我们此时用一个整数使用to_string函数得到一个自定义string类型。int main()
{int x  1234;dy::string ret  dy::to_string(x);return 0;
}但是我们为了一个在to_string函数类一个出作用域就会销毁的对象为了得到它其中的资源就得付出深拷贝一份的代价未免有些太大。如果仅仅是拷贝内置类型来说那么微乎其微但如果深拷贝对象是map、set呢也许你仅仅只需要得到这个即将销毁对象的根节点即可而非是在return返回时让该对象拷贝构造临时对象而付出巨大代价。秉持这样的想法我们为该自定义类设计一个新的拷贝构造函数。        //移动赋值与移动构造string(string s){cout  string(string s): 移动构造  endl;swap(s);}string operator(string s){cout  string operator(string s) 移动赋值  endl;swap(s);return *this;}我们为该类增加这两个函数并再次运行相同的代码。这是为什么该对象的拷贝没有选择去调用深拷贝那么我们不得不搞懂以下的三个问题能够搞懂上述的问题我们也就能够预知编译器会选择怎样做。那如果是以下这样的调用会打印出什么呢int main()
{    dy::string ret2;ret2  dy::to_string(123);return 0;
}小结:左值引用与右值引用减少拷贝的方式是不一样的左值引用是直接起作用的就是给一个变量取别名。右值引用是间接起作用的利用移动构造、移动赋值实现的是一种资源的转移。而被转移的资源也叫做将亡值。也就是出了这个作用域就会销毁的对象。四、左右值引用的其他应用(1)完美转发在前文已经提到过一旦给右值取别名时那么该右值引用名义上虽然是右值的别名但本质是一个可以取地址、甚至可以改变的左值。我们来看看如下的代码。void Func(int x)
{cout  左值引用  endl;
}void Func(int x)
{cout  右值引用  endl;
}void GetFunc(int x)
{Func(x);
}int main()
{int a  10;GetFunc(10);return 0;
}唔我们分别重载了两个函数Func,一个是用来接收左值引用的、一个是来接收右值引用的我们传进来的是一个右值10那么很显然调用后打印的是 右值引用。当右值引用作为参数时虽然名义上接收的是右值但是向下传递时已经改变为了左值。但是我们就想让它保持原有的属性。C库中给提供了一个函数转发std::forwardtype();我们也就可以看到如我们的预期结果。(2)万能引用函数参数有左值引用、也有右值引用C中也有模板那是否模板也有模板左值引用与模板右值引用呢 是的templateclass T
void PerfectFunc(T x)
{Func(x);
}templateclass T
void PerfectFunc(const T x)
{Func(x);
}但其实这都用得不多。因为接下来的操作可能会惊掉你的下把。templateclass T
void PerfectFunc(T x)
{Func(x);
}这什么鬼在有模板的情况下;templateclass Tvoid Func(T ..); 就叫做 万能引用当然如果你没好好阅读上文你可能还会惊奇为什么只会调用左值引用与const左值引用。我们只需要让向下传入的值保持原属性即可。由此可见我们能万能引用的情况下肯定不会去选择T这单调的左值引用参数。总结:①左右值区分的最根本方法是能否取地址能否使用它的空间。②左值引用只能引用左值,右值引用只能引用右值。但是const 左值引用可以引用左值 也可以引用右值。③右值一定没有地址并且不能修改但是右值引用有它自己的地址非const可以进行修改。④左值引用的防拷贝方式更加直接显著。右值引用防拷贝的方式是间接的也叫资源转移。⑤std::move()可以将一个左值变为右值。std::forwardT()能保持参数的原属性。本篇到此结束感谢你的阅读。祝你好运向阳而生~