指针与数组的理解

写在前面

C标准规定了C语言的语法,但对于语法的理解记忆却可以千差万别,只要自己的理解能够在C语言语法正确的前提下自圆其说就好。关于下文的理解方法,欢迎批评指正。

1、指针变量的两个要点

一个指针变量,有两个要点:a) 指针指向的目标类型。b) 指针变量中的地址值
两个指针类型的变量,当它们的目标类型和存储的地址值相等时,则这两个变量参与运算时,具有相同的效果。可以认为它们是同一个“东西”。

2、定义指针变量时的规律

观察下面的 指针变量的定义该指针相对应类型的变量的定义 的比较:

这里写图片描述

在定义变量(也可以是函数,这里为了方便描述,都说成变量)的式子中,如果想要定义指向整个变量的指针,则把原式中的变量名替换为:* 指针变量名

定义 = 类型 + 名称
类型 = 定义 - 名称

如果不知道变量的类型,那么找到变量的定义,去掉变量名,剩下的就是变量的类型。

3、一个前提

看下面的代码:
这里写图片描述
这里写图片描述
这里写图片描述

代码中定义了一维数组 a 。C语言的书中都会告诉我们:数组名 a 是一个指针常量。根据上述的第一点(指针的两个要点),我们得知:1、指针常量 a 中的内容是数组首元素的地址;2、指针常量 a 有它的目标类型。

那么,数组名 a 作为指针常量,它的目标类型时什么呢?
在上图的代码中:
p = a ; 这条语句是成立的,
所以指针变量 p 和指针常量 a 的目标类型是一样的,指针变量 p 的目标类型是 int 。
所以指针常量 a 的目标类型也应该为 int ,即:一维数组名是指向数组的首元素的指针常量。

一维数组名是指向数组的首元素的指针常量。

通过 gdb 打印出 (a + 0) 和 (a + 1) 的值也可以印证这一点。

但是我们用gdb调试,打印出 a 和 p 的类型,得到的结果是:a 的类型是 int [ 5 ],p 的类型是 int * ,a 的类型不是我们希望的 int * 的类型。这里我更倾向的解释是:用 gdb 求 a 的类型时,gdb 返回的结果是整个数组的类型,而不是 a 作为指针常量的类型。

一维数组名是一个指针常量,其值为数组的首地址,其目标类型是数组中的元素的类型。即:一维数组的的数组名是指向数组的首元素的指针常量。 这句话很重要,后面还会重复提到。

4、关于定义的一种理解方法。

下面的截图来自《C和指针》一书的第三章。

这里写图片描述
这里写图片描述

指针与数组

首先,看下面的代码,这个代码已编译通过。
编译器版本为:gcc version 4.6.3 (Ubuntu/Linaro 4.6.3-1ubuntu5)

这里写图片描述

关于上图中的代码,有如下需要关注的地方:

二位数组与多维数组的理解

对于二维数组 int b [ 3 ][ 4 ] ; 这些表达式:b 、b [ 0 ]、b [ 0 ][ 0 ]、&b、&b [ 0 ]、&b [ 0 ][ 0 ]、p3 [ 0 ]、p3 [ 1 ]、p3 [ 0 ][ 0 ]、p4 [ 0 ]、p4 [ 1 ]、p4 [ 5 ]、p5。这么多的表达式,我们应该怎么去理解他们呢?

  1. 首先从数组的存储结构说起:

对于一维数组,我们很容易理解:

这里写图片描述

在内存中,a [ 0 ] ~ a [ 4 ] 从低地址到高地址依次在内存中排列。

对于二维数组,如下图:

这里写图片描述

二维数组的存储结构如上。

现有二维数组 int b [ 3 ][ 4 ] ; 我们上文提到:一维数组名是指向数组的首元素的指针常量。
那么对于二维数组呢?
显然,数组名 b 依然是指针常量,其值是数组的首地址,其目标类型是什么呢?我们需要 gdb 来告诉我们二维数组的数组名 b 的目标类型是什么。

这里写图片描述
这里写图片描述

我们可以得知:b 作为指针常量,它的类型是 ( int ( * )[ 4 ] ) , 它的目标类型是:( int [ 4 ] ),所以(b + 1)得到的值相对于(b + 0)值加上了0x10 .

b 的目标类型是:( int [ 4 ] ),这是一个包含4个整型元素的数组。用 gdb 调试,b [ 0 ] 的类型也是( int [ 4 ] ), 所以我们可以把 b [ 0 ] 理解为二维数组的“首元素”,这个“首元素”是一个包含4个整型元素的数组。

