对于很多C语言初学者来说,指针是一大难点,但是指针也是C语言非常关键的组成部分,离开了指针,C语言几乎就只能处理小学数学题了。其实,指针虽难,肯定没有难到大多数人学不会的程度。之前的文章也强调过:C语言只是一门基础工具,它面向的是普通人,而不是智商拔尖的精英,大多数人都能学会它。
上一节提到,在理解指针时,只需要像 char,short,int 一样,把它也当做是一种“数据类型”就简单了。C语言中变量的数据类型其实就是告诉处理器该如何访问它,以及如何解释它的,同样的,指针的类型也是如此。
先来看看下面这个题目
下面这个题目是美国某著名软件公司的一道面试题,它的C语言代码如下,请看:
#include <stdio.h>
int main()
{
int v[2][10] = {
{1,2,3,4,5,6,7,8,9,10},
{11,12,13,14,15,16,17,18,19,20}
};
int (*a)[10] = v;
printf("%d, %d, %d, %d, %d\n", **a, **(a+1),
*(*a+1), *(a[0]+1), *(a[1]));
return 0;
}
程序首先定义了一个二维数组 v,并赋予了 1~20 的初值,接着又定义了一个指针 a 并让其指向 v,并通过指针 a 依次输出 5 个值。那么,这个C语言程序会输出什么呢?
初步分析
显然,这是一道考察指针的题目,关键点在于指针 a。按照上面说的:“在理解指针时,要像 char,short,int 一样,把它也当做是一种数据类型”,那么 a 其实就是一个 int [10] 类型的数组指针。
“a 是 int [10] 类型的数组指针”,上一节文章已经较为详细的分析过,这里就不再赘述了,读者若感到费解,可以看看上一节的文章。
那么,这个C语言程序会输出什么呢?得到答案最简单粗暴的方法就是编译并执行这段程序,请看:
# gcc t.c
# ./a.out
1, 11, 2, 2, 11
答案是 1, 11, 2, 2, 11,为什么呢?最好理解的是 **a
,它肯定指向数组的第一个元素,也就是 1。剩下的几个,就需要分析指针的移动了。如果读者没能正确分析出答案,请继续往下看。
二维数组在内存中的数据存储
应该明白,即使是二维数组,其在内存中也是线性存储的:
以上面的C语言代码为例,当通过 v[1][2] 访问数据时,编译器其实是将其按照 v[1* 10 + 2] 处理的。在C语言中定义一维数组时,若是有初始化,不指定数组的元素个数也是允许的:
int a[] = {1, 2, 3};
但是在定义二维数组时,不指定二维数组的第二维就不允许了:
// 报错
int a[][] = {{1,2}, {3,4}};
相信读者现在应该明白原因了,因为编译器在访问二维数组数据时,需要知道数组的第二维数才能计算得到要访问的地址。知道了这一点,再分析上面这个面试题就简单了,请继续往下看。
C语言中的指针移动
文章开头提到:“语言中变量的数据类型其实就是告诉处理器该如何访问它,指针也是如此。”这句话是什么意思呢?请看下图:
数据在内存中最小粒度是一个字节,上图假设内存中存储了 9 个字节的数据,在我的机器,int 类型占用 4 字节内存空间,因此 int 型指针是逐 4 字节访问内存的,若 pi 指向地址 1,那 pi+1 就执向地址 5 了(逐4字节访问内存)。
同样的道理,short 类型占用 2 字节内存空间,因此 short 型指针是逐 2 字节访问内存的,若 ps 指向地址 1,那 ps+1 显然就执行地址 3 了。char 型指针的移动分析也是类似的,就不赘述了。
到这里,相信读者也发现了,指针的加减法似乎并不严格的遵循 1+1=2 的数学规则,对于 int 型指针来说,1+1 居然“等于”4,而对于 short 型指针来说,1+1 又“等于”2 !
其实,分析指针加减法的时候不应该只从数学层面考虑,1千克+1克也并不等于 2 克对不?应该考虑单位的,指针的“单位”就是它的类型:int 型指针的单位就是 sizeof(int),short 型指针的单位就是 sizeof(short),这样考虑的话,上面的加法就没有那么诡异了。
现在再来分析面试题的C语言程序就简单了,因为指针 a 是 int [10] 型的,所以在考虑指针 a 的加减法时,它的“单位”就是 sizeof(int [10]),也即是 10。那么,既然 a 指向数组 v 中的 1,a+1 就指向 11,这就解释了上面的C语言程序输出的第二个数字。
再来看看第三个数字。我们使用符号 T 表示 int [10] 类型,那么指针 a 就是 T 型指针,数组 v 的定义则可以写成 T v[2],那么 *a
显然就是 v[0] 了,*a + 1
指向的就是 v[0] +1,取出其中的值:*(*a+1)
显然等于 2。
第四个数字的分析和第三个数字是类似的(因为这里 a[0] 和 *a
是等价的)。第五个数字的分析也不难了,a[1] 和 *(a+1)
是等价的,而 a+1 指向的正是 11。
小结
本节,我们先分析了二维数组在内存中的存储方式,然后讨论了C语言中指针的移动,发现其实只要考虑指针的“单位”,分析指针的加减就简单了。最后,我们把指针像 short、 int 一样当作是一种数据类型,分析了一道面试题。