引用和指针详解以及它们的区别

一:引用

引用是为对象起了另外一个名字。定义引用时,程序把引用和它的初始值绑定在一起,而不是将初始值拷贝给引用。一旦初始化完成,引用将和它的初始值对象一直绑定在一起。因为无法令引用重新绑定到另外一个对象,因此引用必须初始化。因为引用本身不是一个对象,所以不能定义引用的引用。(注意区分引用的引用与右值引用的区别)。

对于引用,要遵守的规则是(有两种情况例外):

(1)引用的类型要和它绑定的对象严格匹配。如:

double dval = 3.14;
int &refval = dval;    //错误,此处的引用类型的初始值必须是int型对象。 (一定要类型匹配)               

(2)引用只能绑定在对象上,而不能与字面值或某个表达式的计算结果绑定在一起。

int &refval = 10;    //错误,引用类型的初始值必须是一个对象。

上面提到过,有两种类型是例外的,下面我们来看看是哪两种类型:

1,const引用(也就是常量引用) 

可以把引用绑定到const对象上,就像绑定到其他对象一样,我们称之为对常量的引用。与普通引用不同的是,对常量的引用不能被用作修改它所绑定的对象

const int ci = 1024;
const int &r1 = ci;    //正确,引用及其对应的对象都是常量引用
r1 = 42;        //错误,r1是对常量的引用,故不能对其进行修改。
int &r2 = ci;    //错误,试图让一个非常量引用指向一个常量对象。

        之前的第一个规则提到:引用的类型必须与其所引用的对象的类型一致,但是对两种类型例外,第一种就是初始化常量引用时。在初始化常量引用时,可以用任意表达式作为初始值,只要该表达式的结果能转换成引用的类型即可。尤其,允许为一个常量引用绑定非常量的对象。(但不能让一个非常量引用指向一个常量对象);

int i = 42;
const int &r1 = i;        //允许将const int&绑定到一个普通的int对象上。
const int &r2 = 42;        //正确,此时r2是一个常量引用。直接绑定到了字面值上面
const int &r3 = r1 * 2;    //正确,此时r3是一个常量引用,绑定在了一个表达式上面。
int &r4 = r1 * 2;         //错误,r4是一个普通的非常量引用,不能绑定到表达式上面。

要理解上面这种例外情况的原因,我们要弄清楚当一个常量引用被绑定到另外一种类型上时到底发生了什么:

double dval = 3.14;
const int &ri = dval;

 此处的ri引用了一个int型的整数。对ri的操作应该是整数运算,但是dval却是一个双精度浮点数而非整数。因此为了确保让ri绑定一个整数,编译器把上述代码变成了如下形式:

const int temp = dval;        //由双精度浮点数生成一个临时的整型常量
const int &ri = temp;        //让ri绑定这个临时变量

在这种情况下,ri绑定了一个临时量对象。所谓临时量对象就是当编译器需要一个空间来暂存表达式的求值结果时临时创建的一个未命名的对象。如果ri不是常量,c++语言把这种情况归为非法。

对const的引用可能引用一个并非const的对象

必须认识到,常量引用仅对引用可参与的操作做出了限定,对于引用的对象本身是不是一个常量未做限定。因为对象也可能是个非常量,所以允许同构其他途径改变它的值:

int i = 42;
int &r1 = i;        //用于r1绑定对象i
const int &r2 = i;    //r2也绑定对象i,但是不允许通过r2修改i的值
r1 = 0;        //正确,r1并非常量,可以通过它修改i的值,此时i值为0.注意,此时r2的值也是0
r2 = 0;        //错误,r2是一个常量引用,不能通过r2修改i的值

r2绑定(非常量)整数i是合法的行为。然而,不允许通过r2修改i的值。尽管如此,i的值仍然允许通过其他的途径来修改,既可以直接给i赋值,也可以通过像r1一样绑定到i的其他引用来修改。

 

另外一种可以改变规则的是:我们可以将基类的引用指向派生类。

 

 

二:指针 

 

与引用类似,指针也实现了对其他对象的间接访问。然后指针与引用相比,又有很多的不同点:

(1)指针本身就是一个对象,允许对指针赋值和拷贝,而且在指针的生命周期内它可以先后指向几个不同的对象。

