为何不精通C? 04 辨析数组与指针

前言

    学习C语言,指针绝对是一道大坎,很多人谈指针色变,使用起来小心翼翼的。“一切指针都是纸老虎” ,同时,对我们得“在战略上藐视指针,战术上重视指针”。

本文先剖析一维数组和指针,多维的情况后序博客继续更新。

文章流程:

1、辨析指针和数组的不同

2、辨析它们相同的时刻

3、总结


指针和数组为什么这么纠缠不清

    首先说一点,指针的使用等同于数组的情况远远多于他们不同的情况,因此,在初学时,为了容易理解,很多人都说 “指针=数组”。 但是,这却是错误的!

来个例子吧:

// file1.c 定义一个数组array
int array[100];
以上是在文件1中的定义

// file2.c 声明file1.c中的array
extern int* array
以上是在文件2中的声明

上面的例子对不对呢? 大家可以自己测试下,会很神奇的发现: 咔,怎么编译出啦! 为什么呢?

继续深入声明和定义

    在辨析数组和指针前,首先要说下声明和定义的区别,之前博文“为何不精通C? 03 深入剖析声明” 中的末尾已经提过这一点了。这里重新说下:

C语言中,对象有且仅有一个定义,而声明却可以有多个extern 声明

定义:只能出现在一个地方,确定同时分配内存,它是特殊的声明

声明:只是描述其他地方创建对象的属性。有extern前缀,作用于变量

对于声明,就像断言一样,说是什么就是什么,不容许有妥协的余地,因为在声明的同时,也规定了指针/数组的移动方式及长度。

So, 对于上面的例子,我们可以知道,file2.c中,array被声明成一个int指针。但是,file1.c 中的定义却是数组。 他们不兼容! 为什么不兼容呢?

通过指针和数组访问数据的方式不同

    这里先补充一个知识点,sizeof(x) 这个运算符,它返回的是x在内存中的字节数。

  1 #include "stdio.h"
  0 main ()
  1 {
  2   int array[20];
  3   printf("类型\t字节数\t指针字节数\n");
  4   printf("%s\t%d\t%d\n","char",sizeof(char), sizeof(char*));
  5   printf("%s\t%d\t%d\n","short",sizeof(short), sizeof(short*));
  6   printf("%s\t%d\t%d\n","int",sizeof(int), sizeof(int*));
  7   printf("%s\t%d\t%d\n","long",sizeof(long), sizeof(long*));
  8   printf("%s\t%d\t%d\n","float",sizeof(float), sizeof(float*));
  9   printf("%s\t%d\t%d\n","double",sizeof(double), sizeof(double*));
 10   printf("%s\t%d\t%d\n","array",sizeof(array), sizeof(&array[0]));
 11   return 0;
 12 }

这里,我们可以归纳出,所有的指针长度都是固定的, 都是4字节(因为我的电脑是32位=4*8bit。同理,在64位电脑这里显示8)。
我们认真观察下第10行,sizeof(array), sizeof(&array[0]),(因为我们都说数组名就是首地址指针,所以我这么表示。)

可以看出,sizeof(array)打印出了80, 即20*4,20个int的字节长度。但是sizeof(&array[0]),还是4,指针的固定长度。喏,一大区别就在这!

好,通过sizeof, 能够很好认识到原来数组和指针还真存在着不同点。现在继续说下通过指针和数组对数组元素访问的不同策略

由sizeof可以知道,在定义数组时,等同于在内存中连续分配了N长度的空间,数组的首地址(假设是5000)代表这段空间的起始点。比如通过array[i]访问时,步骤如下:

  • 移动到 5000+i*4 的地址
  • 取存在这里的值

而通过指针呢? 我们都只是指针中保存的是地址值, 如 int*p = array; 这里,假设p的地址为6000, 它存的值为array的首地址5000, 通过 p[i]访问是,步骤如下:

  • 取指针p中的值5000,移动到该地址
  • 移动到5000+i*4的地址
  • 取存在这里的值

可以看出,多了一个步骤。这个多出来的步骤,其实大家都知道,这也说明了指针的灵活性。

关于字符串数组、指针的初始化

关于字符数组,我们可以这么来初始化:

char *p  = "helloworld";
char a[] = "helloworld";

我们来辨析下内部隐含的区别吧:

对于指针p, 系统为我们分配了匿名一个字符串常量,这个常量是只读的,把它的地址给了p, 因此不能通过p[i]修改字符串常量的值。

对于数组a, 系统定义了一个连续的内存块来分配字符串,它的首地址是a, 我们可以通过 a[i]操作来修改某个字符的值。

关于字符串数组,指针的左值性

在C语言中,数组的首地址被定义为“不可修改的左值”, 即类似  int * const a 这样的声明, 我们可以通过它修改它指向的内容,却不能把它赋个新值。即:允许a[i]=yyy,而对 a=xxx 却是提示非法的!

指针呢,就看他怎么声明咯, 一般来说,就是 int* p, 我们可以 p=a, p=xxx, p[i]=yyy ……,都是允许的。

好啦,大概的区别点就是上述表示的了,那我们继续探讨下他们什么时候相同。


指针等同于数组的时候

我们知道,指针充满了灵活性,而数组却是比较死板的东西,这么说来,就必须先理解好数组,才能更好的了解指针,我们先来说下数组。对于C语言来说,任何事物都必须声明,再使用,我们也按这个顺序,先说下声明:

数组的声明

  • 1、外部数组的声明
  • 2、数组的定义
  • 3、函数参数的声明, 这时候,随便你写指针形式还是数组形式,他们都是等同的。

数组的使用

     任何时候,都可以变成指针表达式

 

 

 

 

 

 

 

 

 

 

我们归纳下:

1、 “表达式中的数组名” 就是指针, 及你可以使用a[i]; *(a+i)p=a, p+i,*p;等形式表达同样的意思。

2、C语言将数组下标作为指针的偏移量

3、作为函数形参的数组名,等同于指针,即:

my_func(int* p);
my_func(int p[]);
my_func(int p[200]); 

都是一个意思,编译器都把它翻译成指针。


总结

采用《C专家编程》中的总结,我适当修改合并了一些。作者建议我们在自己真正理解数组、指针后,要先自我总结下,再看他的总结。

1、用 a[i]形式的表达式,编译器都解释成*(a+i);

2、指针永远是指针,不可改写成数组。你可以通过下标形式访问指针,且必须是你知道指针指向了是一个数组。 毕竟,编译器并不知道 int*指向了什么,一个int数组还是int变量地址。

3、作为函数参数时,数组都被改写成指针的形式来表达,即类似于 p=&a[0];因此,对于编译器而言,参数表中没有数组,只有指针。

4、声明和定义必须匹配!若是定义了数组,在其他文件声明时也必须是数组,指针亦然。

posted @ 2013-05-19 11:19  xield  阅读(501)  评论(19编辑  收藏  举报