数组和指针在一定程度上可以互相使用,但是从本质上来说,他们是有本质的区别的。
X=Y的本质
介绍数组和指针之前,首先说一下X=Y这个简单的赋值语句的本质。
对于一个变量,如X或者Y,他是有两种含义的,一是代表着是X的那一块内存空间,二则达标者X的内容。
对于X=Y,左边的X代表的是X的那一块内存空间,而Y代表着Y的内容,可以理解为:将Y变量的内存空间里面的内容复制到X变量的内存空间里面去。
更加专业的角度来说,赋值符的左边成为左值,右边成为右值。编译器为每一个变量都分配一个地址(左值),这个地址是编译器知道的,而且这个变量和这个地址是永久对应不会改变的。也可以说:对于编译器来说,一旦为变量分配了地址,变量名就是这个地址。存储在这个地址的内容(右值)只有在运行的时候才是可以知道的。
综上所述:变量的地址永远不变,变量的内容在运行时可以改变,我们对变量的修改操作对象其实就是变量的内容。
?
数组和指针的访问方式
每个变量(数组和指针)的地址都是编译器可知的,因此数组的存储地址编译器是知道的,每次访问第N(常数)个元素的时候编译器可以直接计算出来这个元素的所在地址。而对于指针来说,编译器虽然知道这个指针变量的存储地址,但是他不知道这个指针变量的内容,及指针指向的地址,所以如果要通过指针访问第N个元素,需要在运行的时候获得指针指向的地址,然后通过计算获得该元素的最终地址。
举例:数组的访问方式
Char a[5] ="abcd";
Char c=a[i];
假如,编译器为数组a分配了5个字节的内存,首地址为9980。
运行时:
步骤1:取i的值,并将其和9980相加。
步骤2:取(9980+i)的内容。
两步读内存过程。
举例:指针的访问方式
char p;
…
Char c=p[i];
假定,编译器为指针变量p分配了地址4624,地址大小为四个字节(32位)。编译器并不知道这个指针指向哪里,因为他不知道里面存储的内容,需要在运行时才知道。
运行时
步骤1:取变量p的值,即地址为4624的内容,假定取出来后为9980.
步骤2:取i的值,将其与9980相加。
步骤3:取地址为(9980+i)的内容。
三步读内存过程。
数组和指针的区别
指针 | 数组 |
保存数据的地址 | 直接保存数据 |
间接访问数据。首先取指针内容,然后将其作为地址再去取数据。 | 直接访问数据。根据数组的地址。 |
通常用于动态数据结构-堆,当然,指向数组也可以。 | 存储固定数目且数据类型相同的数据,一般是放在栈里面作为临时变量。 |
使用malloc、free为其分配内存。 | 隐式分配、删除 |
指向匿名数据 | 本身就是数据,数组名就是数据的名字 |
数组和指针在定义时都可以用字符串常量对其初始化,但是底层机制不同。
定义指针时,编译器为指针分配空间,创建的字符串常量是只读的,并将其地址存放在指针变量中,因此不能对其进行修改。
而通过字符串常量初始化数组,会将每个字符存入到编译器为数组分配的内存空间里面,也就是可以修改的。
数组和指针的使用
声明:
- extern char a[];不能修改为指针的形式。
- 定义:char a[10];不能修改为指针形式。
- 函数参数:f(char a[]);数组形式或指针形式都可以
表达式中的使用:
- 如c=a[i];数组或指针形式都可以。
数组和指针的可交换性
什么时候数组和指针是相同的,C语言标准中做出了一下的说明:
- 表达式中的数组名被编译器当作一个指向该数组第一个元素的指针。
- C语言把下标作为指针的偏移量。
- 作为函数参数的数组名等同于指针。
可交换性的总结:
- 用a[i]的形式访问数组,将被编译器解释为(a+i)这样的指针访问。
- 指针始终为指针,他不可能改写为数组。下标形式访问指针,也是说明指针指向的是一个数组,编译器也将其解释为*(a+i)这样的形式。
- 在将数组声明为函数的参数时,可以将其看成一个指针,他也具有一个内存空间,也可以修改(数组名没有内存空间也不能修改)。
- 因此,定义函数参数时,指针和数组对边一起来说是一样的,函数内部其实都是一个指针。
在其他情况,定义和声明必须匹配。定义一个数组,那在其他文件对其声明也必须声明为数组。