掌握C++的左值引用和初识右值引用

一、引用和指针的区别?

1、左值引用和右值引用

2、引用的实例

 

 

1、引用是更安全的指针

(1)安全性:

引用是必须初始化的,指针可以不初始化。

 引用能够保证一定能够引用到一个内存(因为必须初始化嘛),而指针有可能是野指针,需要通过开发者自己来保证指针的正确性。所以引用比指针更安全些。

(2)指针性

#include<iostream>

using namespace std;

int main() 
{
    int a = 10;
    int *p = &a;    // 指针指向a内存,因为指针有自己单独的内存,所以你需要取a的地址。这里的&叫“取地址运算符”,p是指针类型变量,所以p存a的地址
    int &b = a;     // 引用变量的作用就是起别名,相当于给a这块内存取了个别名b,这块内存叫a也可以,叫b也可以

    *p = 20;
    cout << a << " " << *p << " " << b << endl;     //访问的都是同一块内存

    b = 30;
    cout << a << " " << *p << " " << b << endl;     //访问的都是同一块内存

    return 0;
}
输出结果:
20 20 20 30 30 30

注意:这里修改p的值,不会影响a的值,修改a的值,也不会影响p的值,因为p存的是地址,a存的是数据,两者无关。但是修改*p会影响a的值,或者修改a会影响*p的值,因为p是存a的地址,*p表示取a中的数据,a和*p互相等同,息息相关。

 


在汇编层面上看,没有引用这一说,引用变量在汇编中对应的操作就是跟指针变量一样的。

 当我们为引用变量赋值,b = 30,在汇编层面上,编译器会自动为引用变量做一个解引用的操作(因为本来在汇编层面上,引用变量≈指针变量,b = 30,又没有*,那编译器就得给它加上),所以本质上b = 30并不是给b赋值,而是给b所指向的内存赋值。

 b = 30转成汇编语言的第二句话,是对eax,也就是对a的地址进行了一个解引用的操作(得到a的内存),然后把1Eh(即30)赋给dword ptr [eax](即a的内存)来修改a的内存的值。

int *p = &a转成汇编的第二句话,dword ptr [p]应该是对p进行一个解引用的操作,即得到p这个地址所对应的内存,然后把eax的值(即a的地址)放进去。

综上,引用是更安全的指针。 

int &c = 20;  这个语句是无法运行成功的,根据上面引用所对应的汇编代码,是需要先取20的地址,然后将其地址放到引用变量c的内存中,但是20是没有地址的呀!!它只是一个立即数(常量),所以这句话是无法运行成功的。

 

2、引用只有一级引用,没有多级引用,指针可以有一级指针,也可以有多级指针

3、定义一个引用变量,和定义一个指针变量,其汇编指令是一模一样的;通过引用变量修改所引用内存的值,和通过指针解引用修改指针指向的内存的值,其底层指令也是一模一样的

 

二、swap函数

1、错误的写法

#include <iostream>

using namespace std;

void swap(int *x, int *y) {
    int *tmp = x;
    x = y;
    y = tmp;
}

int main() {
    int a = 20, b = 5;
    swap(&a, &b);
    cout << "a:"<< a << "  ";
    cout << "b:"<< b ;
}
错误写法
#include <iostream>

using namespace std;

void swap(int *x, int *y) {
    cout << "swap()---start---"<<endl;
    int *tmp = x;
    cout << "*tmp: "<<*tmp << endl;
    x = y;
    cout << "*x: "<< *x <<endl;
    y = tmp;
    cout << "*y: " << *y << endl;
    cout << "swap()---end---" << endl<< endl;
}

int main() {
    int a = 20, b = 5;
    swap(&a, &b);
    cout << "a:"<< a << "  ";
    cout << "b:"<< b ;
}




输出结果:
swap()---start---
*tmp: 20
*x: 5
*y: 20
swap()---end---

a:20  b:5
对错误代码的分析

