概述
4.3 指针和数组之间的恩恩怨怨
指针与数组的区别:
数组是数组,指针是指针,根本就是两个完全不一样的东西。当然要是在宏观的内存角度看,那一段相同类型的连续空间,可以说的上是数组。 但是你可以尝试下,定义一个指针,在其他地方把他声明成数组,看看编译器会不会把两者混为一谈,反过来也不会。
但是为什么我们会经常弄混呢?第一,我们常常利用指针的方式去访问数组。第二,数组作为函数参数的时候,编译器会把它退化成为指针,因为函数的参数是拷贝,如果是一个很大的数组,拷贝是很浪费内存的,所以数组会被退化成指针( 这里一定要理解好,退化的是数组成员的类型指针,不一定是数组指针的哈)。
4.3.1 以指针的形式访问和以下标的形式访问
我们先来详细讨论他们之间的一些特点:例如,函数内部有如下定义:
(a)char * p = "abcdef ";
(b)char a[ ] = "123456";
1.以指针的形式访问指针 && 以下标的形式访问指针
(a)定义了一个指针变量p, p本身在栈上占4个字节,p里存储的是一块内存的首地址。这块内存在静态区,其空间大小为7字节,这块内存也没有名字,对这块内存的访问完全是匿名访问。如果现在读取字符'e’我们有两种方式:
(1) 以指针的形式: *(p+4)。先取出p里存储地址值,假设为0x0000ff00;再加上4个字符的偏移量,得到新的地址0x0000ff04;然后取出0x0000ff04地址上的内容;
(2)以下标的形式:p[4] 。 编译器总是把以下标的形式的操作解析为以指针的形式的操作。 道理:同上
2.以指针的形式访问数组 && 以下标的形式访问数组
(b)定义了一个数组a,a拥有7个char类型的元素,其空间大小为7。数组a本身在栈上面。对a的元素的访问必须先根据数组的名字a找到数组元素的首地址,然后根据偏移量找到相应的值。如果现需读取字符'5',我们有两种方式:;
(1)以指针的形式:*(a+4)。a这个时候代表的是数组首元素的首地址,假设为0x0000ff00; 再加上4个字符的偏移量,得到新的地址0x0000ff04;然后取出0x0000ff04地址上的内容;
(2) 以下标的形式: a[4] 。编译器总是以下标的形式的操作解析为以指针的形式操作。道理:同上啦!
【注意:上面所说的偏移量4代表的是4个元素,而不是4个字节;不过这里刚好是char类型的数据,1字符的大小就为1字节】
4.3.2 a和&a的区别(a为数组首元素地址,&a为数组的首地址)
通过上面的分析,我们再来看看这个例子:
- mian()
- {
- int a[5]={1,2,3,4,5};
- int *ptr=(int*)(&a+1);
- printf("%d,%d",*(a+1),*(ptr-1));
- }
- mian()
打印出来的值为多少?这里主要是考查关于指针加减操作的理解。
&a+1 :取数组 a 的首地址,该地址的值加上sizeof(a)的值,即:&a+5*sizeof(int),也就是下一个数组的首地址【显然当前指针已经越过了数组的界限】。
(int*)(&a+1) :则是把上一步计算出来的地址,强制转换为int *类型,赋值给ptr ;
*(a+1) :a和&a的值是一样的,但是意思不一样;
【a:是数组首元素地址,也就是a[0]的首地址(也即 a = a[0]) ; a+1 是数组下一元素的首地址,即a[1]的首地址 即:输出2;
&a:是数组的首地址; &a+1:是下一个数组的首地址(超过了第一个数组) 】
4.3.3 指针和数组的定义与声明
1 定义为数组,声明为指针
在文件1中,有定义:char a[] = "ABCDEFG";在文件2中,有声明:extern char *a;
首先要说的是,这个是错误的!
对于大多数编译器来说,在文件1中,a占7个byte;在文件2中,会把a当做一个指针来处理,占4个byte。而且,在这4个byte中,分别存储的是A,B,C,D的ASCII码值。由这四个ASCII码值组成的地址并不是我们所想要的数组的首元素的地址。而真正的数组a的首元素的首地址在文件一中定义数组的时候被编译器保存到了某个不知道的地方。
2 定义为指针,声明为数组(这个是错误的!)
在文件1中,有定义:char *p = "abcdefg";在文件2中,有声明:extern char p[];
如上面的分析一样,在文件1中,指针p中存储的是一块静态区域的地址,假设这块地址为0x00FF0000。而在文件2中,把p当做数组来访问,按照char类型取出p[0],p[1],p[2],p[3]分别为0x00,0xFF,0x00,0x00,并不是我们所想要的a,b,c,d。而且如果更改了p[0]等等,那么本来静态区域的地址也会丢失。
4.3.4 指针与数组的对比
指针和数组的特性总结:
区别要点 | 指针 | 数组 |
存储的内容 | 保存数据的地址,任何存入指针变量p的数据都会被当做地址来处理。p本身的地址由编译器另外存储,存储在哪,我们并不知道 | 保存数据,数组名a代表数组首元素的首地址没,而不是数组的首地址。&a才是整个数组的首地址。a本身的地址由编译器另外存储,存储在哪里,我们并不知道。 |
访问数据的方式 |
间接访问数据,首先取得指针变量p的内容,把它作为地址,然后从这个地址读/写数据。
指针以指针的形式访问*(p+i);也以下标的形式访问p[i],但其本质都是先取p内容然后加上i*sizeof(类型)字节作为数据的真正地址
| 直接访问数据,数组名a是整个数组的名字,数组内每个元素并没有名字。只能通过“具名+匿名”的方式来访问其某个元素,不能把数组当一个整体来进行读/写操作。数组能以指针的形式访问*(a+i);也能以下标的形式访问a[i],但其本质都是a所代表的数组首元素的首地址加上i*sizeof(类型)字节作为数据的真正地址 |
常用的场合 | 通常用于动态数据结构 | 通常用于存储固定数目且数据类型相同的元素 |
分配删除 | 相关的函数为malloc 和free | 隐式分配和删除 |
变量含义 | 通常指向匿名数据(当然也可指向具名数据) | 自身即为数组名 |
最后
以上就是虚幻果汁为你收集整理的C语言深度解剖——读书笔记-8、指针和数组的全部内容,希望文章能够帮你解决C语言深度解剖——读书笔记-8、指针和数组所遇到的程序开发问题。
如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。
发表评论 取消回复