1

常见指针类型入门

指针是C++中极其重要的概念,如果说某汪在华语乐坛占据了半壁江山,那么指针也是支撑C++众多高级特性的一双有力的大手。本文简要说明C++中指针的定义及指针类型的判断,其中指针类型的判断往往是初学者最大的障碍,但这也是最需要重点掌握的基础技能

一、什么是指针

  1. 指针的定义

    从各种教科书上大同小异的定义来看,指针就是保存其他变量的地址的变量。要想弄懂这个定义,我们需要先搞清楚两个概念:变量、变量的地址。

  2. 变量、变量的地址

(1)变量的引入

变量是用于存储某一数值的“容器”(“容器”这一词在C++和其他领域中是专业术语,这里只是用于比喻,不要把这个词当成对变量的术语,牢记!),我们在计算机中编程时,总体来看都是“输入——计算——输出”这一套路,所以往往会有大量的数据,如果程序中全部的数据都是使用字面常量的方式来记录,那么先不说可实现性,光光是程序的编写和维护就是一件几乎不可能的事了,所以从BCPL和B语言之后(准确地说是C语言后),正式有了变量的概念。程序员只需要定义好某种类型的变量,就可以存储对应类型的数据,而不需再再像在无类型语言里那样关心数据在内存中是如何存储的了。

(2)逻辑上的内存模型

这里所说的内存模型是高级语言程序员脑中的内存模型,实际上并不存在;内存的物理模型不需要高级语言的初学者考虑,故不在此讨论
程序运行在内存中,数据和指令都加载进内存中(然而实际上并不是这么理想,在之后的文章中我会再介绍操作系统对内存的管理),所以为了让访问内存的设备能准确地定位到某一处,内存的每一处都分配了一个唯一的数值,相当于身份证号,称作地址。内存地址是二进制的数值。32位机的寻址能力就是能在由32位标识的内存地址中进行定位,所以内存大小被限制在4GB。
地址这一词与人类社会的门牌号很像,人住在屋子里,而屋子有一个编号,人看作数据,屋子就是存储“人”这一类型的变量,而门牌号就是变量的地址。

  1. 指针的作用

    看了以上这么多的文字,那么指针的类比概念也很好理解了,不过到此我们需要回答一个问题:为什么需要指针?通过指针来操作变量还是一个二级的操作,这不会显得很麻烦吗?

是的,通过指针我们确实比直接操作变量多做了工作,但恰恰就是指针,也给了程序员直接操作内存的机会,因为直接操作内存比操作变量在时间上要快上很多,这也是为什么C++程序运行的效率高的原因之一。

  1. 指针的类型

    指针(pointer)的全名其实是指针变量,也就是说指针也是变量。既然是变量,那么很自然的也就有类型这一问题。C++是强类型的静态语言,某一种类型的变量就只能存储其对应类型的数据,指针也不例外。通常,当某一类型指针保存着其对应类型的地址时,我们说“这个指针指向了那个变量”。


二、指针的类型

正文从这开始。
把指针考虑成变量,所以我们从最基本的变量(以整型为例)的定义开始分析。

  • int p;

p是一个整型变量。

  • int *p;

p是一个指向整型变量的指针。

  • int p[SIZE];

p是一个数组,数组中有SIZE个元素,每个元素都是整型。

  • int *p[SIZE];

从这里开始变得有趣了
对于这种“复杂”类型的定义,有一种方法是从中间开始读,根据运算符优先级向两边扩展。
p是一个数组(先与定义数组的下标运算符结合,因为下标运算符[]的优先级高于解引用运算符*),数组中有SIZE个元素,然后与*结合,数组中的元素都是指针,最后看int,即指针指向int。所以,把以上的分析过程连起来,就是,p是一个由SIZE个指向整型的指针构成的数组。

  • int (*p)[SIZE];

这里我们会开始简单看到()运算符的其中一个作用
首先,从p开始寻找优先级比*高的运算符——是的,()在这里的意义和数学中的小括号是一样的,都是用于强行改变运算优先级的运算符。所以,因为有(),p先与*结合,p是一个指针,然后再与[]结合,p这个指针指向一个有SIZE个元素的数组,最后,看int,即,p是一个指向由SIZE个整型元素构成的数组的指针。

  • int p(int);

这里就是()的另一个作用
这里的()是函数调用运算符,()内写清了参数列表,所以,p是一个函数,函数有一个整型变量的参数,函数返回值为整型。

  • int p(int, int *, int);

分析过程同上一点,直接给出结果。
p是一个函数,有三个参数,依次为整型、指向整型的指针和整型,返回值为整型。

  • int **p;

C++中没有**这种的运算符,所以这种写法是等同于int *(*p);的。
其表示,p是一个指针,指向了一个指针变量,且被p指向的那个指针指向整型。
这种指针称作二级指针,类似的,还有多级指针,但在实际编写程序时,二级指针的使用已经比较少了,多级指针更是几乎(不代表没有用处)不会使用,所以初学者不必考虑多级指针。

  • int (*p)(int *, int);

p是一个指针,指向了一个函数,这个函数的参数列表为(int *, int),返回值为int
这里的p,我们就称之为函数指针,因为它指向了函数。函数指针的概念需要实际练习几次才能初步掌握。

  • void (*p[SIZE])(int *, int);

这种复杂程度的类型定义是程序中经常见到的,所以一点一点地来分析。
p先与[]结合,说明p是一个数组;再与左侧的*结合,说明数组里的元素都是指针;之后与右侧的函数调用运算符()结合,那些指针指向的函数的参数依次为(int *, int),返回值类型为void。所以,再整理一下刚刚的分析:
p是一个由SIZE个元素构成的数组,每个元素都是函数指针,其指向的函数的参数依次为指向整型的指针、整型,返回值类型为void
这种类型要求重点掌握。

  • int *(*p(int))[SIZE];

这种类型的可以先跳过,在确认了掌握了上面讲述的所有定义后再来尝试理解。
从p开始分析,先与()结合,说明p是一个函数;然后进入到参数列表中,发现这个函数的参数只一个int;再看p左侧的*,说明这个函数的返回值是一个指针;之后到外层(这里外层的()是为了强制改变优先级),先与[]结合,说明函数返回的指针指向的是一个数组;再与左面的*结合,说明指向的数组中的元素都是指针;最后与int结合,说明数组中的元素指向整型。
所以,p是一个参数为一个int、且返回一个指向由SIZE个int *构成的数组的的指针的函数。

    这里思考,如果去掉最外层的`()`,即改为如下:
    `int **p(int)[SIZE];`
    会是什么结果?为什么?欢迎在之后的讨论区发表意见。

三、结语

常用的指针类型(或者直接说是类型)定义大多都是如上类型的变种,当然会有更复杂的,不过再复杂的都可以用上述的方法来进行分析。


Soap
78 声望25 粉丝