C++语言中的指针与引用

本文详细比较了C++中的指针和引用,探讨了它们的定义、初始化、赋值、级别、内存占用、函数参数传递等区别,指出引用是受限制的指针,更安全,适合在已知指向固定对象且不会改变的情况使用。
摘要由CSDN通过智能技术生成

!!指针!!是C语言的核心内容,而对于在C语言基础上研发的C++语言来说,<指针>同样是其一个重要的核心概念,相信无论是初学者还是资深程序员都一定对<指针>有所了解,而!!引用!!作为是C++在C<指针>的基础上更新改进的一个概念,是C语言所没有的,既然<引用>是在指针的基础上进行的改进,那么在使用它的时候有什么便利之处,<引用>就一定比<指针>好用吗?能用<指针>的地方就能用<引用>吗?本文通过多方面对比<引用>与<指针>的区别,探讨了两者的深层含义以及两者在平时使用时的一些注意事项。
=二、引用的概念=
!!首先我们先明确引用的定义。在定义一个引用时,程序是将应用与它的初始值进行绑定,而且引用本事不是一个对象,是不占用内存的,在使用的过程中可以认为其与绑定的变量是同一个东西。!!
然后我们先来了解一下引用的使用方法,为了便于理解并与指针进行对比我们同时也对指针进行定义。与定义指针十分相似,在定义引用时只需要将”*“替换为”&“,但在定义引用时要注意必须将其初始化,这一点在后续会进行详细讲解。
``` 
lang = c++
void main
{
    int var = 7;
    int *pVar = &var;
    int &refVar = var;
}
 ```
(NOTE)探讨:
我们都知道在定义变量的时候,是把变量值放到内存中,而在使用变量名的时候,在代码经过编译生成目标代码时,是用存放变量的地址代替变量名,所以变量名是不占内存的。在定义指针的时候,指针是指向某一块内存,除了指向的内存,其本身存储内容为所指向内存的地址,也是占用内存的。那么引用作为变量的别名是怎么实现的呢?其实从汇编过程来看,引用是在指针的基础上进行的一种改进,因此其实现是跟指针一样的,上边的代码的汇编语言如下所示:

```
lang = c++
    int var = 7;
00C23CF2  mov         dword ptr [var],7  
    int *pVar = &var;
00C23CF9  lea         eax,[var]  
00C23CFC  mov         dword ptr [pVar],eax  
    int &refVar = var;
00C23CFF  lea         eax,[var]  
00C23D02  mov         dword ptr [refVar],eax  
```
(NOTE)可以看到在使用指针与引用时,两者的动作完全一样,编译器都是将变量的地址赋给了一块新的内存区域,即无论是引用还是指针都是存放的变量的地址,即引用在语言内部是使用指针实现的,因此引用可以理解为操作受限的指针。
!!但是我们在平时使用的时候,都是认为引用就是变量的别名,是不占内存的,是指不占用对象空间,不要因为汇编结果将引用与指针混淆。引用就是一种概念,我们只需要知道其接口,具体是怎么实现的由编译器自己决定。这里的探讨只是针对汇编过程中的引用的实现。!!

=三、引用与指针的区别=

==1、两者的定义与概念不同==

 在上边我们已经讲过指针是一个变量,其内部存储的是变量的地址,而引用是原变量的一个别名,如下图所示,在一定程度上引用和变量实质上是同一个东西。
{F424063, layout = center, size = full}
==2、两者定义与初始化不同==
我们在定义指针的时候,可以对其进行初始化也可以不初始化或对其进行赋空值(NULL),但是引用需要我们在定义的时候就要对其进行初始化,且引用不能为NULL。
``` 
lang = c++
void main
{
    int *pVar; //合法
    int &refVar; //不合法
    int *pVar = NULL; //合法
    int &refVar = NULL; //不合法
}
 ```
(NOTE)在实际编程中,可以先对一个变量进行赋空值,然后将引用指向空值如下所示,如果不使用这个引用,程序在编译时不会报错,但是一旦使用使用这个引用就会出现错误,因此要绝对避免给引用赋空值。在编程时我们也默认忽略引用指向空值的可能性。

``` 
lang = c++
void main
{
    int *pVar = NULL;
    int &refVar = *pVar;
}
 ```
==3、两者的赋值操作不同==
对于指针,在定义之后我们还能改变指针的指向地址,改变指针的值,但是一旦进行初始化之后它的值就不能再改变。
``` 
lang = c++
void main
{
    int var = 7;
    int *pVar = &var;
    int &refVar = var;
    int var2 = 2202;
    pVar = &var2; //合法
    refVar = var2; //不合法
}
 ```
(WARNING)WARNING: 这里所说的不合法是指改变引用的值不合法,在实际编程中上述代码是可以编译的,但是在进行refVar = var2操作时,不仅改变了引用的值,也改变了变量var的值,即对var进行了重新赋值,引用refVar仍指向var,所以refVar的值也改变了,并不是类似指针一样改变了引用refVar的引用(只改变了refVar的值),引用一旦初始化就始终指向特定对象(!!从一而终的特性!!)。

==4、两者级别的不同==
我们知道指针可以是多级的,但是引用只能是一级,即引用不能再被引用。
``` 
lang = c++
void main
{
    int **pVar; // 合法
    int &&refVar; // 不合法
}
 ```