(2)指针无需在定义时赋初值。和其他内置类型一样,在块作用域内定义的指针如果没有被初始化,也将拥有一个不确定的值。

 

获取对象的地址

指针存放某个对象的地址,要想获取该地址,需要用取地址符(操作符&)(注意与引用也是用这个符号)

因为:引用不是对象,没有实际地址,所以不能定义指向引用的指针

使用指针的一个简单例子就是:

int v = 1;
int *p = &v;


需要预先强调的是没有指向引用的指针!因为引用不是对象,没有地址。但是指向引用的指针是什么形式呢?
是对一个引用进行如下取地址吗:

int v = 1;
int &ri = v;    // 整型变量v的引用
int *p = &ri;    // 这是指向引用的指针吗?


事实上不是,这是一个普通的整型指针,虽然我们说引用没有地址,但是对引用ri的操作实际上是对v的操作。这是是定义了一个整型指针,并且让它指向了v。那如何定义一个指向引用的指针呢(虽然是不合理的请求)?当我们定义指针的时候,我们用到了*,那么当我们定义指向引用的指针时,免不了要用到*和&。

int v = 1;
int &ri = v;
// int &*p = &ri;    //这就是指向引用的指针,这是错误的。不能定义指向引用的指针


由于引用并不存在地址,因此第三行将会报错。我们可以从右往左读,*表明p是一个指针,余下的&说明了p指向类型的类型。

指针的引用
之前说到指向引用的指针,现在来说指针的引用就容易多了。

int v = 1;
int *p = &v;
int *&rp = p;    //从右向左看,这是一个指针的引用。这是正确的

&说明r是一个引用。*确定r引用的类型是一个指针。

其他
因为引用不是对象,故无引用的数组,无指向引用的指针,无到引用的引用:

int& a[3]; // 错误,不存在引用的数组
int&* p;   // 错误,不存在指向引用的指针
int& &r;   // 错误,不存在引用的引用

 

另外,除了两种情况以外,也有一个规则是:

指针的类型都要和他所指向的对象严格匹配。

double dval;
double *pd = &dval;    //正确,初始值是double类型对象的地址
double *pd2 = pd;        //正确,初始值是指向double对象的指针。

int *pi = pd;        //错误,指针pi的类型和pd的类型不匹配
pi = &dval;            //错误,试图把double类型对象的地址赋值给int型指针

 

指针值:

指针的值应该为以下四种状态之一:

(1)指向一个对象

(2)指向紧邻对象所占空间的下一个位置

(3)空指针,意味着指针没有指向任何对象

(4)无效指针,也就是上述情况之外的其他值(试图拷贝或以其他的方式访问无效指针的值都将引发错误,编译器不负责检查次类型的错误)。

 

利用指针访问对象

如果指针指向了一个对象,则允许使用解引用符(操作符*)来访问该对象。(解引用操作仅适用于那些确实指向了某个对象的有效指针)。

 

由于&和*有多重含义,因此我们要理解它们在不同场景下的不同作用

int i = 42;
int &r = i;        //&紧随类型名出线,因此是声明的一部分,r是一个引用
int *p;            //*紧随类型名出现,因此是声明的一部分,p是一个指针
p = &i;            //&出现在表达式中,是一个取地址符
*p = i;            //*出现在表达式中,是一个解引用符
int &r2 = *p;        //&是声明的一部分,*是一个解引用符

 

空指针

空指针不指向任何对象。在试图使用一个指针之前代码可以首先检查它是否为空。空指针的定义有以下几种方法:

int *p1 = nullptr;        //等价于int *p1 = 0; 这是c++11新标准引入的
int *p2 = 0;              //直接将p2初始化为字面值常量0

//需要首先#include cstdlib
int *p3 = NULL;        //等价于int *p3 = 0;

得到空指针最直接的办法是用字面值nullptr来初始化指针。nullptr是一种特殊类型的字面值,它可以被转换成任意其他的指针类型。

