复合类型是指基于其他类型定义的类型。
一条声明语句由一个基本数据类型(base type)和紧随其后的一个声明符(declarator)列表组成。每个声明符命名了一个变量并指定该变量为与基本数据类型有关的某种类型。
2.3.1 引用(reference)
引用类型引用另外一种类型,通过将声明符写成&d
的形式来定义引用类型,其中 d 是声明的变量名:
int ival = 1024;
int &refVal = ival; // refVal指向 ival
int &refVal2; // 错误:引用必须被初始化
定义引用时,程序把引用和它的初始值绑定(bind)在一起,而不是将初始值拷贝给引用。
引用即别名
引用并非对象,它只是为一个已经存在的对象所起的另外一个名字。定义了一个引用后,对其进行的所有操作都是在与之绑定的对象上进行的:
refVal = 2; // 把 2 赋给 refVal 所指向的对象,此处赋值给了 ival
int ii = refVal; // 与 ii = ival 执行结果一样
int &refVal3 = refVal; // 正确:refVal3 绑定到了那个与 refVal 绑定的对象上
// 利用与 refVal 绑定的对象的值初始化变量 i
int i = refVal; // 正确:i 被初始化为 ival 的值
引用本身不是一个对象,不能定义引用的引用。
引用的定义
允许在一条语句中定义多个引用,其中每个引用标识符都必须以符号&
开头。
引用的类型要和与之绑定的对象严格匹配;引用只能绑定在对象上,而不能与字面值或某个表达式的计算结果绑定在一起。
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 &refVal5 = dval; // 错误:引用类型的初始值与绑定对象不相符
2.3.2 指针(pointer)
指针是指向另外一种类型的复合类型。指针本身是一个对象,允许对指针赋值和拷贝,在指针的生命周期内可以先后指向几个不同的对象;指针无需再定义时赋初值。
定义指针类型的方法将声明符写成*d
的形式,其中 d 是变量名。如果在一条语句中定义了几个指针变量,每个变量前都必须有符号*
获取对象的地址
指针存放某个对象的地址,要获取该对象的地址,使用取地址符&
int ival = 42;
// 这条语句实际是先定义了一个 int* 的指针,然后初始化 p 指向 ival
int *p = &ival; // p 存放变量 ival 的地址,或者说 p 是指向变量 ival 的指针
指针的类型要和它所指向的对象严格匹配。原因在于声明语句中指针的类型实际上被用于指定它所指对象的类型。
指针值
指针的值,也就是地址,应属于下列 4 种状态之一:
- 指向一个对象
- 指向紧邻对象所占空间的下一个位置
- 空指针,指针没有指向任何对象
- 无效指针,上述3种情况之外的其他值
利用指针访问对象
使用解引用符*
来访问指针指向的对象,对指针解引用会得出所指向的对象,因此如果给解引用的结果赋值,实际上就是给指针所指向的对象赋值
int ival = 24;
int *p = &ival; // 定义并初始化 p
cout << *p
*p = 0;
cout << ival; // ival 变为0
空指针(null pointer)
有以下几种方式可以生成空指针:
int *p1 = nullptr;
int *p2 = NULL; // 需要 include cstdlib
int *p3 = 0;
赋值和指针
引用无法绑定到另外的对象,每次使用引用都是访问引用最初绑定的对象。
给指针赋值就是令指针存放一个新的地址,从而指向一个新的对象。
int i = 42;
int *pi = 0; // 声明并定义一个 pi,为空指针
int *pi2 = &i; // 定义 pi2,指向 i
int *p3; // 定义 pi3,初始值由定义时的位置决定
pi3 = pi2; // 现在 pi3 也指向 i
pi2 = 0; // pi2 的地址变为 0,不在指任何对象
赋值永远改变的是等号左侧的对象。
其他指针操作
只要指针拥有一个合法值,就能将指针用在条件表达式中。可采用算术值遵循的规则类似,如果指针的值是0,条件取 false
int ival = 24;
int *pi = 0; // 初始 pi 为空指针
int *pi2 = &ival; // 初始 pi2 指向 ival
if(pi) // 条件不成立
// ...
if(pi2) // 条件成立
// ...
对于类型相同的两个指针,可以使用相等操作符==
和不等操作符!=
进行比较,比较的结果是布尔类型。
指针的比较实际比较的是指针存放的地址值。
void* 指针
void* 指针可用于存放任意对象的地址。
void* 指针的作用:
- 和别的指针进行比较
- 作为函数的输入或者输出
- 赋给另外一个 void* 指针
注意:不能直接操作 void* 指针所指向的对象
2.3.3 理解复合类型的声明
变量的定义包括一个基本数据类型(base type)和一组声明符
int i = 1024, *p = &i, &r = i;
示例中:int
是基本数据类型, *
&
都是声明符的一部分
定义多个变量
涉及指针或引用的声明,一般有两种写法:
- 把修饰符和变量辨识写在一起:
int *p1, *p2
- 把修饰符和类型名写在一起,并且每条语句只定义一个变量:
int* p1; int* p2
指向指针的指针
指针时内存中的对象,像其他对象一样有自己的地址,因此允许把指针的地址再存放到另一个指针中
指向指针的引用
引用本身不是一个对象,因此不能定义指向引用的指针。但是指针是一个对象,所以存在对指针的引用
int i = 42;
int *pi = 0;
int *&r = pi; // r 是一个对指针 pi 的引用, r 的类型是 int *
r = &i; // pi 指向 i
*r = 0; // i 现在为 0
要理解r
的类型,最简单的方法是从右向左阅读r
的定义。离变量名最近的符号对变量的类型有最直接的影响,声明符的其余部分用于确定r
引用的类型是什么。