C + + → 引用

C + + → 引用,第1张

目录

一、  引用的概念

 二、引用的特性

1. 引用在定义时必须初始化

2. 一个变量可以有多个引用

3. 引用一旦引用一个实体,再不能引用其他实体

三、使用场景

1.做参数

 2.做返回值

3.使用引用的两个好处

3.1 减少拷贝提高效率

 3.2可以修改返回值

四、常引用

五、引用和指针的区别


一、  引用的概念

        引用其实就相当于是一个人的外号或者小名,比如宋江的外号叫及时雨,林俊杰的叫JJ,我的小名叫狗蛋(谁愿意要这样的小名啊喂!!!)。

        引用本质上不是新定义一个变量,而是给已存在变量取了一个别名,编译器不会为引用变量开辟内存空间,它和它引用的变量共用同一块内存空间。

 [类型] + [&] + [引用变量名(对象名)]  =  [引用实体] ;   

int main()
{
    int a = 10;
    int& ra = a;//<====定义引用类型
    printf("%p\n", &a);
    printf("%p\n", &ra);
}

         下图一我们可以看到 a 和  ra 的地址是一样的,这也进一步说明了引用不会创建新变量,而只是一个别名。图二表示引用类型必须和引用实体是同种类型的,否则就会报错,编译不过。

 

 


 二、引用的特性

1. 引用在定义时必须初始化

2. 一个变量可以有多个引用
 int main()
{
	int a = 10;
	int& ra = a;
	int& rra = a;//可以有多个引用

	printf("%p %p %p\n", &a, &ra, &rra);
}

 

3. 引用一旦引用一个实体,再不能引用其他实体

 下图这种就属于赋值了,如果像指针一样用,前面加个 & (引用符号)会直接报错,它和指针的用法是略有不同的。


三、使用场景

1.做参数

下面有三种方式,传值,传址和引用,这样看引用是不是很方便呢?没有对比就没有伤害呀。

//这里是传值调用,参数是形参,
//形参是实参的一份临时拷贝,改变形参不会改变外面的实参
void Swap(int left, int right)
{
   int temp = left;
   left = right;
   right = temp;
}
//这里是传址调用,也就是传地址,指针指向原来的实参
//解引用后就找到实参的内容,就可以对实参进行修改
void Swap(int* left, int* right)
{
   int temp = *left;
   *left = *right;
   *right = temp;
}
//这里是引用,这里的参数是实参的别名,
//可以直接使用进行修改
void Swap(int& left, int& right)
{
   int temp = left;
   left = right;
   right = temp;
}
 2.做返回值

        做返回值时使用不当特别容易出错,这里会涉及一点函数栈帧的细节,且听我慢慢道来。

如下图二,在main函数里调用Count函数时,会开辟一个新的名为Count的函数栈帧,n是在Count栈帧里创建的临时变量,存在栈帧中,返回的时侯不是把n的地址返回给 ret ,而是把值传给一个叫寄存器的东西。因为函数调用完毕后会把栈帧销毁,而存在栈帧中的临时变量也随之被销毁(这里的销毁指把内存还给 *** 作系统,只是没有了使用权,而不是真正的销毁掉了)。这是n的值被放在寄存器中返回给ret,ret接受到了值就可以进行其他的 *** 作了。

        下图一为什么会加一个 static 修饰呢?如果没有加 static 修饰n的话,那么返回时会把 n 销毁掉,寄存器也有地址,由于返回类型是  int 的引用,会把寄存器当成 n 的别名返回给 ret ,因为 n 的空间已经还给 *** 作系统了,但是通过别名可以访问到 n 的空间,这时打印 ret 的值是随机的。用 static 修饰的 n 的空间不会被销毁,所以可以用引用做这里的返回值。

#include 
using namespace std;
int& Count()
{
   static int n = 0;
   n++;
   return n;
}

int main()
{
    int ret = Count();//这里返回的是n的别名
    cout << ret << endl;
     return 0;
}
#include 
using namespace std;
int Count()
{
   int n = 0;
   n++;
   return n;
}

int main()
{
    int ret = Count();
    cout << ret << endl;
     return 0;
}

注:如果函数返回时,出了函数作用域,如果返回对象还在(还没还给系统),则可以使用 引用返回,如果已经还给系统了,则必须使用传值返回。


  • 下图我们可以看到ret ,n ,x的地址是一样的,而 ret 调用第二次就变成了随机值,调用 Func 函数后 ret 的值变成了 Func 函数里 x 的值了。如果在实际项目中使用后果很严重。
  • 由此可以得出:出了函数作用域,返回变量不存在了,不能使用引用返回,因为引用返回的结果是未定义的。出了函数作用域,返回变量存在,才能使用引用返回。


3.使用引用的两个好处 3.1 减少拷贝提高效率

        下图可以看到引用返回的效率还是比较高的。(单位是毫秒)

 3.2可以修改返回值

下图可以看到我们通过 ret 直接对 n 的值进行了修改。


四、常引用

  • 一般引用做参数都是用 const 引用
//指针和引用赋值,权限可以缩小,但是不能放大
void Func(const int& num)//既可以接收普通值,也可以接收const修饰的值
{

}
int main()
{    
    int a = 0;
    //权限平移
    int& ra = a;

    const int b = 10;
    //我引用你,把权限放大了,是不行的
    int& rb = b;//会报错
    
    //我引用你,把权限缩小了,可行
    const int& rra = a;
    
    //我的权限缩小不代表你的权限也缩小了
    rra++;//会报错
    a++;//可以用

    Func(rra);
    Func(ra);
    Func(b);

    return 0;
}

五、引用和指针的区别

        在语法概念上引用就是一个别名,没有独立空间,和其引用实体共用同一块空间。

        在底层实现上实际是有空间的,因为引用是按照指针方式来实现的。

        下图可以看到,引用和指针的汇编指令是一样的。


引用和指针的不同点:

1. 引用在概念上定义一个变量的别名,指针存储一个变量地址。

2. 引用在定义时必须初始化,指针没有要求

3. 引用在初始化时引用一个实体后,就不能再引用其他实体,而指针可以在任何时候指向任何一个同类型实体

4. 没有NULL引用,但有NULL指针

5. 在sizeof中含义不同:引用结果为引用类型的大小,但指针始终是地址空间所占字节个数(32 位平台下占4个字节)

6. 引用自加即引用的实体增加1,指针自加即指针向后偏移一个类型的大小

7. 有多级指针,但是没有多级引用

8. 访问实体方式不同,指针需要显式解引用,引用编译器自己处理

9. 引用比指针使用起来相对更安全

        本章是我对引用的一些看法和了解,有不对的地方请大家批评指正。

欢迎分享,转载请注明来源:内存溢出

原文地址: http://outofmemory.cn/langs/3002846.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2022-09-27
下一篇 2022-09-27

发表评论

登录后才能评论

评论列表(0条)

保存