引用与指针
1、引用
引用(reference)为对象起了另外一个名字,引用类型引用(refer to)另外一种类型。通过将声明符写出&d的形式来定义引用类型,其中d是声明的变量名。
一般在初始化变量时,初始值会被拷贝到新建的对象中,然后定义引用时,程序把引用和它的初始值绑定在一起,而不是将初始值拷贝给引用。一旦初始化完成,引用将和它的初始值一直绑定在一起。因为无法令引用重新绑定到另外一个对象,因此引用必须初始化。
引用即别名。引用并非对象,相反的,它只是为一个已经存在的对象所起的另外一个名字。
int val = 1024;
int &refval1 = val; //refval指向val(是val的另外一个名字)
int &refval2; //报错:引用必须初始化
定义一个引用之后,对其进行的所有操作都是在与之绑定的对象上进行的。为引用赋值,实际上是把值赋给了与引用绑定的对象;获取引用的值,实际上是获取了与引用绑定的对象的值;以引用作为初始值,实际上是以与引用绑定的对象作为初始值。
refval1 = 2; //把2赋给refval1所指向的对象,此处即赋给了val
int ii = refval1; //ii被初始化为val的值,与ii = val;执行结果相同
int &refval3 = refval1; //refval3绑定到了那个鱼refval1绑定的对象上,即绑定到val上
因为引用本身不是一个对象,所以不能定义引用的引用。
允许在一条语句中定义多个引用,其中每个引用标识符都必须以&开头。
几乎所有引用的类型都要和与之绑定的对象严格匹配。
引用只能绑定在对象上,不能与字面值或某个表达式的计算结果绑定到一起。
int i = 1024, i2 = 2048; //i和i2都是int
int &r = i, r2 = i2; //r是一个引用,与i绑定到一起,r2是一个int
int i3 = 1024, &r3 = i3; //i3是int,r3是一个引用,与i3绑定在一起
int &r3 = i3, &r4 = i2; //r3和r4都是引用
int &refval4 = 10; //错误:引用类型的初始值必须是一个对象
double dval =3.14;
int &refval5 = dval; //错误:此处引用类型的初始值必须是int型对象
2、指针
定义:
指针(pointer)是指向(point to)另外一种类型的复合类型。定义指针的类型的方法是将声明符写成*d的形式,其中d是变量名。如果在一条语句中定义了多个指针变量,每个变量前面都必须有符号。
int *ip1, *ip2; // ip1和ip2均为指向int型对象的指针
double dp, **dp2; //dp2为指向double型对象的指针,dp为double型对象
获取对象的地址
指针存放某个对象的地址,获取该地址需要使用取地址符(&)
在声明语句中指针的类型实际上被用于指定它所指向对象的类型,所以除了两种类型意外,其他所有指针的类型都要和它所指向的对象类型严格匹配。
因为引用不是对象,没有实际的地址,所以不能定义指向引用的指针。
int ival = 42, &ref = ival;
int *p = &ival; //p存放变量ival的地址,或者说p是指向变量ival的指针
int *p1 = &ref; //错误:ref为引用,没有地址,不能定义指向它的指针
double dval;
double *pd = &dval; //初始值double类型对象的地址
double *pd2 = pd; //初始值是指向double对象的指针
int *pi = pd; //错误:指针pi的类型和pd的类型是不匹配的
pi = &dval; //错误:试图将double型对象的地址赋给int型指针
指针的值
指针的值(即地址)应属于以下4中状态之一:
- 指向一个对象
- 指向紧邻对象所占空间的下一个位置
- 空指针,意味着没有指向任何对象
- 无效指针,也就是上述情况之外的其他值
试图拷贝或以其他方式访问无效指针将引发错误,编译器不负责检查此类错误。
第2和第3种形式的指针虽然有效,但是由于没有指向任何具体对象,所以试图访问此类指针对象的行为也不被允许。
利用指针访问对象
指针指向一个对象,则允许使用解引用符(*)来访问该对象。
对指针解引用会得到所指的对象,因此如果给解引用的结果赋值,实际上也就是给指针所指的对象赋值。
解引用操作仅适用于那些确实指向了某个对象的有效指针。
int ival = 42; //
int *p = &ival; //p存放着变量ival的地址,或者说p是指向变量ival的指针
cout << *p; //由符号*得到指针p所指的对象,输出42
*p = 0; //由符号*得到p所指的对象,即可由p为变量ival赋值
在声明语句中,&和*用于组成复合类型,在表达式中,他们是运算符
空指针
空指针不指向任何对象,在试图使用一个指针之前,可以先检查一个它是否为空。
生成空指针的方法:
int *p = nullptr; //等价于第二个,nullptr是一种特殊类型的字面值,可以被转换成任意其他类型的指针。
int *p2 = 0; //直接将p2初始化为字面常量0
int *p3 = NULL; //NULL 为预处理变量,它的值就为0,需要首先#include cstdlib
把int变量直接赋值给指针是错误的操作,即使int变量的值恰好等于0也不行。
int zero = 0;
pi = zero; //错误:不能把int变量直接赋给指针
和其他变量一样,访问未经初始化的指针所引发的后果是无法预测的,通常这一行为将会造成程序崩溃。
赋值和指针
和其他非引用的任何变量一样,给指针赋值就是令它存放一个新的地址,从而指向一个新的对象。
赋值永远改变的是等号左侧的对象。
int i = 42;
int *pi = 0; //pi被初始化,但是没有指向任何对象
int *pi2 = &i; //pi2被初始化,存有i的地址
int *pi3; //如果pi3存放在块内,则pi3的值无法确定
pi3 = pi2; //pi2与pi3指向同一个对象i
*pi2 = 50; //pi2指向的i的值被改变,指针pi2并没有改变
pi2 = 0; //pi2的值被改变,现在pi2不指向任何对象了
其他指针操作
只要指针拥有一个合法值,就可以用在条件表达式中,和采用算术值作为条件遵循的规则类似。任何非0指针对应的条件值都是true,0指针条件去false。
对于两个类型相同的指针,可以用相等操作符(==)和不相等操作符(!=)来比较她们,比较的结果是布尔类型。
void*指针
void*是一种特殊的指针类型,可用于存放任意对象的地址,该地址中到底是个什么类型的变量并不了解。
void*指针能做的事情很有限:和别的指针比较、作为函数的输入或输出、赋给另外一个void*指针。
不能直接操作void*指针所指的对象。
指针与引用的区别
★ 相同点:
1. 都是地址的概念;
指针指向一块内存,它的内容是所指内存的地址;引用是某块内存的别名。
★ 区别:
1. 指针是一个变量,而引用仅是个别名;
2. 引用使用时无需解引用(*),指针需要解引用;
3. 引用只能在定义时被初始化一次,之后不可变;指针可变;
4. 引用没有 const,指针有 const,const 的指针不可变;
5. 引用不能为空,指针可以为空;
6. “sizeof 引用”得到的是所指向的变量(对象)的大小,而“sizeof 指针”得到的是指针本身(所指向的变量或对象的地址)的大小;typeid(T) == typeid(T&) 恒为真,sizeof(T) == sizeof(T&) 恒为真,但是当引用作为成员时,其占用空间与指针相同(没找到标准的规定)。
7. 指针和引用的自增(++)运算意义不一样;
8. 如果返回动态内存分配的对象或者内存,必须使用指针,引用可能引起内存泄漏;
★ 联系
1. 引用在语言内部用指针实现(const 指针?)。
2. 对一般应用而言,把引用理解为指针,不会犯严重语义错误。引用是操作受限了的指针(仅容许取内容操作)。