深圳外贸网站开发公司,网页视频怎么下载高清,wordpress无法目录下,房山区做网站#x1f308; 个人主页#xff1a;谁在夜里看海. #x1f525; 个人专栏#xff1a;《C系列》《Linux系列》 ⛰️ 天高地阔#xff0c;欲往观之。
目录 前言#xff1a;对引用的底层理解
一、左值与右值
提问#xff1a;左值在左#xff0c;右值在右#xff1f;… 个人主页谁在夜里看海. 个人专栏《C系列》《Linux系列》 ⛰️ 天高地阔欲往观之。
目录 前言对引用的底层理解
一、左值与右值
提问左值在左右值在右
二、左值引用与右值引用
1.提问右值引用为左值
2.不能取地址≠没有地址
3.左右值引用的绑定
4.左右值引用的比较
三、右值引用的意义
1.左值引用的使用场景
作为函数参数
作为函数返回值
2.左值引用的局限
3.右值引用和移动语义 前言对引用的底层理解
在区分左右值引用之前我先补充一下对引用的理解。
相较于C语言C引入了一种语法引用我们需要了解的是为什么C语言没有引用而C有呢
在C语言中设计者希望语言保持简单并且支持直接操作内存因此选择使用指针完成数据的传递通过指针C语言可以实现对变量的直接访问和修改
在C中引入了更高级抽象机制引用作为一种高级抽象比指针更安全、易用并且在实现参数传递和返回值时不需要、*操作符更符合直观语义便于面向对象编程。
引用被看作一种别名在上层是变量的别名但在底层其实是地址的别名为什么这么说呢
在语言层面int num 10表示创建一个int类型的变量num并初始化为10
但是跳出高级语言层面我们来看底层num并不是什么变量名称num对应了一个地址是一个位于进程地址空间栈区的地址int也不是什么类型它表示从该地址往后的4字节的空间被进程使用了要以4字节为一个整体修改该地址上的内容而字面常量10呢用二进制表示00001010根据辅助对象的不同进行提升或截断10要赋值给int对象先被提升成32bit即4字节存储时将这些字面值从正文代码区拷贝到num对应的栈区地址上。
所以引用实际上是地址的别名是与地址建立的一种映射关系我们可以通过不同的别名访问同一块地址空间由于引用并不直接接触地址这使得程序出错的可能性减少安全性也提高了。
说完了引用我们来说一下左值右值
一、左值与右值
左值与右值统称为值类别它们都是表示数据的表达式而左右的区分决定了表达式的使用方式为什么这么说呢下面来左值右值的特点就知道了
左值可以获取地址并且可以对其赋值。如变量名、数组指针等 int a 10; // a 是左值a 有效
a 15; // 可以对左值进行赋值
int arr[5] {1, 2, 3, 4, 5};
arr[2] 10; // arr[2] 是左值可以被赋值class MyClass {
public:int value;
};MyClass obj;
obj.value 5; // obj.value 是左值右值不可以取地址也不能对其赋值。如字面常量、表达式返回值、函数返回值 int x 5; // 5 是右值不能取地址
std::string(temporary); // 这是一个右值不能取地址
int y x 10; // x 10 是右值不能取地址提问左值在左右值在右
左值只能出现在 左边右值只能出现在 右边吗
虽然这种说法很符合左右值取名的定义但是这种说法是不准确的 int a 10;
int b a; // a是左值但是在右边
// 10 a; // 报错左边必须为左值
赋值符号 左边必须是左值右值不行并且是可修改的左值const修饰的左值不行
二、左值引用与右值引用
传统的C语法中就有引用的语法而C11中新增了右值的引用语法特征下面这些都是左值引用的情况 int main(){// 以下的p、b、c、*p都是左值
int* p new int(0);int b 1;const int c 2;// 以下几个是对上面左值的左值引用
int* rp p;int rb b;const int rc c;int pvalue *p;return 0;} 那右值引用该怎么用呢我们怎么对字面常量 10 进行引用呢左值引用是在类型后面加右值引用就是在类型后面加 int main()
{double x 1.1, y 2.2;// 以下几个都是常见的右值10;x y;fmin(x, y);// 以下几个都是对右值的右值引用int rr1 10;double rr2 x y;double rr3 fmin(x, y);// 这里编译会报错error C2106: “”: 左操作数必须为左值10 1;x y 1;fmin(x, y) 1;return 0;
} 1.提问右值引用为左值
左值引用是左值吗右值引用是右值吗
引用作为表达式的别名它本身也是一个表达式所以也有左右值之分要进行区分我们对它进行取地址看看可不可行
我们发现ra作为左值引用rb作为右值引用它们都可以被取地址并且赋值说明它们都是左值这就很奇妙了左值引用为左值并不奇怪但是右值引用也是左值这是为什么呢
要了解原因我们就得从左值右值的底层入手
2.不能取地址≠没有地址
我们知道字面常量如 2作为右值是不能取地址的也就是10这种做法是被禁止的但是右值不能被取地址就代表它没有地址吗显然不是
我们上面提到过int num 2; 这段代码被编译后会放到进程空间的正文代码区那么系统怎么知道你要用2去初始化num呢因为正文代码区存储了10的二进制序列以及它要放入的地址信息以及把10放入该地址的指令。
回头看这个规则右值不能被取地址2有地址吗当然右如果没有地址系统怎么知道初始化的值是2。所以不能取地址不是因为没有地址而是因为这个地址指向只读数据区该地址上的数据只有在程序运行后才会被系统读取由于数据不能被修改所以编译器禁止取地址操作取到地址就可以凭借地址对数据进行篡改于是编译失败。
2的地址是禁止访问的但是rb作为2的右值引用却可以进行地址访问这不应该啊唯一合理解释就是右值的引用与右值并不共用一块地址。
3.左右值引用的绑定
左值引用例如int ref a;确实直接绑定到左值的地址即原始对象的内存位置。这意味着左值引用和原始对象共享同一个地址
但是右值引用本身并不是直接对右值地址的引用而是编译器会分配一个新的存储地址将右值的值拷贝到该位置。
因此可以作如下区分
1️⃣左值引用绑定左值的 值地址
2️⃣右值引用只绑定右值的 值不绑定地址额外分配一块地址
4.左右值引用的比较
左值引用
1.左值引用只能引用左值不能引用右值。
2.但是const左值既可以引用左值也可以引用右值 int main()
{// 左值引用只能引用左值不能引用右值。int a 10;int ra1 a; // ra为a的别名//int ra2 10; // 编译失败因为10是右值// const左值引用既可引用左值也可引用右值。const int ra3 10;const int ra4 a;return 0;
} 右值引用
1.右值引用只能引用右值不能引用左值
2.但是右值引用可以引用move以后的左值move将左值转化成右值 int main()
{// 右值引用只能右值不能引用左值。int r1 10;// error C2440: “初始化”: 无法从“int”转换为“int ”// message : 无法将左值绑定到右值引用int a 10;int r2 a;// 右值引用可以引用move以后的左值int r3 std::move(a);return 0;
} 三、右值引用的意义
右值引用到底有什么意义呢C11为什么要推出右值引用这个概念呢
在右值引用出现之前只存在左值引用那么就说明左值引用存在短板需要右值引用来补齐。
1.左值引用的使用场景
作为函数参数
我们用对象作为参数传递的时候使用左值引用可以避免对象的拷贝在传递较大对象或包含复杂数据结构的对象时可以显著提高效率下面用一个自定义string类来演示出现拷贝构造时会打印信息 // 拷贝构造string(const string s):_str(nullptr){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;}
可以看到使用左值引用避免了一次拷贝构造深拷贝
作为函数返回值
左值引用也可以作为函数的返回值从而可以通过函数调用直接操作该变量比如访问数组元素、链表节点这戏 #include iostreamint getElement(int arr[], int index) {return arr[index]; // 返回数组元素的引用
}int main() {int arr[5] {1, 2, 3, 4, 5};getElement(arr, 2) 10; // 修改返回的元素std::cout arr[2] std::endl; // 输出10return 0;
}但是左值引用作为函数返回值的情况有一个局限那就是当函数返回对象是一个局部变量出了函数作用域就不存在了就不能使用左值引用返回只能传值返回。这会有什么影响呢
2.左值引用的局限
当我们用传值返回的方式返回一个局部对象例如下面这个函数将整形转成字符串 my::string to_string(int value)
{bool flag true;if (value 0){flag false;value 0 - value;}my::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;
}int main()
{my::string str to_string(123);
} 上面这种情况会进行几次拷贝构造编译器说是一次但其实是两次这里是编译器进行优化了
由于对象作为局部变量在函数结束时就会销毁所以要想保留对象的内容就需要一个临时对象来接收即返回对象ret此时就会调用一次拷贝构造将局部变量的内容拷贝到返回对象中返回对象也只是临时的它的作用就是在外部需要接收时再将内容拷贝构造给新的对象所以总共是发生了两次拷贝构造
不过现在的编译器会优化成一次拷贝构造将局部对象直接作为函数临时对象拷贝给接收对象 无论如何至少都要进行一次深拷贝面对较大对象时会很大程度上影响性能那么可不可以不多这一次拷贝构造呢就是将局部对象的内容直接传给外部接收对象左值引用不能做到的事情右值引用可以做到。
3.右值引用和移动语义
上述拷贝构造函数的参数都是左值引用所以我们需要重新定义拷贝构造函数其参数列表为右值引用。 在左值引用传参的拷贝构造函数中由于左值引用传递的对象仍然在其他地方使用所以我们需要定义一个临时对象tmp开辟一块新的空间将传入参数的数据安全地拷贝到tmp中然后通过swap将tmp的内容与this对象进行交换这种拷贝称为深拷贝。
而在右值引用传参的拷贝构造函数中由于右值引用传递的对象例如临时变量即将销毁我们可以直接“窃取”其资源就不用深拷贝了所以它叫做移动拷贝将别人的资源转移到自己身上。 // 移动构造string(string s):_str(nullptr), _size(0), _capacity(0){cout string(string s) -- 移动语义 endl;swap(s);}// 移动赋值string operator(string s){cout string operator(string s) -- 移动语义 endl;swap(s);return *this;} 如此一来大大提升了效率。 以上就是对左值引用与右值引用的介绍与个人理解欢迎指正~
码文不易还请多多关注支持这是我持续创作的最大动力