分析:从输出结果看出来,*x是等于5,并不是意味着*x和*y进行了交换(*表示解引用,*x表示解开x的值,即解开a的地址,取出a内存中的值,所以*x表示x指针所指向的内存的值)!!!!而是说明x和y进行了交换,x和y是两个int *型的指针,指针也有自己的内存,它们存放的分别是a和b的地址,上面的那种写法,只是对指针的内存的内容(a和b的地址)进行交换,而不是对指针所指向的内存进行交换(即未对指针内存中的内容(即地址)所对应的内存的内容(即a和b的值)进行交换)。

此错误的swap函数只对指针的内存进行了交换操作,并未对指针所指向的内存进行交换操作。

 

2、正确的两种写法

#include <iostream>

using namespace std;

void swap(int *x, int *y) {
    int tmp = *x;
    *x = *y;
    *y = tmp;
}

int main() {
    int a = 20, b = 5;
    swap(&a, &b);
    cout << "a:"<< a << "  ";
    cout << "b:"<< b ;
}
正确的写法一:用指针
#include <iostream>

using namespace std;

void swap(int &x, int &y) {     // 汇编层面,引用变量 ≈ 指针变量
    int tmp = x;    // 编译器仍会对x进行一个解引用的操作
    x = y;
    y = tmp;
}

int main() {
    int a = 20, b = 5;
    swap(a, b);     // 传过去仍然是a和b的地址
    cout << "a:"<< a << "  ";
    cout << "b:"<< b ;
}
正确的写法二:用引用

其实从汇编角度上看,写法一和写法二完全一样,只不过引用会保证必有初始值,会更安全些,所以写法二会好一点。

 

三、定义一个引用变量来引用数组

要定义引用变量的话,你可以先定义指针变量。

int a;

int *p = &a;

把右边的&放到左边,覆盖掉*,就成了引用变量。

int &p = a;



那定义一个引用变量来引用数组要怎么做呢?

int array[5] = { };

int (*q)[5] = &array;  // 用一个数组指针来指向&array,普通的指针是指向不了的,因为&array的类型是int (*) [5]类型,即Point array int [5]类型。

那改成引用变量跟上面同理,用&去覆盖*

int (&m)[5] = array;

引用变量跟指针变量有一点点不同的地方,就是引用变量m是作为别名,array的别名,也就是说,m和array是一回事。所以array和m所占的内存大小,都是一个数组的大小。

sizeof(array)  20

sizeof(q)    4

sizeof(m)    20

 

四、为什么要分左值和右值呢?

 

int main() {
 int a = 10;// a是左值,它有内存,有名字,值可以修改的
 int &b = a;// 是一个左值引用。因为有内存,所以可以被引用
 
 //int &c = 20;// 20是右值:没内存,没名字
 //C++11提供了右值引用(对于对象的效率提升非常巨大)
 int && c = 20;// 右值引用来引用右值
 c = 30;
 
 int &e = c;// 一个右值引用变量,本身是一个左值,因为它有内存、有名字嘛
 
 const int &d = 20;// 如果是左值引用,直接引用是不行的,但是我们可以通过const这个引用来引用这个常量
 /*
定义一个引用变量就要把右边的地址给存起来?NO!不是的!!
这里加了const之后,它是产生临时量,先把20存起来,再把临时量的地址放到b的内存里面
int temp = 20;
temp -> b;
 */
}
View Code
 
 

右值引用和左值引用在汇编层面上的指令是一样的,也就是上面定义c和d的指令是一样的。

不一样的地方在于

引用变量成了一个常量,是常引用,被const修饰了,故d不能作为左值,不能被修改。

而c是可以被修改的,比如修改成c = 30

在这里这个区别点还体现不出它的好处,在面向对象那部分就可以体现出来了。

 

总结:

右值引用

1、int &&c = 20; 专门用来引用右值类型,指令上,可以自动产生临时量,然后直接应用临时量c = 40;

2、右值引用变量本身是一个左值,只能用左值引用来引用它

3、不能用右值引用,来引用一个左值。

右值引用在生成指令的时候,是必须生成临时量的。

左值自己本身就有内存,人家给你生成临时量干啥?(意思就是不需要生成临时量)。

所以右值引用就是用来引用右值的。

 

 

posted @ 2022-10-15 22:47  算法扫地僧  阅读(161)  评论(0)    收藏  举报