深入探索 C/C++ 数组与指针的奥秘之三:数组的解剖学

深入探索 C/C++ 数组与指针的奥秘之三:数组的解剖学

C/C++ 的数组不同于 VB 等语言的数组,是有层次的,这个层次指的不是维度,而是象俄罗斯有名的套娃一样,一维套一维,亦即数组的嵌套,数组的元素也是数组,VB 等语言的数组与之相比更像一个平面。
数组嵌套这个现象从其它语言的角度来看有点奇特,但其实原因也很简单。C/C++ 的对象模型并不视数组为某种数值的简单集合,而是对象的聚集,每个元素都是一个对象。元素为整数对象,就是整数数组,为浮点数对象,就是浮点数数组。然而,数组本身也是一种对象,因此一个数组也能作为另一个数组的元素。当某个一维数组以一维数组作为元素时,这个一维数组每个元素都具有数组类型,这个一维数组其实是二维数组,同理,一个以二维数组作为元素的一维数组其实是三维数组。因此,使用 C/C++ 数组的时候应该用数组嵌套的观点去看待。有人据此认为,C/C++ 的数组不是真正的数组,还有的认为 C/C++ 没有多维数组,这些观点都有失偏颇,与其它语言的数组相比,两者只是同一事物的不同实例,是实现方法的不同,而本质是一样的,C/C++ 的数组嵌套可视为对数组概念的发展。
现在来看看数组的定义:
6.5.4.2 Array declarators
Semantics
If, in the declaration “T Dl.” Dl has the form
D [ constant expressionopt ]
这个定义非常简单,其中 T 代表元素类型,D 代表标识符,constant expression 必须为大于 0 的常量表达式,opt 表示可选,即 [] 中的内容可以为空,当 [] 为空时叫不完整类型,表示这个数组对象的长度未知,不完整数组类型可以在程序的某个地方补充完整。细心的人马上就会发现,从形式上看,怎么只有一维数组的定义?这个形式如何定义多维数组?刚才说过,C/C++ 的数组是数组的嵌套,因此多维数组的定义也反映了这个本质。多维数组的定义是通过嵌套的一维数组定义构造的。对于一维数组:
T D[M]
当元素为一维数组 T[N] 时,元素的类型也为数组类型,用 T[N] 代替 T,则为:
T[N] D[M]
这个语法结构不符合 C/C++ 数组定义的语法形式,将 [N] 移动到 [M] 后,就是正式的二维数组的定义了:
T D[M][N]
其中 D[0]---D[M-1] 都是一维数组,具有数组类型 T[N]。各种维度的多维数组可以用同样的嵌套方法构造出来。
一个一维数组 T[M],经过数组到指针的转换后,类型转换为 T*,二维数组 T[M][N] 转换为指针后,类型转换为 T ( * )[N],有些初学者对 T( * )[N] 这种形式较难理解,怎么多了一维,形式就有这么大的差别呢,其实原理还是跟嵌套有关,二维数组为一维数组的嵌套,元素为数组类型,因此用 T[N] 代替 T,则二维数组转换之后的指针类型为 T[N] *,将 [N] 移动到 * 的右边,就是 T*[N],由于 [] 的优先级比 * 高,因此需要加括号,就成为 T( * )[N] 了,否则就不是指针类型,而成了指针数组类型了。
围绕数组名,存在一些有趣的表达式,下面的内容通过讨论这些表达式中较为重要的几个,来加深对数组的理解。对于二维数组:
T a[M][N]
a:表达式中的 a 的类型转换为 T ( * )[N],代表数组的首地址;
&a:是一个指向二维数组对象的指针,类型为 T ( * )[M][N]。在 C 标准出现之前,一些早期的实现并不允许 &a,因为这些编译器认为此时的 a 转换为一个右值,而 & 运算符要求一个左值,因此非法。C 标准委员会鉴于对象的概念已经得到了扩展,而且允许 &a 并没有害处,因此把 & 运算符作为一个例外写进了数组到指针的转换条款中。这种情况下的 a 代表数组对象,&a 表示对数组对象取地址,因此 &a 的结果跟 a 是相同的,但类型不同。
笔者曾经见过某些观点认为,&a 才是数组的首地址,不是 a。这个观点初看起来似乎很有道理,一个数组对象的引用,不正是首地址吗?但实际上这种论述是不符合标准的,数组到指针的转换条款规定,当产生一个 points to the initial element of the array object 的时候,其前提是由 array of type 到 pointer to type 的转换,但 &a 的类型属于 pointer to array of type,不是 pointer to type,因此真正代表数组首地址的是 a 本身,不是 &a。
这两天闲逛时无意中看见了 09 年曾经回复的一个帖子,把这问题又重新思考了几遍,觉得上一段所阐述的理由依然比较牵强,现在觉得应该这样解释更好一点:数组名与数组首地址的关联来源于数组与指针的隐式转换条款,但对于 &a 来说,这里的 a 属于条款中的例外,并不存在这样的转换,就是说,这里的 a 与数组首地址并无任何关联,&a 的结果之所以与 a 的首地址相符,仅得益于 & 运算符的性质,而不是来源于数组名 a 本身,所以 &a 的意义并非数组首地址。---2011 年 1 月 9 日
&a[0][0]:这是数组首元素的地址。&a[0][0] 常常被误解为数组 a 的首地址,其实 a[0][0] 只不过由于位置特殊,其地址值才与 a 相同,&a[0][0] 是一个 T 类型对象的引用,不是一个数组对象的引用,而且其类型不是由 array of type 转换得来的,因此其意义不是数组首地址。
a[i](其中 i >= 0 && i < M):从数组嵌套的观点来看,a 是一个一维数组,元素的类型为数组类型,因此 a[i] 的类型为 T[N],在表达式中转换为 T*,是第 i 个一维数组的首地址。
a + 1:a 隐式转换为指针类型 T( * )[N] 然后加 1,请记住指针加法是以指针指向对象的大小为步长的,因此 a + 1 将跨过 N * sizeof( T ) 个字节。
&a + 1:与 a + 1 同理,&a 类型为 T( * )[M][N],因此 &a + 1 的步长为 M * N * sizeof( T )。
原文链接:http://blog.csdn.net/supermegaboy/archive/2009/11/23/4855010.aspx

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值