引用
引用为对象起了另外一个名字,引用类型引用另外一个类型。通过将声明符写成&d的形式来定义引用类型,其中d是声明的变量名:
int ival=1024;
int &refval=ival; //refval指向ival(是ival的另一个名字)
int &refval2; //报错:引用必须初始化
一般在初始化变量的时,初始值会被拷贝到新建的对象中。然而定义引用时,程序把引用和它的初始值绑定在一起,而不是将初始值拷贝给引用。一旦初始化完成,引用将和它的初始值对象一直绑定在一起。因为无法令引用重新绑定到另外一个对象,因此引用必须初始化。
引用即别名
引用并非对象,相反的,它只是为一个已经存在的对象所起的另外一个名字。
定义一个引用之后,对其进行的所有操作都是在与之绑定的对象上进行的:
refval=2;//把2赋给refval指向的对象,此处即是赋给了ival
int li=refval; //与li=ival执行结果一样
为引用赋值,实际上是把值赋给了与引用绑定的对象。获取引用的值,实际上是获取了与引用绑定的对象的值。同理,以引用作为初始值,实际上是以与引用绑定的对象作为初始值:
//正确:refval3绑定到了那个与refval绑定的对象上,这里就是绑定到了ival上
int &refval3=refval;
//利用与refval绑定的对象的值初始化变量i
int i=refval; //正确: i被初始化为ival的值
因为引用本身不是一个对象,所以不能定义引用的引用。
使用引用要注意的两点:
1 一般的,引用类型要和与之绑定的对象严格匹配。
2 引用只能绑定在对象上,而不能与字面值或某个表达式的计算结果绑定在一起。
指针
指针是指向另一种类型的复合类型。与引用类似,指针也实现了对其它对象的间接访问。然而指针与引用相比又有很多不同点。其一,指针本身就是一个对象,允许对指针赋值和拷贝,而且在指针的声明周期内它可以先后指向几个不同的对象。其二,指针无须在定义时赋初值。和其他内置类型一样,在块作用域内定义的指针如果没有被初始化,它将拥有一个不确定的值。
指针值
指针的值(即地址)应属于下列4中状态之一:
1 指向一个对象
2 指向紧邻对象所占空间的下一个位置
3 空指针,意味着指针没有指向任何对象
4 无效指针,也就是上述情况之外的其他值
利用指针访问对象
如果指针指向了一个对象,则允许使用解引用符(操作法*)来访问该对象。
解引用操作符仅适合用于那些确实指向了某个对象的有效指针。
空指针
空指针不指向任何对象,在试图使用一个指针之前代码可以首先检查他是否为空。以下列出几个生成空指针的方法:
int *p1=nullptr; //等价于int *p1=0;
int *p2=0; //直接将p2初始化为字面常量0
//需要首先#include cstdlib
int *p3=NULL; //等价于int *p3=0;
得到空指针最直接的方法就是用字面值nullptr来初始化指针,这是C++11新标准刚刚引入的一种方法。nullptr是一种特殊类型的字面值,它可以被转换成任意其他的指针类型。另一种方法就如对p2的定义一样,也可以通过将指针初始化为字面值0来生成空指针。
赋值和指针
指针和引用都能提供对其他对象的间接访问,然而在具体实现细节上二者有很大不同,其中最重要的一点就是引用本身并非一个对象。一旦定义了引用,就无法令其再绑定到另外的对象,之后每次使用这个引用都是访问它最初绑定的那个对象。
指针和它存放的地址之间就没有这种限制了。和其他任何变量(只要不是引用)一样,给指针赋值就是令他存放一个新的地址,从而指向一个新的对象。
有时候要想搞清楚一条赋值语句到底是改变了指针的值还是改变了指针所指的对象的值不太容易,最好的办法就是记住赋值永远改变的是等号左侧的对象。当写出如下语句时,
pi=&ival; //pi的值被改变,现在pi指向了ival
意思是为pi赋一个新的值,也就是改变了那个存放在pi内的地址值。相反的,如果写出如下语句:
*pi=0; //ival的值被改变,指针pi并没有改变
则*pi(也就是指针pi指向的那个对象)发生改变。
指向指针的指针
一般来说,声明符中修饰符的个数并没有限制。当有多个修饰符连写在一起时,按照其逻辑关系详加解释即可。以指针为例,指针是内存中的对象,像其他对象一样也有自己的地址,因此允许把指针的地址再存放到另一个指针当中。
通常*的个数可以区分指针的级别。也就是说,**表示指向指针的指针,***表示指向指针的指针的指针,以此类推:
int ival=1024;
int *pi=&ival; //pi指向一个int型的数
int **ppi=π //ppi指向一个int型的指针
此处pi是指向int型数的指针,而ppi是指向int型指针的指针。
解引用int型指针会得到一个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的类型到底是什么,最简单的办法是从右向左阅读r的定义。离变量名最近的符号(此例中是&r的符号&)对变量的类型有最直接的影响。因此r是一个引用。声明符的其余部分用以确定r引用的类型是什么,此例中的符号*说明r引用的是一个指针。最后,声明的基本数据类型部分指出r引用的是一个int指针。
面对一条比较复杂的指针或引用的声明语句时,从右向左阅读有助于弄清楚它的真实含义。