指针与数组


前言

大家都知道,我们在使用指针和数组的时候,在使用方面会有许多相似的地方,甚至在某些方面我们就把这两个东西,当成一个东西来使用了,比如说:我们想通过指针来访问一个元素,我们既可以通过*解引用访问,也可以通过[ ]的方式来访问,那我们是不是可以说明数组和指针是一个东西呢?
答案是:当然不是;就比如说,欧美人和亚洲人都有一个头,两只眼,两只手,但是你能说这两种人是完全一样的吗?😁😁😁
当然不可能;
指针和数组也是如此;
接下来,我们进入正题:
讨论一下指针和数组的区别:

一、指针

既然我们想要了解指针和数组之间的区别,那我们是不是应该先了解一下指针和数组一些相关的知识?

1、什么是指针?

先举个简单的栗子;
假设我现在是某公司的包工头;我现在呢?承包了某学校的宿舍建设,由于是刚建设的,还没来得及给宿舍上门牌号;有一天呢,你饿了,你准备点一份外卖,于是你卡卡一通乱按,定了个外卖,当外卖小哥问你地址的时候,你说我在某某学校的刚建设地宿舍地一个房间里,你来找我吧!由于没有门牌号你也不好想外卖小哥详细解释,只能大概这么说;(假设你不能下楼去接外卖,也不能在窗户哟呵)外卖小哥一听你给的烂地址,懵逼了啊!但是为了好评,他只能一间间的找,最终他在五楼地一个房间找到了你;
此时时间也过了20min,你也块饿死了;于是你下定决心一定要快点把这个门牌号装好;😭😭😭

回归正题,我们的内存就相当于这里的在建设的宿舍楼,里面的小房间就相当于一个个小小的字节空间;其中的门牌号也就是我们的地址,也就是我们的指针;故地址就是指针,指针就是地址;用来存放地址的,叫指针变量;
在这里插入图片描述

2、为什么会有指针?

通过上面的栗子我们可以知道,假设没有指针的话,我们cpu想要读取一个元素的话,就只能挨个挨个的寻找,效率极低不说,还容易出错;
其次是:
在cpu和内存之间有一些线(物理线)来连接的;
在这里插入图片描述
当cpu想访问某个变量的时候,他就通过地址总线对内存说:“内存啊,你帮我找一个地址为…某某的数据”,于是内存卡卡几下就精确找到了这个元素;找到了,然后通过数据总线返回给cpu;(在某些机器上地址总线和数据总线共用同一批线);
我们知道在32位平台下,有32个地址线;每个地址线都指正传输正负电,也就是0 1,也就是说32根地址线能表示的地址就是:
0000 0000 0000 0000 0000 0000 0000 0000;

1111 11111 1111 1111 1111 11111 1111 1111;
那么好,那么是不是一个地址代表着一个字节的空间;
那么32位机器下:我们能编址的空间最大:2^32*1字节=4GB;
同时也说明了,这些指针最多只占4个字节,因此我们给32位平台下的指针分配4个字节的空间就行了;

总结:

指针变量是用来存放地址的,地址是唯一标示一个内存单元的。
指针的大小在32位平台是4个字节,在64位平台是8个字节

3、指针和指针类型

那么好,既然我们了解了指针的“前世今生”,那我们是不是就得来使用一下它呢?在使用之前我们是不是又得仔细了解一下它的类型呢?

char*//指向char类型的指针
short*//指向short类型的指针
int*//指向int类型的指针
long*//指向long类型的指针
long long*//指向long long类型的指针
float*//指向float类型的指针
double*//指向double类型的指针
//还有结构体指针等等;

在这里插入图片描述
我们如果画图来示意:
在这里插入图片描述
&表示取出a的地址;
在定义的时候*表示这个变量是个指针;
不在定义的时候 * 表示解引用;
也就代表着,我们可以通过指针去把你给改掉;
在这里插入图片描述

4、指针+ -整数

我们先来看一段代码:
我们可以想一想这段代码的结果:

