指针与数组间的“恩恩怨怨”

2024-02-10 06:30
文章标签 数组 指针 恩恩怨怨

本文主要是介绍指针与数组间的“恩恩怨怨”,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

指针与数组之间的"恩恩怨怨"

  • 前言
  • 1.数组(一维)
    • 1.1数组的内存布局
    • 1.2 a、&a、&a[0]的区别
  • 2.指针
    • 2.1指针的内存布局
    • 2.2 “*”好比门钥匙
    • 2.3 int * p = NULL 和 * p = NULL 有什么区别?
    • 2.4 p、&p、*p、p+1、*p+1、 *(p+1)、p[1]、&p[1]
  • 3.指针数组与数组指针--傻傻的分不清
    • 3.1指针数组与数组指针的内存布局

最近在学习语法的时候有了不小的收获,对数组和指针这两个玩意有了一些新的认识,也发现自己以前的理解存在很大的问题,谨以此文梳理吾之所得

前言

指针就是指针,指针变量在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,其内存布局示意图如下:


在这里插入图片描述

首先编译器会根据定义数组的类型与元素个数划分出一块连续内存,再与a关联起来

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 类型数据的数组,即数组指针

下面附上二者的内存布局图帮助理解

在这里插入图片描述
老道只能“抛砖引玉”,因为我实则是搞硬件的,对数组和指针的要求并不是很高^ - ^

加油!拼命干才会百分百成功,下一篇再见!
  1. 右值为放在等号右边的值,左值即为等号左边的值 ↩︎

这篇关于指针与数组间的“恩恩怨怨”的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/696373

相关文章

C++原地删除有序数组重复项的N种方法

《C++原地删除有序数组重复项的N种方法》给定一个排序数组,你需要在原地删除重复出现的元素,使得每个元素只出现一次,返回移除后数组的新长度,不要使用额外的数组空间,你必须在原地修改输入数组并在使用O(... 目录一、问题二、问题分析三、算法实现四、问题变体:最多保留两次五、分析和代码实现5.1、问题分析5.

Java中数组转换为列表的两种实现方式(超简单)

《Java中数组转换为列表的两种实现方式(超简单)》本文介绍了在Java中将数组转换为列表的两种常见方法使用Arrays.asList和Java8的StreamAPI,Arrays.asList方法简... 目录1. 使用Java Collections框架(Arrays.asList)1.1 示例代码1.

解决java.lang.NullPointerException问题(空指针异常)

《解决java.lang.NullPointerException问题(空指针异常)》本文详细介绍了Java中的NullPointerException异常及其常见原因,包括对象引用为null、数组元... 目录Java.lang.NullPointerException(空指针异常)NullPointer

C++一个数组赋值给另一个数组方式

《C++一个数组赋值给另一个数组方式》文章介绍了三种在C++中将一个数组赋值给另一个数组的方法:使用循环逐个元素赋值、使用标准库函数std::copy或std::memcpy以及使用标准库容器,每种方... 目录C++一个数组赋值给另一个数组循环遍历赋值使用标准库中的函数 std::copy 或 std::

C++初始化数组的几种常见方法(简单易懂)

《C++初始化数组的几种常见方法(简单易懂)》本文介绍了C++中数组的初始化方法,包括一维数组和二维数组的初始化,以及用new动态初始化数组,在C++11及以上版本中,还提供了使用std::array... 目录1、初始化一维数组1.1、使用列表初始化(推荐方式)1.2、初始化部分列表1.3、使用std::

C++ Primer 多维数组的使用

《C++Primer多维数组的使用》本文主要介绍了多维数组在C++语言中的定义、初始化、下标引用以及使用范围for语句处理多维数组的方法,具有一定的参考价值,感兴趣的可以了解一下... 目录多维数组多维数组的初始化多维数组的下标引用使用范围for语句处理多维数组指针和多维数组多维数组严格来说,C++语言没

Java 字符数组转字符串的常用方法

《Java字符数组转字符串的常用方法》文章总结了在Java中将字符数组转换为字符串的几种常用方法,包括使用String构造函数、String.valueOf()方法、StringBuilder以及A... 目录1. 使用String构造函数1.1 基本转换方法1.2 注意事项2. 使用String.valu

JAVA中整型数组、字符串数组、整型数和字符串 的创建与转换的方法

《JAVA中整型数组、字符串数组、整型数和字符串的创建与转换的方法》本文介绍了Java中字符串、字符数组和整型数组的创建方法,以及它们之间的转换方法,还详细讲解了字符串中的一些常用方法,如index... 目录一、字符串、字符数组和整型数组的创建1、字符串的创建方法1.1 通过引用字符数组来创建字符串1.2

vue如何监听对象或者数组某个属性的变化详解

《vue如何监听对象或者数组某个属性的变化详解》这篇文章主要给大家介绍了关于vue如何监听对象或者数组某个属性的变化,在Vue.js中可以通过watch监听属性变化并动态修改其他属性的值,watch通... 目录前言用watch监听深度监听使用计算属性watch和计算属性的区别在vue 3中使用watchE

hdu2241(二分+合并数组)

题意:判断是否存在a+b+c = x,a,b,c分别属于集合A,B,C 如果用暴力会超时,所以这里用到了数组合并,将b,c数组合并成d,d数组存的是b,c数组元素的和,然后对d数组进行二分就可以了 代码如下(附注释): #include<iostream>#include<algorithm>#include<cstring>#include<stack>#include<que