C51学习笔记,数组和指针的程序设计
扫描二维码
随时随地手机看文章
终于说到了指针。指针是C语言的精华部分,如果没有指针,c语言对底层的许多操作将无法完成。也是因为指针的存在,使得c语言看起来并不那么高级,因为指针操作的对象的是内存地址,想要熟练地进行指针操作,必须考虑到内存等偏硬件方面的东西。当然,也不需要了解过多。但是,数据结构这一关还是要过的。我对数据结构方面了解尚浅,就不多说了。数组与指针的关系如此复杂,让我不得不照着书来写这一篇笔记了。
一、数组不等于指针
C语言中,对数组的操作,是仿照指针的模式进行的。但是需要记住一点,数组不等于指针。对于一维数组a[],指向数组的指针p=a来说,他们之间最大的区别在于,数组方式使用数组名a(同时也是数组的首地址)对数组进行直接的访问和操作,而指针方式使用指针名p对数组进行的是间接的访问和操作。在多数情况下,他们操作结果是相同的,但是也有例外。
如果我们在文件外定义了一个指针p,int *p=a(a是一个整型数组)。在文件内用到p时,需要用extern声明一下,表明p是个外部变量,在外部定义好了。如果我们声明为指针 extern int * p,然后去使用,肯定是没有问题的。但是如果我们声明为数组extern int p[] ,问题就出现了,编译系统处理的时候会使用数组方式对指针进行操作。也就是说,当我们想得到*p,也就是*a,a[0]的值的时候,因为系统把p当作了数组首地址,所以*p并不能得到a[0],得到的是p所存放的地址值,也就是a[0]的地址。这种情况下,一定要使extern声明与定义相匹配。
数组的直接访问数据模式与指针的间接访问数据模式,是两者之间最根本的不同。数组不等于指针。数组通常用于存储固定数目且数据类型相同的元素,数组所占用的内存是隐式分配和删除的,数组中保存数据,并且数组中的每个元素都有唯一且明确的变量名来标识数据,使用数组可以直接访问数据也就是说a[i]只是简单地以a+i为地址取得数据。而指针通常用于动态数据结构,指针变量保存的是数据的地址(其中包括变量的地址,也包括不匿名数据),使用指针访问数据采用的是间接访问模式,即首先取得指针的内容,把它作为地址,然后从这个地址提取数据。如果指针有下标,p[i]就是先去的指针p的内容,然后把指针p的内容加上i作为地址,从中提取数据。指针可以指向匿名数据,所以要学会用指针操作匿名内存,c语言中与内存空间相关的函数为malLOC(), free()。
在定义指针时,编译器并不为指针所指的对象分配内存空间,只是分配指针本身的空间,除非在定义字符指针(必须是指向字符型的)的同时用字符串常量进行初始化。其实就算是这种情况,也可当作是编译器为此字符串常量分配了内存空间后,在为字符指针本身分配了空间,并使字符指针指向字符串常量的首地址。在ANSI C中,初始化指针所创建的字符串常量被定义为只读(?这一点,我用turbo C试了试,好像可以修改)。数组也可以用字符串常量进行初始化,但与指针襄樊,由字符串常量初始化的数组是可以修改的,原因很简单,由字符串常量初始化的数组本来就是一个字符数组,每个字符都有确定的变量名与之对应,所以当然可以修改单个字符了。而初始化指针所创建的字符串常量,其实是匿名的数据,如果你把指向字符串常量的字符指针赋予了其他地址,这个字符串常量显然再也找不到了。
由此可见,数组和指针在编译器处理时是不同的,在运行时的表示形式也是不一样的,并且可能产生不同的代码。对编译器而言,一个数组就是一个地址,一个指针就是一个地址的地址。所以,在外部数组的声明时,在数组的定义(因为数组的定义必然要分配内存空间)时,不能用指针来替代数组。
还有,在下列情况下,对数组的引用不能用指向该数组第一个元素的指针来替代:
1. 数组名作为sizeof()的操作数,因为此时需要的是整个数组的大小,而不是指针所指向的第一个元素的大小;
2. 使用&操作符取数组的地址。&操作符的主要用途是实现传址调用。指针本身就是地址,所以对指针使用&意义不大。
3. 数组是一个字符串常量初始值。这一点上面已经提到,不多说了。字符串常量初始化数组必须一气呵成,不能分成两了语句。也就是说,字符串常量只能对数组进行声明初始化,不能用字符串常量对数组赋值。这点是因为C语言中只能够在数组声明时对它进行初始化(这点没啥原因,也许是因为数组名是不可修改的左值吧),不能对数组名赋值,只能对数组元素逐个赋值。
4. 数组名是不可修改的左值,它的值是数组第一个元素的地址,不可以改变。
二、什么时候数组与指针相同
大多数的时候,数组和指针可以互换。
1. 除了个别情况,表达式中的数组名(数组在使用中,而不是声明中)就是指针
假如我们声明:int a[10], *p, i=2;
就可以通过以下方法来访问a[i] :
a[i];
*(a+i);
p=a ; p[i] ;
p=a ; *(p+i) ;
p=a+i ; *p ;
实际上,对数组的引用如a[i]在编译时总是被编译器改写成*(a+i)的形式。所以如加法一样,取下标操作符的操作数是可以交换的(a[i] 与i[a] 都是正确的,等价的)。
编译器自动把下标值得步长调整到数组元素的大小。对起始地址执行加法操作之前,编译器会负责计算每次增加的步长。这就是为什么指针总是有类型限制,每个指针只能指向一种类型的原因所在。
2. 数组下标总是与指针的偏移量相同
数组下标总是与指针的偏移量相同,所以程序员完全可以使用指针来访问数组,从而绕过下标操作符。在这种情况下,对数组下标范围检查并不能检测所有对数组的访问情况。因此,C语言并不进行下标范围检测。但是我们编写程序的时候,可要小心,不要越界。
在处理以为数组时,指针并不比数组更快。C语言吧数组下标改写成指针偏移量的根本原因是指针和偏移量是底层硬件所使用的基本模型。
3. 作为函数参数的数组名等同于指针
吧作为形参的数组和指针等同起来是出于效率原因的考虑。在函数参数的声明中,数组名被编译器当中指向该数据第一个元素的指针。编译器只向函数传递数组的地址,而不是整个数字的拷贝。隐性转换意味着下面三种函数定义形式是完全相同的:
fun(int *p){...}
fun(int p[]) {...}
fun(int p[10]) {...}
在函数的声明和调用上,使用数组或者指向数组第一个元素的指针都是合法的。
注意,这里第一个元素的含义,这第一个元素可以是数值变量、字符变量、数组、结构体、指针。
三、总结
数组不等于指针,但是数组可以用指针等效;指针始终是指针,指针绝对不可以改写为数组。你可以使用下标方式访问指针,一般是指针作为函数参数时,一般是指针指向的是数组元素时。
在外部声明和定义时,数组不能拿指针来等效,除了数组作为函数参数之外,定义和声明必须匹配。
在使用时,数组基本可以与指针互换。a[i]总是被编译器解释为*(a+i)。
在作为函数参数时,数组可以与指针互换。编译器总是把函数参数的数组修改为指向数组第一个元素的指针。在函数内部获得的事实上都是一个指针。
c语言实际上没有多维数组。只有数组元素是数组的数组。多维数组作为函数参数,传递的指针类型是指向数组的指针。