过去的程序用一个名为NULL的预处理变量来给指针赋值,这个变量在头文件cstdlib中定义,它的值就是0.对于预处理,是运行于编译之前的一段程序。当用到一个预处理变量时,预处理会自动将它替换为实际值,因此用NULL初始化指针和用0初始化指针是一样的。现在的c++程序最好使用nullptr,同时尽量避免使用NULL。

void*指针

void*指针是一种特殊的指针类型,可用于存放任意对象的地址。但是我们对该地址中到底是个什么类型的对象并不了解。

注意,void*指针不是空指针,这一点一定要区分开来。

double obj = 3.14;
double *pd = &obj;

void *pv = &obj;        //obj可以是任意类型的对象
pv = pd;                //pv可以存放任意类型的指针

不过对弈void*指针能做的事情也比较有限:(1)拿他和别的指针比较,,(2)作为函数的输入或输出,(3)赋值给另外一个void*指针。但我们不能直接操作void*指针所指的对象,因为我们不知道这个对象到底是什么类型,也无法确定能在这个对象上做哪些操作。

 

指向指针的指针

指针也是内存中的对象,也拥有自己的地址,因此可以把一个指针的地址再存放到另外一个指针中去。

通过*的个数可以区分指针的等级: **表示指向指针的指针,***表示指向指针的指针的指针,以此类推。

int ival = 1024;
int *pi = &ival;        //pi指向一个int型的数
int **ppi = π        //ppi指向一个int型的指针

 

指向指针的引用

引用本身不是对象,因此不存在指向引用的指针,。但是指针本身是一个对象,所以存在指向指针的引用。

int i = 42;
int *p;        //p是一个int型的指针
int *&r = p;    //r是一个对指针p的引用

r = &i;        //r引用了一个指针,因此给r赋值&i就是令p指向i
*r = 0;        //解引用r得到i,也就是p指向的对象,将i的值改为0

要理解&的类型到底是什么,最简单的办法是从右向左阅读r的定义。离变量名最近的符号对变量的类型有最直接的影响。

 

常量指针:说明指向的对象时一个常量,不能通过该指针去修改变量的值

int i = 42;
const int *p = &i;        //只是一个常量指针,意味着不能通过指针p去修改i的值,
//但是i可以通过其他方式改变它的值,它自己也可以直接改变。另外,p也可以再指向其他的变量。

i = 10;        //i自己改变,此时*p的值也变为了10;

*p = 30;        //错误;不能通过该指针去改变变量的值

int a = 20;
p = &a;        //p可以再直线指向其他的变量。

试试这样想吧,所谓指向常量的指针或者引用,不过是指针或引用“自以为是”罢了,它们觉得自己指向了常量,所以自觉的不去改变所指对象的值。

 

指针常量:表示指针本身是一个常量,该指针将一直指向该变量,不能再指向其他变量。

int i = 42;
int *const p = &i;        //这是一个指针常量,意味着p将一直指向i,不能再让p指向其他的变量。

*p = 20;        //可以通过指针去修改这个变量的值
i = 20;            //变量本身也可以进行修改

int a = 30;
p = &a;            //错误,p是一个指针常量,将一直指向i,不能再指向其他的变量

 

顶层const和底层const

顶层const表示指针本身是一个常量,也就是指针常量。

底层const表示指针所指的对象时一个常量,也就是常量指针。

int i = 0;
int *const p1 = &i;        //不能改变p1的值,p1一直指向i,这是一个顶层const
const int ci - 42;        //不能改变ci的值,这是一个顶层const
const int *p2 = &ci;        //常量指针,允许p2再执行其他的变量,这是一个底层const
const int *const p3 = p2;        //左边的const是一个底层const,右边的是顶层const
const int &r = ci;        //用于声明引用的const都是底层const.

可以先这样来记忆:常量指针是底层const,指针常量是顶层const。

 

下面是引用和指针 的区别

引用是别名,指针是一个变量,里面保存着地址)。

1)初始化要求不同。(引用创建时必须初始化,指针不需要)

2)可修改性不同、(引用一旦指向某一个对象,不能再继续指向另外一个对象)

3)不存在null引用。(不能使用指向空值的引用。可以指向任意对象的指针)(由于不存在空引用,但存在空指针,这就是为什么引用比指针安全。)

4)测试需要的区别。(应用指针时,要测试是否为空指针)。

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值