那么,我们可以把上文中的结论推广一下:

数组名是指向数组的首元素的指针常量。

对于一维数组的首元素 a [ 0 ] 的类型是 int ,数组名指向该首元素,所以其类型是 int *
对于二维数组的首元素 b [ 0 ] 的类型是 int [ 4 ],数组名指向该首元素,所以其类型是 int ( * )[ 4 ]
对于三维数组的首元素 c [ 0 ] 的类型是 int [ 3 ][ 4 ],数组名指向该首元素,所以其类型是 int ( * )[ 3 ][ 4 ]

所以,下图中对于指针变量的赋值就容易理解了。

这里写图片描述

我们也可以这么理解,把 int b [ 3 ][ 4 ] 和 int a [ 4 ] 比较,a 和 b [ 3 ] 占据了相同的位置,所以我们可以把 b [ 0 ]、b [ 1 ]、b [ 2 ] 当做数组名来对待。

这里写图片描述

把它们当做数组名来对待的话, 对于 b [ 0 ] 这个数组来说,b [ 0 ] 作为数组名是指向数组首元素的指针常量,数组的元素是 int 类型的,所以 b [ 0 ] 的类型是( int * ) 。

所以对于下图中的指针变量的赋值,就容易理解了。

这里写图片描述

我们之所以可以这样理解,这样的理解是对的,我们可以通过 gdb 来验证我们的想法。

这里写图片描述

b [ 0 ] 作为数组名,用 whatis 查看时,它的类型是 int [ 4 ] ,作为指针常量,是用p b [ 0 ] + 0 得出的类型是 int * 。这和一维数组的情况是一样的。所以,把b[0] 理解成为数组名是解释的通的。

三维数组

这里写图片描述

三维数组的存储结构如上。

这里写图片描述

对于三维数组,我从最外层看去,我们可以数组 c 看作是两个元素的一维数组,其中每个元素是 int [ 3 ][ 4 ] 类型的。

再往里看一层,我们可以将 int [ 3 ][ 4 ] 的每个元素看成是一个三个元素的一维数组,其中每个元素是 int [ 4 ] 类型的。

如果再往里看一层,每个 int [ 4 ] 元素是一个拥有四个 int 型元素的一维数组。

同样,数组名是指向数组首元素的指针常量。我们所说的首元素永远是指从最外层看去的首元素。所以这里的首元素是 int [ 3 ][ 4 ] 类型的。那么 数组名 c 作为指针常量,它指向的类型是 int [ 3 ][ 4 ] 类型的,所以 c 的类型是 int ( * )[ 3 ][ 4 ] 。下图中,我们通过 gdb 打印出了 c 和 c + 1 的类型和值。

这里写图片描述

同样,我们可以把 c [ 0 ] 和 c [ 0 ][ 0 ] 看成是数组名去理解。
这样我们就可以理解下面的三句赋值语句了。

这里写图片描述

&a 和 a 的理解

首先看下面用 gdb 给出的类型,比较 a 和 &a ,b 和 &b ,c 和 &c

这里写图片描述

可以看出,对数组名进行取地址运算,得到的类型是指向整个数组的;其中的值依然是数组的首地址。

这里写图片描述

上面我们提到,可以把 b [ 0 ] 和 c [ 0 ] 和 c [ 0 ][ 0 ] 都看成是数组名,对他们进行取地址运算,得到的类型同样是指向整个数组的。
上图中的用 gdb 查看类型,我们得出的结果也符合我们的预期。
啰嗦一句:b [ 0 ]、b [ 1 ]、b [ 2 ]、c [ 0 ]、c [ 1 ]、c [ 0 ][ 0 ]、c [ 0 ][ 1 ]、c [ 0 ][ 2 ]、c [ 1 ][ 0 ]、、、、、 等,都可以看做是数组名。

注意:我们上述所有提到的 a、b、b [ 0 ]、&b [ 0 ]、c、c [ 0 ]、c [ 0 ][ 0 ]、&c [ 0 ][ 0 ]、、、等等的表达式都只能作为右值,不能作为左值。

同时:我们对比也看到,b 和 &b[ 0 ] 指向的类型是一样的,并且它们的值是一样的。那么他们应该是等价的。

在《C和指针》一书中也给出了解释。
这里写图片描述

于是有下面的推导:

这里写图片描述

用我自己不专业的理解和记忆的方法,可以这样理解上面的推导。 对于数组名来说,&是升维的过程,[ ]是降维的过程。

那么我们就可以理解下面的赋值语句了:

这里写图片描述

  • 1
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值