4 C++ 复合类型:引用和指针
复合类型是指基于其它类型定义的类型。C++ 有几种复合类型,包括引用、指针。
1 引用
引用(reference)为对象起了另外一个名字,引用类型引用另外一种类型。通过将声明符写成&d
的形式来定义引用类型,其中d
是声明的变量名:
int ival = 1024;
int &refval = ival; // refval 指向 ival(是 ival 的另一个名字)
int &refval2; // 错误:引用必须被初始化
一般在初始化变量时,初始值会被拷贝到新建的对象中。然而定义引用时,程序把引用和它的初始值绑定在一起,而不是将初始值拷贝给引用。一旦初始化完成,引用将和它的初始值对象一直绑定在一起。因为无法令引用重新绑定到另外一个对象,因此引用必须初始化。
定义了一个引用之后,对其进行的所有操作都是在与之绑定的对象上进行的。为引用赋值,实际上是把值赋给了与引用绑定的对象。获取引用的值,实际上是获取了与引用绑定的对象的值。同理,以引用作为初始值,实际上是以与引用绑定的对象作为初始值。
因为引用本身不是一个对象,所以不能定义引用的引用。
允许在一条语句中定义多个引用,其中每个引用标识符都必须以符号&
开头。除了两种例外情况,其它所有引用的类型都要和与之绑定的对象严格匹配。而且,引用只能绑定在对象上,而不能与字面值或某个表达式的计算结果绑定在一起。
int &refVal1 = 10; // 错误:引用类型的初始值必须是一个对象
double dval = 3.14;
int &refVal2 = dval; // 错误:此处引用类型的初始值必须是 int 型对象
2 指针
指针是 “指向” 另外一种类型的复合类型。与引用类似,指针也实现了对其它对象的间接访问。然而指针和引用相比又有很多不同点。其一,指针本身就是一个对象,允许对指针赋值和拷贝,而且在指针的生命周期内,它可以先后指向几个不同的对象。其二,指针无须在定义时赋初值。和其它内置类型一样,在块作用域内定义的指针如果没有被初始化,也将拥有一个不确定的值。
定义指针类型的方法是将声明符写成*d
的形式,其中d
是变量名。如果在一条语句中定义了几个指针变量,每个变量前面都必须有符号*
:
int *ip1, *ip2; // ip1 和 ip2 都是指向 int 型对象的指针
double dp, *dp2; // dp2 是指向 double 型对象的指针,dp 是 double 型对象
2.1 获取对象的地址
指针存放某个对象的地址,要想获取该地址,需要使用取地址符&
:
int ival = 42;
int *p = &ival; // p 存放变量 ival 的地址,或者说 p 是指向变量 ival 的指针
第二条语句把p
定义为一个指向int
型对象的指针,随后初始化p
令其指向名为ival
的int
对象。因为引用不是对象,没有实际地址,所以不能定义指向引用的指针。
除了两种例外情况,其它所有指针的类型都要和它所指向的对象严格匹配。
2.2 指针值
指针的值(即地址)应属于下列 4 种状态之一:
- 指向一个对象。
- 指向紧邻对象所占空间的下一个位置。
- 空指针,意味着指针没有指向任何对象。
- 无效指针,也就是上述情况之外的其它值。
试图拷贝或以其它方式访问无效指针的值都将引发错误。编译器并不负责检查此类错误,这一点和试图使用未经初始化的变量是一样的。
2.3 利用指针访问对象
如果指针指向了一个对象,则允许使用解引用符*
来访问对象:
int ival = 42;
int *p = &ival; // p 存放着变量 ival 的地址,或者说 p 是指向变量 ival 的指针
std::cout << *p; // 由符号 * 得到指针 p 所指的对象,输出 42
对指针解引用会得出所指的对象,因此如果给解引用的结果赋值,实际上也就是给指针所指的对象赋值。解引用操作仅适用于那些确实指向了某个对象的有效指针。
2.4 空指针
空指针不指向任何对象,在试图使用一个指针之前可以先检查它是否为空。以下列出了几个生成空指针的方法:
int *p1 = nullptr; // 等价于 int *p1 = 0;
int *p2 = 0; // 直接将 p2 初始化为字面值常量 0
int *p3 = NULL; // 等价于 int *p3 = 0; 需要 #include <cstdlib>
nullptr
是一种特殊类型的字面值,它可以被转换成任意其它的指针类型。NULL
是一个预处理变量,它的值就是 0。当用到一个预处理变量时,预处理器会自动地将它替换为实际值,因此用NULL
初始化指针和用 0 初始化指针是一样的。
把int
变量直接赋给指针是错误的操作,即使int
变量的值恰好等于 0 也不行。
2.5 赋值和指针
指针和引用都能提供对其它对象的间接访问,然而在具体实现细节上二者有很大不同,其中最重要的一点就是引用本身并非一个对象。一旦定义了引用,就无法令其再绑定到另外的对象,之后每次使用这个引用都是访问它最初绑定的那个对象。
指针和它存放的地址之间就没有这种限制了。和其它变量(只要不是引用)一样,给指针赋值就是令它存放一个新的地址,从而指向一个新的对象。
2.6 其它指针操作
只要指针拥有一个合法值,就能将它用在条件表达式中。和采用算术值作为条件遵循的规则类似,如果指针的值是 0,条件取false
,任何非 0 指针对应的条件值都是true
。
对于两个类型相同的合法指针,可以用相等操作符==
或不相等操作符!=
来比较它们,比较的结果是布尔类型。如果两个指针存放的地址值相同,则它们相等;反之它们不相等。这里两个指针存放的地址值相同有三种可能:它们都为空、都指向同一个对象、都指向了同一个对象的下一个地址。需要注意的是,一个指针指向某对象,同时另一个指针指向另外对象的下一地址,此时也有可能出现这两个指针值相同的情况,即指针相等。
2.7 void*
指针
void*
是一种特殊的指针类型,可用于存放任意对象的地址。一个void*
指针存放着一个地址,这一点和其它指针类似。不同的是,我们对该地址中到底是个什么类型的对象并不了解。
利用void*
指针能做的事比较有限:拿它和别的指针比较、作为函数的输入或输出,或者赋给另外一个void*
指针。不能直接操作void*
指针所指的对象,因为我们并不知道这个对象到底是什么类型,也就无法确定能在这个对象上做哪些操作。
概括来说,以void*
的视角来看内存空间也就仅仅是内存空间,没办法访问内存空间中所存的对象。
3 理解复合类型的声明
3.1 指向指针的指针
一般来说,声明符中修饰的个数并没有限制。当有多个修饰符连写在一起时,按照其逻辑关系详加解释即可。以指针为例,指针是内存中的对象,也有自己的地址,因此允许把指针的地址再存放到另一个指针当中。
通过*
的个数可以区分指针的级别。也就是说,**
表示指向指针的指针,***
表示指向指针的指针的指针,以此类推。对应的,解引用时也有相应的级别。
3.2 指向指针的引用
引用本身不是一个对象,因此不能定义指向引用的指针。但指针是对象,所以存在对指针的引用。
原文地址:https://blog.csdn.net/zhaobenjian/article/details/143807466
免责声明:本站文章内容转载自网络资源,如本站内容侵犯了原著者的合法权益,可联系本站删除。更多内容请关注自学内容网(zxcms.com)!