引用必须初始化
因为引用本身不是一个对象,所以不能定义引用的引用
引用的定义
允许在一条语句中定义多个引用,其中每个引用标识符都必须以符号&开头:
int i=1024, i2=2048; //i和i2都是int
int &r=i, r2=i2; //r是一个引用,与i绑定在一起r2是int
int i3=1024, &ri=i3; //i3是int,ri是一个引用,与i3绑定在一起
int &r3=i3, &r4=i2; //r3和r4都是引用
int &refVal4 = 10; //错误:引用类型的初始值必须是一个对象
double dVal = 3.14;
int &refVal = dval; //错误:此处引用类型的初始值必须是int型对象
空指针(null pointer)不指向任何对象,在试图使用一个指针之前代码可以首先检查它是否为空。
以下列出几个生成空指针的方法:
int *p1 = nullptr; //等价int *p1=0;
int *p2 = 0; //直接将p2初始化为字面常量0
//首先需要#include cstdlib
int *p3 = NULL; //等价于int *p3=0;
得到空指针最直接的办法就是用字面值nullptr来初始化指针,这也是C++11新标准刚刚引入的一种方法。
nullptr是一种特殊类型的字面值,它可以被转换成任意其他指针类型。
将int变量直接赋值给指针是错误的操作,即使int变量的值恰好等于0也不行。
int zero = 0;
pi = zero; //错误:不能把int变量直接赋给指针
建议:初始化所有指针
使用未初始化的指针是引发运行时错误的一大原因。
和其他变量一样,访问未经初始化的指针所引发的后果也是无法预计的。通常这一行为将造成程序崩溃,
而且一旦崩溃,要想定位到出错位置将是特别棘手的问题。
在大多数编译器环境下,如果使用了未经初始化的指针,则该指针所占内存空间的当前内容将被看作一个地址值。
访问该指针,相当于访问一个本不存在的位置上的本不存在的对象。糟糕的是,如果指针所占内存空间中恰好有内容,
而这些内容又被当做了某个地址,我们就很难分清它到底是合法的还是非法的。
因此建议初始化所有的指针,并且在可能的情况下,尽量等定义了对象之后在定义指向它的指针。如果实在不清楚
指针应该指向何处,就把它初始化为nullptr或者0.这样程序就能检测并知道它没有指向任何具体的对象了
指针和引用都能提供对其他对象的间接访问,然而在具体实现细节上二者有很大不同,其中最重要的一点就是引用本身并
非一个对象。一旦定义了引用,就无法令其再绑定到另外的对象,之后每次使用这个引用都是访问它最初绑定的那个对象。
指针和它存放的地址之间就没有这种限制了。和其他任何变量(只要不是引用)一样,给指针赋值就是令它存放一个新的地址,
从而指向一个新的对象。
指向指针的引用
引用本身不是一个对象,因此不能定义指向引用的指针。但指针是对象,所以存在对指针的引用:
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;
因为const对象一旦创建后其值就不能改变,所以const对象必须初始化。
const int k; //错误:k是一个未经初始化的常量
如果想在多个文件之间共享const对象,必须在变量的定义和声明之前添加extern关键字
初始化对常量的引用
const int ci = 1024;
const int &r1 = ci; //正确,引用极其对应的对象都是常量
r1 = 42; //错误:r1是对常量的引用
int &r2 = ci; //错误:试图让一个非常量引用指向一个常量对象
int i=42;
int &r1=i; //引用ri绑定对象i
const int &r2 = i; //r2也绑定对象i,但不允许通过r2修改i的值
r1 = 0; //r1并非常量,i的值修改为0
r2 = 0; //错误:r2是一个常量引用
指针和const
(指向常量的指针)
要想存放常量对象的地址,只能使用指向常量的指针:
const double pi = 3.14; //pi是个常量,它的值不能改变
double *ptr = π //错误:ptr是一个普通指针
const double *cptr = π //正确:cptr可以指向一个双精度常量
*cptr = 42; //错误:不能给*cptr赋值
double dval = 3.14; //dval是一个双精度浮点数,它的值可以改变
cptr = &dval; //正确:但是不通过cptr改变dval的值
tip:试试这样想吧:所谓指向常量的指针或引用,不过是指针或引用“自以为是”罢了,
它们觉得自己指向了常量,所以自觉地不去改变所指对象的值。
const 指针(常量指针)
指针是对象而引用不是,因此就像其他对象类型一样,允许把指针本身定为常量。常量
指针必须初始化,而且一旦初始化完成,则它的值(也就是存放在指针中的那个地址)就
不能再改变了。把*放在const关键字之前用以说明指针是一个常量。
int errNumb = 0;
int *const curErr = &errNumb; //curErr将一直指向errNumb
const double pi = 3.14159;
const double *const pip = π //pip是一个指向常量对象的常量指针
指针本身是一个常量并不意味着不能通过指针修改其所指对象的值,能否这样做完全依赖于所指对象的类型
*pip = 2.72; //错误:pip是一个指向常量的指针
//如果curErr所指的对象(也就是errNumb)的值不为0
if(*curErr)
{
errorHandler();
*curErr = 0; //正确:把curErr所指的对象的值重置
}
int i = 0;
int *const p1 = &i; //不能改变p1的值,这是一个顶层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区别明显。其中,顶层
const不受什么影响
i = ci; //正确:拷贝ci的值,ci是一个顶层const,对此操作无影响
p2 = p3; //正确:p2和p3指向的对象类型相同,p3顶层const的部分无影响
执行拷贝操作并不会改变被拷贝对象的值,因此,拷入和拷出对象是否是常量都
没什么影响。
另一方面,底层const的限制却不能忽视。当执行对象的拷贝操作时,拷入和拷出
的对象必须具有相同的底层const资格,或者两个对象的数据类型必须能够转换。一
般来说,非常量可以转换成常量,反之则不行:
int *p = p3; //错误:p3包含底层const含义,而p没有
p2 = p3; //正确:p2和p3都是底层const
p2 = &i; //正确:int*能转换成const int*
int &r = ci; //错误:普通的int&不能绑定到int常量上
const int &r2 = i; //正确:const int&可以绑定到一个普通int上
const int &r2 = i; //正确:const int&可以绑定到一个普通int上
p3既是顶层const也是底层const,拷贝p3时可以不在乎它是一个顶层const,但是
必须清楚它指向的对象得是一个常量。因此,不能用p3去初始化p,因为p指向的是
一个普通的(非常量)整数。另一方面,p3的值可以赋给p2,因为这两个指针都是
底层const,尽管p3同时也是一个常量指针(顶层const),仅就这次赋值而言不会
有什么影响。