==5、在使用sizeof()结果不同==
我们在编程中经常使用sizeof()操作符快速获取对象或者类型所占的内存字节数,那么对指针和引用使用sizeof()的结果如下所示:
``` 
lang = c++
void main
{
    double var = 7;
    double *pVar = &var;
    double &refVar = var;

    std::cout << sizeof(var) << std::endl;  // 输出 8
    std::cout << sizeof(pVar) << std::endl; // 输出 4
    std::cout << sizeof(*pVar) << std::endl; // 输出 8
    std::cout << sizeof(refVar) << std::endl; // 输出 8
}
 ```
在对引用使用sizeof()时,相当于对其引用的变量使用sizeof(),其值始终与原变量保持一致,但是对指针pVar使用sizeof()时,则是对指针大小取值为4字节(指针的占用内存大小是根据当前CPU运行模式的寻址位数决定的)。
==6、自增运算结果不同==
在对指针和引用使用自增运算时,对于指针来说是指向变量var后面的内存内容,而引用则是变量var加1。同理在对指针和引用进行其他运算符操作时,指针指向对应的内存地址,而引用始终代表原变量的变化。
``` 
lang = c++
void main
{
    int var = 7;
    int *pVar = &var;
    int &refVar = var;

    pVar++;//指针指向变量内存的下一个地址内的值
    refVar++;//变量var加1
}
 ```
==7、两者作为函数参数时的不同==
指针和引用都可以作为函数的形参,这也是引用的主要用途之一,在使用指针的时候我们经常(必须)对指针进行判空,检查指针是否为空,但是引用则不需要。
``` 
lang = c++
void FunPtr(int *p)
{
    // 需要检查P是否为空
    if (p) 
    {
        // TODO
    }
}

void FunRef(int &r)
{
    // 不需要检查r
    // TODO
}
 ```
函数的参数与返回值的传递方式有三种:值传递、指针传递、引用传递,如下所示;
``` 
lang = c++
int n = 1;
void Fun(int x)
{
    x = x + 99;
}
void FunPtr(int *p)
{
    *p = *p + 99;
}
void FunRef(int &ref)
{
    ref = ref + 10;
}
void main
{
    Fun(n);//函数内的x是变量n的一份拷贝,x发生变化时并不影响n,因此n仍未1
    FunPtr(&n);//函数指针指向的是变量n,因此在改变指针的指向内容时,变量n会发生变化为100
    FunRef(n);//函数内的ref是变量n的引用,所以ref和n在本质上可以看成一个东西,所以ref改变时n也会改变,此时n为110
}
 ```
可以看到引用在函数传参的时候,其性质类似于指针传递都改变了外部变量的值,但是形式却类似值传递。
==8、const 定义==
对于指针,在其定义前面加上const,如果const在最前则表示指针为常量指针,如果在*后则表示为指针常量。指针常量表示一个指针修饰的常量,指针所指向的内存地址不可更改(即该指针不能再指向其他的地址,但是所指向地址的内存内容可以修改)。而对于常量指针,表明这是一个指向常量的指针,即指针指向的内容不可修改,但是指针的指向可以改变。对于引用,是没有引用常量的,因为引用本来就不可更改,但是引用有常量引用,在上方的示例中,都是引用的变量,而在进行常量引用的时候,与指针有所不同。在定义指针的时候,我们知道不能将指针指向类型不同变量,但是引用在加上const后却可以。const引用可以用不同类型的对象初始化(只要能从一种类型转换到另一种类型),也可以是不可寻址的对象(文字常量)。
``` 
lang = c++
void main
{
    int var1 = 007;
    int var2 = 2022;
    int *const pVar1 = &var1;//指针常量
    *pVar1 = 100;
    pVar1 = &var2;//不合法

    const  int *pVar2 = &var1;//常量指针
    *pVar2 = 100;//不合法;
    pVar2 = &var2;//合法

    double *pVar3 = &var1;//不合法
    const double *pVar4 = &var1;//不合法

    double &refVar1 = var1;//不合法
    const double &refVar2 = var1;//合法
    
    int &refVar3= 100;//不合法
    const int &refVar4 = 100;//合法
}
 ```
解释这个的原因就需要我们深入了解引用的内部实现了,还记得开篇我们讲到过引用在汇编里的实现过程,实际上引用在内部存放的是一个对象的地址,对于不可寻址的值和不同类型的对象,编译器为了实现引用,必须生成一个临时的对象,引用实际上指向的是该临时对象,但是用户无法访问这个临时对象。上边的代码在内部的实现过程类似下方代码(在内部实现了类型转换):
``` 
lang = c++
void main
{
    int var = 7;

    double temp1 = var;//临时变量
    const double &refVar1 = temp1;

    int temp2 = 100;//临时变量
    const int &refVar2 = temp2;
}
 ```
=四、小结=
对比分析了这么多指针与引用的区别,那我们在什么情况下使用引用,什么情况下使用指针呢?其实我们不难发现无论在什么情况下引用能完成的事,指针一样也能够完成,那么为什么还要使用引用呢?我们都知道指针的强大,但是指针在其功能强大的背景下也有诸多不便,如果使用不当十分危险,而引用则可以理解为受限制的指针,其功能虽然有所缩小但是十分的安全。正所谓”杀鸡焉用宰牛刀“,用最合适的工具去完成最合适的事情。
使用指针的情况:
- 存在不指向任何对象的可能
- 需要在不同的时刻指向不同的对象
如果你明确知道总是指向一个对象并且一旦指向这个对象后就不会再改变指向的情况下,应该优先使用引用。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值