前言
学习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、声明和定义必须匹配!若是定义了数组,在其他文件声明时也必须是数组,指针亦然。
来源:https://www.cnblogs.com/IntellX/archive/2013/05/19/3086448.html