首页 > 代码库 > 《C专家编程》第四章——令人震惊的事实:数组和指针并不相同

《C专家编程》第四章——令人震惊的事实:数组和指针并不相同

  数组和指针是C语言里相当重要的两部分内容,也是新手程序员最容易搞混的两个地方,本章我们锁定指针与数组,探讨它们的异同点。

  首先来看指针与数组在声明上的区别:

  int a[10];

  int *p;

  很明显的,第一个是数组a,第二个是指针p。下一个问题是a的类型是什么?p的类型是什么?a[0]的类型是int,而a是个数组名,它是否表示整个数组呢?事实并非如此,a是一个指针常量,是一个指向int的指针常量,而p是一个指向int的指针,是一个变量。这是它们的第一个区别一个是常量,一个是变量。那么常量和变量之间存在的区别,它们都存在。比方说,变量可以相互赋值,而常量则不行,如下面这个例子:

  int a[10];

  int b[10];

  int *c;

  a与b均是数组名,为常量指针,而c是一个变量,所以你可以对c进行赋值,比如说c = &a[0],或者c = a,都没有问题,可是考虑一下b = a吧,一下子就会发现这条语句是非法的,如果你要复制一个数组,只能一个变量一个变量的用循环赋值。

  第二个重要的区别是两者的访问方式不同,尽管它们的访问形式看起来都可以用下标引用。

  char a[] = "helloworld";

  char *p = "helloworld";

  比如在上面两个定义中,如果你查看a[3]的值,那么肯定是 ‘l‘,但是同样的你也可以查看p[3],答案同样也是 ‘l’,不过访问的方式稍有区别:

  首先看到a[3],它等同于*(a + 3),我们假定a这个常量指针的地址为1000,那么数组在内存中可能是这样子的:

  技术分享

  直接代入a的地址看看,那么就是*(1000 + 3),就是1003号地址,显而易见的就是字符 ’l‘。再来看看指针访问的方式:

       技术分享

  这里面的“Helloworld”是一个字符串常量,系统会为它分配一片内存,2345这个地址是随机分配的,指针的地址1032也是随机的,让指针p指向一个常量字符串就是将地址1032的内容改为字符串常量的首地址,那么现在来看p[3]的意思,首先要取p的地址中的内容也就是2345,然后将下标所表示的偏移量加上指针的值,产生一个地址,那么就是2345+3,这个地址就是2348,之后在再对2348这个地址进行访问,从而得到字符 ‘l‘.

  用char说明可能比较简单,在这里用char作为类型会让你忽略一个细节,如果使用int的话,这个细节将会被你注意到,那就是它们计算地址的方式,比方说还是以上面的例子,不过这次换成int作为类型。

  int a[10];

  int *p;

  同样考虑a[3]与p[3],这下子问题就来了,char在内存中刚好占一个字节,而int,一般来说,在32位机器上占了4个字节,那么a[3]是否还等于*( a + 3)呢?那明显不对,因为假定a[0]的地址为1000,那么a[1]的地址就是1004了,a + 3的值为1003的话应该取多少呢?答案是,在这里应该这么计算,它等于:

  *( a + 3 * int)

  要乘以一个类型长度,所以它实际上就是*( a + 3 * 4),取的地址是1012,。

  那么对于p[3]呢?也是一样的,由于指针的类型不同,指针也会要乘以一个int的长度值。

  经过对两种访问方式的探讨,我们可以发现,如果你把声明和定义搞混了,那是要出问题的,比如说,你定义了一个数组char p[10],但是你在其他文件使用它的时候对它进行了这样的声明extern char *p,那么你就有大麻烦了,由于数组和指针的访问方式不同,你的数组中的那个值可能要被作为一个地址,把一个数值当成一个地址,再去取里面的值,它是一个垃圾值。

  当然,除了访问方式的不同,它们保存数据的方法也有区别

   仍然如上例,当你声明定义一个数组时,它会实际为你分配一片区域;而你声明定义一个指针时,它只为你分配指针变量的内存,指针所指向的值并不分配内存,并且这种情况只对字符有用,比如说你可以这样:char *p = "helloworld",但是,你却不能这样:float *p = 3.14,这条语句是非法的。

  主要区别就在于上面的几点,另外下面来看看它们之间的相同之处:体现得最明显的一点就在于当数组名作为函数的参数传递时:

  int function(char a[]);

  像上面这条语句肯定是没有问题的,它说明一个字符数组被作为参数传递给函数function,不过它居然没有说明数组的大小呢?仔细想想,你就会觉得这个用法相当机智,有下面几点原因:首先这说明所有的数组无论大小,都可以作为同一个函数的参数,你想想,如果处理只有两个元素的数组和只有三个元素的数组都要编写不同的函数,那将会是一个相当恐怖的事情;

  后面的优点则要基于它的传递行为,上面那个语句等价于下面这个:

  int function(char *a);

  这能够很好的应用于函数的一个性质:所有的参数传递均为传值调用,也就是说形参都只是实参的一份拷贝,在function这个函数里,由于传递的参数是指向原数组a的一份指针拷贝,所以你可以放心的使用a++之类的语句,而不用担心编译器会警告你修改数组名。

  从上面的比较可以看出,指针和数组之间关系密切,两者既相似又有很大的区别,所以大家使用时一定要小心,上面只是列出它们之间的主要区别和主要共同点,至于有其它的一些关系,大家可以参考其他资料。

《C专家编程》第四章——令人震惊的事实:数组和指针并不相同