int main()
{
	char* p1 = NULL;
	short* p2 = NULL;
	int* p3 = NULL;
	long long* p4 = NULL;
	float* p5 = NULL;
	printf("%p\n",p1);
	printf("%p\n\n", p1+1);

	printf("%p\n", p2);
	printf("%p\n\n", p2 + 1);

	printf("%p\n", p3);
	printf("%p\n\n", p3 + 1);

	printf("%p\n", p4);
	printf("%p\n\n", p4 + 1);
	
	printf("%p\n", p5);
	printf("%p\n", p5 + 1);
}

运行截图:
在这里插入图片描述
我们可以看到指针的+1其实并不是我们简单的认为算数加1而已,指针中的1其实有着深刻的含义,细心的同学可能会发现,指针每次加的1似乎是加的该指针所指向类型的大小;你看char指向char类型,char类型占一个字节那我们的p+1其实就是加的1个字节;同理一个int指向int类型,int类型占4个字节,我们的p+1,其实就是加的4个字节;后面依次类推;
那我们再来看看这个呗:

double**p=NULL;
printf("%p\n",p);
printf("%p\n",p+1);

打印结果是:00000000
---------------:00000004
double**指向的类型为double*;
而double*占4个字节;
故p+1;其实是跳过的4个空间的地址;
同理

double***************p=NULL;
printf("%p\n",p);
printf("%p\n",p+1);

也是一样的结果;
那么为什么会出现这样的情况呢?(或者说为什么要这样设计呢?)
我们都知道一个int型变量占4个字节,那么按理说他就应该有4个地址,确实是这样的,但是我么们&取地址的时候,我们取得地址是这4个地址中最低的地址,(对于任何类型都是这样);但是如果我们到时候就拿着这一个地址去访问这个元素的话,就会造成访问不精确,因此这时候,我们指向的类型的作用不就体现了,我们只需要知道这个指向的类型占多少个字节,又知道这几个字节中的最低的地址,那我们直接往后访问相应的步长不就行了,不就相当于我们知道了,这个变量的所有地址,它所有地址都知道了,我们不就能对其进行精确的更改了;
在这里插入图片描述

总结:

指针的类型决定了,对指针解引用的时候有多大的权限(能操作几个字节)。
比如: char* 的指针解引用就只能访问一个字节,而 int* 的指针的解引用就能访问四个字节。

5、指针的规范

既然我们有了指针这么方便的东西;我们就应该规范的去使用它,以免造成不必要的bug;
1、定义了指针一定要初始化,如果不明确它的指向,用NULL初始化;
2、明确指针指向类型,不要乱标指针类型;
3、对于不属于自己的空间,尽管我还保留着这块空间的地址,我们应该及时置空;

二、数组

1、什么是数组?

数组可以说是目前为止讲到的第一个真正意义上存储数据的结构。虽然前面学习的变量也能存储数据,但变量所能存储的数据很有限。
那么到底什么是数组呢?顾名思义数组就是很多数的组合!那么这些数有没有什么要求呢,是不是不管什么数组合在一起都是数组呢?同一数组中存储的数必须满足以下两个条件:
1、这些数的类型必须相同。
2、这些数在内存中必须是连续存储的。
换句话说,数组是在内存中连续存储的具有相同类型的一组数据的集合;
比如说:我想要统计一个班里所有学生的数学成绩:这时候如果在定义多个单独的变量来分别记录每个学生的成绩就显得不是那么合适了;
这时候数组就比较合适;
有了数组的概念,我们以后对于排序,查找等操作就方便多了;

2、如何使用数组?

如图所示是数组的正确定义:
在这里插入图片描述
注意:
在定义的时候[ ]里面的必须是常量,不能是变量就算是const修饰的也不行;
当然我们只能在数组初始化的时候赋值,不能像这样赋值:
在这里插入图片描述
当然对于数组的初始化:
我们经常可以见到一下几个版本;
1、不完全初始化:
在这里插入图片描述
对于未被初始化的部分,编译器会帮我们自动初始化为0;
在这里插入图片描述
2、初始化时不指明数组大小的:
在这里插入图片描述
像这种情况,编译器会自动帮我们计算大小;
3、定义了但却未初始化:
在这里插入图片描述
里面放的都是随机值:
在这里插入图片描述
我们要想对其初始化,只能一个一个的对其改变;
其中我们用[ ]对数组进行访问;
对数组遍历:

