其他章节:C++ Primer 学习心得
- 复合类型指基于其他类型定义的类型——引用和指针。
- 一条声明语句由一个基本数据类型和紧随其后的一个声明符(变量名)列表组成。
- 每个声明符命名了一个变量并指定该变量与其基本数据类型有关的某种类型。
引用
主要阐述左值引用
引用为对象起了另外一个名字,引用类型引用另外一种类型,声明符写为&d
的形式定义引用类型。
int ival = 1024;
int &refVal = ival; // refVal指向ival(是ival的另一个名字)
int &refVal2; // 报错:引用必须被初始化
- 一般在初始化变量时,初始值会被拷贝到新建的对象中
- 定义引用时,程序把引用和它的初始值绑定在一起,而不是将初始值拷贝给引用。
- 初始化完成,引用将和它的初始值对象一直绑定在一起。
- 无法令引用重新绑定到另外一个对象,因此引用必须初始化。
引用即别名
- 引用并非对象,它只是一个已经存在的对象所起的另一个名字。
- 定义引用之后,对其的所有操作都是在与之绑定的对象上进行的:
- 为引用赋值,是把值赋给了与引用绑定的对象;
- 获取引用的值,是获取了与引用绑定的对象的值;
- 引用作为初始值,是以引用绑定的对象作为初始值。
refVal = 2; // 把2赋给 refVal指向的对象,此处即是赋给了ival
int ii = refVal; // 与 ii = ival 执行结果一样
// 正确:refVal3 绑定到了那个与refVal绑定的对象上,这里就是绑定到ival上
int &refVal3 = refVal;
// 利用与 refVal绑定的对象的值初始化变量 i
int i = refVal; // 正确:被初始化为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; // 错误:此处引用类型的初始值必须是int型对象
指针
指针是指向另外一种类型的复合类型,实现对其他对象的间接访问。
- 指针本身就是一个对象,允许对指针赋值和拷贝,而且在指针的生命周期内它可以先后指向不同的对象。
- 指针无须在定义时赋初值。与其他内置类型一样,在块作用域内定义的指针如果没有被初始化,也将拥有不确定的值。
- 定义指针类型的方法将声明符写成
*d
的形式。
获取对象的地址
指针存放某个对象的地址,需使用取地址符(&
)获取地址。
int ival = 42;
int *p = &ival; // p存放变量ival的地址,或者说p是指向变量ival的指针
- 引用不是对象,没有实际地址,不能定义指向引用的指针。
- 声明语句中的指针的类型必须严格匹配它所指向的对象类型。
指针值
指针的值(地址)应属下列四种状态之一:
- 指向一个对象。
- 指向紧邻对象所占空间的下一个位置。
- 空指针,指针没有指向任何对象。
- 无效指针,上述情况外的其他值。
- 拷贝或以其他方式访问无效指针的值都将引发错误,编译器不检查此类错误。
- 2与3形式的指针都是有效的,但没有指向任何具体对象,访问此类指针对象的行为不被允许。
利用指针访问对象
如果指针指向了一个对象,允许使用解引用符(*
)来访问对象。
指针解引用会得到所指的对象,给解引用的结果赋值,就是给指针所指的对象赋值。
解引用操作仅适用于确实指向某个对象的有效指针。
声明 | 表达式 | |
---|---|---|
& | 引用 | 取地址符 |
* | 指针 | 解引用符 |
int i = 42;
int &r = i; // &紧随类型名出现,因此是声明的一部分,r是一个引用
int *p; // *紧随类型名出现,因此是声明的一部分,p是一个指针
p = &i; // &出现在表达式中,是一个取地址符
*p = i; // *出现在表达式中,是一个解引用符
int &r2 = *p; // &是声明的一部分,*是一个解引用符
同一个符号不同的含义,看作不同的符号。
空指针
不指向任何对象,试图使用一个指针之前可以检查是否为空。
生成空指针的方法:
- 最直接办法:字面值
nullptr
来初始化指针,可以被转换为任意其他的指针类型。 - 将指针初始化为字面值
0
。 - 名为
NULL
的预处理变量给指针赋值。
int *pl = nullptr; // 等价于int *p1=0;
int *p2 = 0; // 直接将p2初始化为字面常量0
// 需要首先#include cstdlib
int *p3 = NULL; // 等价于int *p3=0;
- 将
int
变量直接赋给指针是错误的操作,即使int变量的值为0也不行。int *p = 0; // 正确
初始化所有指针
- 定义对象之后再定义指向它的指针。
- 不清楚指针指向何处,就先初始化为
nullptr
或者0
。
赋值和指针
- 指针和引用都提供了对其他对象的间接访问,具体的实现细节上有很大不同。
- 引用本身并非一个对象,一旦定义了引用,就无法再令其绑定到另外的对象,之后每次使用这个引用都是访问它最初绑定的那个对象。
- 指针与其他任何变量(除引用外)一样,给指针赋值就令他存放一个新的地址,指向一个新的对象。
int i = 42;
int *pi = 0; // pi 被初始化,但没有指向任何对象
int *pi2 = &i; // pi2 被初始化,存有i的地址
int *pi3; // 如果pi3定义于块内,则 pi3的值是无法确定的
pi3 = pi2; // pi3和pi2指向同一个对象i
pi2 = 0; // 现在 pi2不指向任何对象了
搞清楚赋值语句改变了指针的值还是改变了指针所指向对象的值,赋值改变等号左侧对象。
pi = &ival; // pi的值被改变,现在的pi指向了ival
*pi = 0; // ival的指被改变,指针pi并没有改变
其他指针操作
- 指针在条件表达式中,如果指针的值是
0
,条件取false,任何非0
指针对应的条件值都是true。 - 对于两个类型相同的合法指针,可以用相等操作符(
==
)或不相等操作符 (!=
)来比较它们,比较的结果是布尔类型。- 如果两个指针存放的地址值相同,则它们相等;反之它们不相等。
- 两个指针存放的地址值相同(两个指针相等)有三种可能:
- 都为空
- 都指向同一个对象
- 都指向了同一个对象的下一地址
- 一个指针指向某对象,同时另一个指针指向另外对象的下一地址,此时也有可能出现这两个指针值相同的情况,即指针相等。
int ival = 1024;
int *pi = 0; // pi合法,是一个空指针
int *pi2 = &ival; // pi2是一个合法的指针,存放着ival的地址
if (pi) // pi的值是0,因此条件的值是false
//...
if (pi2) // pi2指向ival,因此它的值不是0,条件的值是true
//...
void*指针
void*
可以存放任意对象的地址,对该地址中是什么类型的对象不了解。
复合类型的声明
同一条定义语句中,虽然基本数据类型只有一个,但是声明符的形式可以不同,一条定义语句可能定义出不同类型的变量。
// i是一个int型的数,p是一个int型指针,r是一个int型引用
int i = 1024, *p = &i, &r = i;
定义多个变量
int* p1, p2; // p1是指向int的指针,p2是int
- 指针或引用的声明,两种写法:
- 把修饰符(
*
或&
)和变量标识符写在一起,强调变量具有复合类型; - 把修饰符和类型名称写在一起,每条语句定义一个变量,强调声明定义了一种复合类型。
- 把修饰符(
int *p1, *p2; // p1和p2都是指向int的指针
int *p1; // p1是指向int的指针
int *p2; // p2是指向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
的含义。