复合类型
引用
引用是一种复合类型,通过在变量名前添加“&”符号来定义。复合类型是指用其他类型定义的类型。在引用的情况下,每一种引用类型都“关联到”某一其他类型。不能定义引用类型的引用,但可以定义任何其他类型的引用。
引用必须用与该引用同类型的对象初始化:
int ival = 1024;
int &refVal = ival; // ok: refVal refers to ival
int &refVal2; // error: a reference must be initialized
int &refVal3 = 10; // error: initializer must be an object
引用是别名
因为引用只是它绑定的对象的另一名字,作用在引用上的所有操作事实上都是作用在该引用绑定的对象上当引用初始化后,只要该引用存在,它就保持绑定到初始化时指向的对象。不可能将引用绑定到另一个对象。
定义多个引用
可以在一个类型定义行中定义多个引用。必须在每个引用标识符前添加“&”符号:
int i = 1024, i2 = 2048;
int &r = i, r2 = i2; // r is a reference, r2 is an int
int i3 = 1024, &ri = i3; // defines one object, and one reference
int &r3 = i3, &r4 = i2; // defines two references
指针
指针是指向另外一种类型的复合类型,与引用类似,指针也实现了对其它对象的间接访问。然而指针与引用相比又有很多不同点:
指针本身就是对象,允许对指针赋值和拷贝,而且在指针的生命周期中可以先后指向不同的对象。
指针无须在定义时赋初值。
与其他内置类型相同,在块作用域内定义的指针如果没有被初始化,也将拥有一个不确定的值。
如果在一条语句中定义了几个指针变量,每个指针 变量前面都必须有*:
int *ip1, *ip2; //ip1,ip2都是指向int型对象的指针
double dp, *dp2; //dp2是指向double型对象的指针,dp是double型对象
获取对象的地址
指针存放某个对象的地址,想要获取地址,需要使用取地址符&。
因为引用不是对象,没有实际地址,所以不能定义指向引用的指针。
通常,指针的类型应该与它所指向的对象严格匹配。
指针值
指针值有四种状态:
指向一个对象;
指向紧邻对象所占空间的下一个位置;
空指针,意味着指针并没有任何指向对象;
无效指针,也就是上述情况之外的其它值。
试图拷贝或者以其它方式访问无效指针的值都将引发错误,编译器并不负责检查此类错误,这一点和试图使用未经初始化的变量是一样的,访问无效指针的后果无法预计。
利用指针访问对象
如果指针指向了一个对象,则允许使用解引用操作符*来访问该对象:
int ival = 42;
int *p = &ival;
cout << *p;
对指针解引用会得出所指的对象,因此如果给解引用的结果赋值,实际上也就是给指针所指的对象赋值。
*p = 0; //由*得到指针p所指的对象,即可由p为变量ival赋值
cout << *p; //输出0
某些符号有多重定义,像&和*,既能用作表达式里的运算符,也能作为声明的一部分出现,符号的上下文决定了符号的意义:在声明语句中,&和*用于组成复合类型,在表达式中,它们的角色又转换成运算符。
空指针
空指针不指向任何对象,在使用一个指针以前必须检查指针是否为空。
生成空指针的方法:
int *p1 = nullptr;
int *p2 = 0;
int *p3 = NULL;
得到空指针最直接的方法就是使用字面值nullptr来初始化指针,这也是C++11新标准引入的一种方法。在新的标准下,最好使用nullptr,避免使用NULL。
把int型变量直接赋给指针是错误的操作,即使int变量的恰好为0也不行。
int zero = 0;
int *pi = zero; //错误:不能把int变量直接赋给指针
赋值和指针
指针和引用都能够提供对其它对象的间接访问,但是引用本身并非一个对象,一旦定义了引用,就无法令其再绑定到另外的对象,之后每次使用这个引用就是访问最初绑定的那个对象。
指针和它存放的地址之间没有这种限制,和其他任何变量一样,给指针赋值就是令其存放一个新的地址,从而指向新的对象:
int i = 42;
int *pi = 0;
int *pi2 = &i;
int *pi3;
pi3 = pi2;
pi2 = 0;
其它指针操作
只要指针拥有一个合法值,就能将它用在条件表达式中,如果指针的值是0,条件就是false,任何非0指针对应的条件值都是true。
对于两个类型相同的合法指针,可以使用相等操作符==或不等操作符!=来比较它们,比较的结果是bool类型。如果两个指针存放的地址值相同,则它们相等;反之它们不相等。两个指针相等的情况:
它们都为空;
它们都指向同一个对象;
它们都指向同一个对象的下一个地址。
需要注意的是,一个指针指向某个对象,同时另一个指针指向另外对象的下一个地址,此时也有可能出现两个指针相等的情况。
void*指针
void* 是一种特殊的指针可以用于存放任意对象的地址,一个void*指针存放着一个地址,但是该地址中到底是什么类型的对象并不清楚。
使用void*指针能做的事比较有限:
拿它和别的指针比较;
作为函数的输入或输出;
将其赋值给另外一个void*类型的指针。
注意,不能直接使用void*所指的对象,因为并不知道其到底是什么类型,也就无法确定能在这个对象上做哪些操作。
理解复合类型的声明
在同一条定义语句中,虽然基本数据类型只有一个,但是声明符的形式却可以不同,也就是说,一条定义语句可能定义出不同类型的变量:
int i = 1024,*p = &i,&r= i;
定义多个变量
int *p1, p2; //p1是指向int的指针,p2是int
int *p1, *p2; //p1是指向int的指针,p2是指向int的指
指向指针的指针
通过*的个数可以区分指针的级别,也就是说**表示指向指针的指针,***表示指向指针的指针的指针,以此类推。
int ival = 1024;
int *pi = &ival;
int **ppi = π
解引用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引用的是一个int指针。