指针与数组之间的"恩恩怨怨"
最近在学习语法的时候有了不小的收获,对数组和指针这两个玩意有了一些新的认识,也发现自己以前的理解存在很大的问题,谨以此文梳理吾之所得
前言
指针就是指针,指针变量在64位系统下始终占8个byte,指针可以指向任何地方,但不是任何地方都可以通过指针变量访问的
数组就是数组,是相同类型变量组成的集合(就是数学中集合的意思),数组的大小由组成变量的类型和元素的个数决定,满足等式:数组大小=元素类型大小*元素个数
这二位是八竿子打不着,完全没有任何关系
为什么要强调二者没有任何关系呢?
因为他们经常“穿着相同的衣服”逗你玩,不信看下面代码
void Usart_Printf(u8 *str){
u8 data=0;
do{
// USART_SendData(USART2,str[data]);//发送一个字
USART2->DR = ( str[data]& (uint16_t)0x01FF);
while(USART_GetFlagStatus(USART2,USART_FLAG_TXE)==RESET);//等待单字发送完成
data++;
}
while(str[data]!=0); //判断数据是否发送完成
}
肯定会有人(以前的我)会认为str[data]为数组的一个元素,究竟是不是,看了下文你就明白了
1.数组(一维)
1.1数组的内存布局
int a[5];
定义了一个整形的数组a,其内存布局示意图如下:
![在这里插入图片描述](https://i-blog.csdnimg.cn/blog_migrate/43291172bf390fbc477f54d3e448f70f.png)
a作为右值时 1,代表数组首元素的首地址而非数组的首地址,a不能作为左值
a[0]、a[1]…这些是a的元素,我们可以通过其访问这些元素的内容,但并不是说他们是这些元素的名字(了解即可,不求甚解)
1.2 a、&a、&a[0]的区别
这三者的区别,可能有朋友会傻傻的分不清,前面我们说了,a作为右值时代表数组首元素的首地址而非数组的首地址(不理解没关系,下面用实例证明),借用陈老师的《C语言深度解剖》里的话“省政府和市政的区别----&a[0]和&a 的区别”,&a表示整个数组的首地址如同四川的省政府在成都,&a[0]表示数组首元素的首地址如同成都的市政府也在成都,二者虽然在数值上是相等的,但物理意义完全不相同,而a与&a[0]则是等价的
实例证明:
#include "string.h"
#include "stdio.h"
int main()
{
int a[5]={1,2,3,4,5};
printf("%d %d %d\n",&a[0],a,&a);
printf("%d %d %d",&a[0]+1,a+1,&a+1);
return 0;
}
运行截图:
&a[0],a,&a的值都为6422016(编译器分配的内存地址)看似没什么区别
但是:
&a[0]+1,a+1,&a+1的值分别为6422020、6422020、6422036,前两者的由6422016+sizeof(int)(首元素的首地址加上元素类型所占的空间)得来,可见二者没有任何区别,而&a+1则是由6422016+sizeof(int)*5(数组的首地址加上数组占的大小)得来
其实是通过物理意义来理解,“+1”就是在原有基础上移动一个单位,前二者表示元素的首地址其基础就是元素,后者表示的是数组的首地址其基础就是数组
相信看到这的朋友对数组已经有了相当的了解,接下来就来看看指针——“披着羊皮的狼”
2.指针
2.1指针的内存布局
int *p
定义指针p,其内存布局示意图如下:
如上图所示,我们把 p 称为指针变量,p 里存储的内存地址处的内存称为 p 所指向的内存,指针变量 p 里存储的任何数据都将被当作地址来处理,变量p本身的地址未知(根据编译器分配的不同而不确定)
*p前面的类型说明p所指向的内存存的数据类型
2.2 “*”好比门钥匙
如何理解 “ * ”?
我们已经知道指针变量p里面存的是且只能是地址,如果把这个地址比作是门牌号(某小区几单元几楼几号),那么 “ * ” 就好比是门钥匙,要知道门里有什么,那必须的要打开门吧,同理要知道地址对应的内存存了什么东西就必须要这把“钥匙” “ * ”
2.3 int * p = NULL 和 * p = NULL 有什么区别?
这是指针中比较经典的一个问题
首先说NULL在数值上和0是相等的,但和0的意义完全不同
在stdio.h中NULL的定义如下:
#if !defined(NULL) && defined(__NEEDS_NULL)
#ifdef __cplusplus
#define NULL 0
#else
#define NULL ((void *)0)
#endif
#endif
C++中被宏定义为0,C中被宏定义为空指针常量
int * p = NULL这句代码的意思是:定义一个指针变量 p,其指向的内存里面保存的是 int 类型的数据;在定义变量 p 的同时把 p 的值设置为0x00000000,而不是把*p 的值设置为 0x00000000
*p = NULL这句代码的意思是:给指针变量p所指向的内存赋值为0
2.4 p、&p、*p、p+1、*p+1、 *(p+1)、p[1]、&p[1]
- p为指针变量,存储所指向内存的地址,类似于数组首地址a
- ‘&’都不陌生,很明显这里是取地址符号,&p表示求取指针变量p本身的地址
- *p带上了“钥匙”对吧,那它的功能必定就是获取“房间”里的东西,没错 *p就是获取所指向地址内存所存储的数据类似于数组a[0]
- p+1表示在p的基础上移动一个“单位”,这里的“单位”就相当于数组中的下一个元素
- *p+1表示在 *p的基础上加上1,这里的1理解为阿拉伯数字1,e.g. 如果 *p=1,那么 *p+1=2,注意这里并不是理解为“1+1=2”,而是1的ascll码‘0x31’+1=‘0x32’,‘0x32’对应就为字符2
- *(p+1)很好理解,就是C语言运算符的优先级而已,不用说()的优先级当然高于 *
- p[1]等价于*(p+1),这玩意就是“披着羊皮的狼”不注意还真会弄成是数组,这玩意完全可以当成数组元素那样理解,但和数组没有半毛钱关系,e.g. char *p=“Exclusive is handsome.”,p[0]=‘E’,p[1]=‘x’…
- &p[1]就是取当前存储内容对应的地址
下面上代码帮助大家理解:
#include "string.h"
#include "stdio.h"
int main()
{
int *p="ExclusiveTP is handsome.";
printf("%d\n",sizeof(p));
printf("%d %d %d %d\n",p,&p,p+1,&p[1]);
printf("%c %c %c %c",*p,*(p+1),*p+1,p[1]);
return 0;
}
运行结果截图:
到这里我们现在可以明确分辨出前言部分中Usart_Printf(u8 *str)定义的是一个指针变量str,至于str[data]不过是“披着羊皮的狼”而已
3.指针数组与数组指针–傻傻的分不清
啥情况,指针数组?数组指针?不是说指针和数组没有半毛钱关系吗?^ - ^
是的,指针和数组没有半毛钱关系,但在某种情况下二者是可以结合的,就比如‘+’,‘-’都是算术运算符号,二者是独立存在的没有关系,但你总是会遇见加减混合运算吧,指针数组和数组指针就可以理解为混合体
3.1指针数组与数组指针的内存布局
指针数组:首先它是一个数组,数组的元素都是指针,数组占多少个字节由数组本身决
定,它是“储存指针的数组” 的简称
数组指针:首先它是一个指针,它指向一个数组,在 64 位系统下永远是占 8 个字节,
至于它指向的数组占多少字节,不知道,它是“指向数组的指针” 的简称
二者定义如下:
int *p1[5];
int (*p2)[5];
哪一个是指针数组?哪一个是数组指针?
这里需要明白一个符号之间的优先级问题,“ [] ” 的优先级比 “ * ” 要高,p1 先与 “ [] ” 结合,构成一个数组的定义,数组名为 p1, int *修饰的是数组的内容,即数组的每个元素,那现在我们清楚,这是一个数组,其包含 5 个指向 int 类型数据的指针,即指针数组
至于 p2 就更好理解了,在这里 “ () ” 的优先级比 “ [] ” 高, “ * ” 号和 p2 构成一个指针的定义,指针变量名为 p2, int 修饰的是数组的内容,即数组的每个元素,数组在这里并没有名字,是个匿名数组,那现在我们清楚 p2 是一个指针,它指向一个包含 5个 int 类型数据的数组,即数组指针
下面附上二者的内存布局图帮助理解
老道只能“抛砖引玉”,因为我实则是搞硬件的,对数组和指针的要求并不是很高^ - ^
右值为放在等号右边的值,左值即为等号左边的值 ↩︎