int main()
{
	int arr[10];
	for (int i = 0; i < 10; i++)
	{
		arr[i] = i;
	}
	return 0;
}

在这里插入图片描述
其中这里的[ ]里面可以是变量;(这里不是定义,是访问,所开的空间大小已经固定了)
当然[ ]里面是数组的下标,C语言规定,数组下标从0开始,依次递增;
其中数组名表示首元素地址;

3、数组的内存分布

我们先来看看这段代码:在这里插入图片描述
我们可以发现a—>c的地址是依次减小的;
这其实也是能理解的;
毕竟局部变量都是在栈上开辟的,而栈是先进后出,栈底是高地址,栈顶是低地址:在这里插入图片描述
a先开嘛,故先入栈,因此a是在高地址嘛;
后面依次;
那我们再来看看数组呢?|

int main()
{
	int arr[10] = { 9,8,7,6,5,4,3,2,1,0 };
	for (int i = 0; i < 10; i++)
	{
		printf("&arr[%d]:%p\n",i,&arr[i]);
	}

	return 0;
}

在这里插入图片描述
着是不是和我们刚才所讲的理论有所区别:
按照我们刚才的理论:从下标为0->9地址应该是依次减小的,好家伙现在去截然相反;其实啊,我们在这样想的时候,就潜移默化的把数组当作一个个单独的元素了,而实际上呢数组本身就是一个类型,编译器在为数组开辟空间的时候,(以此题数组为例)编译器会把int arr[10],当作一个占40个字节的空间的类型来看待,所以编译器会一次性在栈上开辟40个字节的空间;
空间开辟好过后,把低地址的空间作为下标为0的元素的空间,后面依次类推;因此我们看到的数组的地址是依次增大的;

4、数组与指针的区别

我们在平常中对于指针和数组的使用方式似乎很相似,以至于我们在有时候把指针当做数组,数组当作指针,实际上呢这是两个完全不同的概念;
我们都知道数组名是首元素的地址,注意是首元素的地址,不是某个单独的变量的地址;这个首元素可以是变量,可以是指针,可以是数组等等;
假设int arr[10]={9,8,7,6,5,4,3,2,1,0};
arr是数组名也是首元素地址;
也就相当于arr也就是一串地址;
在这里插入图片描述

那么当然可以用int*型指针变量来保存了;
在这里插入图片描述
代码编译的时候也没有警告,程序也能跑过去;
同时我们还能利用p对数组进行操作:
在这里插入图片描述
既然p里面放的首元素的地址,那么也就相当于,我们对于p也可以进行[ ]对数组进行访问;当然也可以 *(p+i)对数组进行访问,二者是等价的;
但是呢,我们上文说了耶,arr相当于数值首元素地址,那我们用arr对数组进行访问的时候比利用p对数组进行访问效率更高!!!
arr不是数组首元素地址嘛,是地址唉,直接就是地址唉!!cpu到时候直接通过这个地址就找到了这个数组;而p是变量唉,我们现在又想使用p里面的内容,我们是不是得先找到p然后再通过p里面的内容来找到这个数组;
我们现在再get两个点:
1、&arr表示的是整个数组的指针;
2、sizeof(arr)表示的是算的整个数组的大小,此时的arr代表整个数组;
目前只有这两个表示的是整个数组;
在这里插入图片描述
这段代码的结果现在能理解了吧。
你把数组当作一个类型来看;
它的低地址是不是就和它首元素的地址一样嘛;
接下来我们来分别对其加1看看:
在这里插入图片描述

这时候就很明显了,我们上文才说了,指针加1实际上是跳过所指向类型的大小;
arr和arr+1我们能理解;
接下来我们来理解理解&arr和&arr+1;
在这里插入图片描述
这也就从侧面说明了数组是一个类型,与指针是不同的类型;

  • 26
    点赞
  • 27
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 34
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 34
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

南猿北者

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值