【C指针】初识指针,轻松了解指针

⭐博客主页:️CS semi主页
⭐欢迎关注:点赞收藏+留言
⭐系列专栏:C语言初阶
⭐代码仓库:C Advanced
家人们更新不易,你们的点赞和关注对我而言十分重要,友友们麻烦多多点赞+关注,你们的支持是我创作最大的动力,欢迎友友们私信提问,家人们不要忘记点赞收藏+关注哦!!!


前言

刚开始大家对指针可能比较陌生,不像前面的函数数组那么好理解,可是指针是C语言的灵魂,指针让C语言常年霸占编译软件世界前三的位置,经久不衰,所以,接下来我会带大家认识一下指针,了解指针的基本原理和构造,相信大家学完这一篇博客,就会发现指针没有那么难,让大家对指针产生兴趣并充分运用指针。


一、指针是什么

两个要点:1.指针是内存中一个最小单元的编号,也就是地址。
2.平时大家所说的指针,通常是指针变量,是用来存放地址的变量。

1.内存

先理解一下内存:
顾名思义,内存就是电脑上的存储设备,大小一般是4G/8G/16G,在程序运行的过程中会加载到内存中,也会使用内存空间。
那先把我们的运行管理器拿出来看看(Ctrl+Alt+Delete):
在这里插入图片描述

那既然内存是存放程序的地方,是不是可以理解成一个个小抽屉去存放程序的,上图看看!
在这里插入图片描述
其实很好理解,一个内存单元相当于一个字节,而右边的16进制(也可其他进制)则是编号,相当于门牌号,加入你想找第三个内存单元,直接找它的编号(门牌号)即能找到,即寻址。
在这里插入图片描述
如上图所示,在不同的地方的叫法不一样,但是,其本质是一样的,编号就是地址,地址就是指针,他们是一回事。

2.指针变量

要理解指针变量,我们需要先理解一下取地址取的是哪个地址,取的是第一个字节,也就是较小的地址。
在这里插入图片描述
那接下来就有个疑惑了,这地址取出来有什么用,地址取出来不就只是一个简单的值吗?
在这里插入图片描述

因为pa是存放一个指针的,pa是根据取地址的那个变量的类型决定的。指针变量是一种变量,是专门用来存放地址的。

3.知识点(解引用)

在这里插入图片描述

#include<stdio.h>
int main() {
	int a = 10;
	int* pa = &a;
	//printf("%p\n", pa);
	//printf("%p\n", &a);
	*pa = 20; //*为解引用操作
	printf("%d\n",a);
	return 0;
}

4.编址是如何产生的

电脑是二进制运算的,以32位电脑举例,假设有32根地址线,那么假设每根地址线在寻址的时候产生高电平(1)和低电平(0),那么32根地址线产生的地址是:
在这里插入图片描述
每一个二进制序列表示一个内存单元的编号,但这些编号是真实存在吗?答案是NO!它仅仅是计算机内部通过物理的电信号产生这个二进制序列,依据这个二进制序列去找相对应的内容。就好比人家给你在一张纸,上面写了他家的门牌号,你拿着纸吭哧吭哧去找,找到了以后这张纸丢了,你也不记得他家是那个门牌号了,因为没有经过保存,仅仅是一个信号而已。

解决完编号的问题,我们再来看一下内存的问题,32位机器是会产生2的32次方个地址,管理2的32次方个内存单元,也就是管理2的32次方个字节,先罗列一下换算表:
在这里插入图片描述

所以32根地址线存放了4个G的内存空间。
结论:在32位的机器上,地址是32个0或者1组成二进制序列,那地址就得用4个字节的空间来存储,所以一个指针变量的大小就应该是4个字节。
那如果是在64位机器上,如果有64根地址线,那一个指针变量的大小是8个字节,才能存放一个地址。


二、指针和指针类型

1.指针存储空间

在讲述指针大小之前,大家需要知道的知识点是int占4个字节,char 占1个字节,short占2个字节,double占8个字节。而指针类型只取决于机器是几位的,理由如上编址是如何产生的。

指针类型有没有一种固定的格式呢,就比如说ptr* 这种一个类型可以用所有的指针呢?答案肯定是不行的,如我们所见,指针类型有多种多样,int* ,float* ,char*等等,所以指针类型是有多样的。

用一串代码来解释一下:

#include<stdio.h>
int main() {
	int a = 0x11223344;
	int* p = &a;
	*p = 0;
	return 0;
}

在这里插入图片描述
如图,int* pa存的是44 33 22 11,至于为什么是倒着放,这是因为在数据结构里内存的大小端,这取决于编译器,后续博客我会给大家讲解到的。
在这里插入图片描述
大家可能有疑惑,这int类型不是占4个字节吗,这不是正好对应了吗,那换成比它小的short或者char呢?还是一样的吗?答案是一样能够存放的下,接下来上一串代码:
在这里插入图片描述
发现p和pc的值是一模一样的,说明指针变量的大小只取决于是几位的机器的。
那我们重复上面p的操作,利用解引用操作将pc的值改变成零看看是一种什么情况吧!
在这里插入图片描述
发现只改变了第一个字节。

2.访问空间大小

先要明白一个定义:指针类型是有意义的
指针类型决定了指针进行解引用操作的时候一次性访问几个字节,前提是要在同一个位数的机子下进行,*号前面是什么类型,则访问几个字节,所以当我们想要访问多大的空间,只需要用不同的指针类型访问即可。

在这里插入图片描述

3.指针±整数

指针类型决定了指针的步长(指针+1跳过几个字节)。
即:char指针+1,跳过1个字节;int指针+1,跳过4个字节,是不是很方便,只要你想访问几个几个字节,只需要用不同的指针类型进行访问,再解引用进行更换或替代值。
在这里插入图片描述

指针的不同类型,其实就是提供了不同的视角去观看和访问内存。
char* - 一次访问1个字节,+1跳过1个字节;
int* - 一次访问4个字节,+1跳过4个字节;
在这里插入图片描述
所以,指针的类型是什么,我就在访问内存空间的时候访问的是几个字节,这是完全取决于指针的类型的,与数值原本的类型无关。
举例:
在这里插入图片描述

4.指针的解引用操作

指针的类型决定了对指针解引用的时候有多大的权限(能操作几个字节)。
解引用以后存放的值是有限的,不是无限的,所以不能存放超过其内存大小的值。


三、野指针

(一)野指针的定义和成因

概念:是指指针指向的位置是不可知的(随机的、不正确的、没有明确限制的)。就相当于路边一只野狗,没有主人拴绳,你不知道它会怎么样,到那边走几步,到这边走几步,有可能还要咬人,这是不确定的,而野指针也是这样,是非常危险的,非常随机的。

1.指针未初始化

在这里插入图片描述
如上图就是一个野指针,假如当我喝醉后在大马路上随机进入一家,说,这个房间是我的,那这家主人肯定要把我赶出去,因为我没有在这个地方申请一个空间,这个地方本就不属于我,是随机去找的一个地方,是很危险的。
结论:指针未进行初始化就是个野指针。下图就是没有问题的指针使用。
在这里插入图片描述

2.指针的越界访问

顾名思义,越过所规定的边界去访问,就好比我越过了同桌在桌子中间画的线,那肯定要被他说的,但要是他没看见那不会,但他看见了,肯定会打我一下。这是非常危险的,如下图:
在这里插入图片描述
越界访问后,最后一个数是随机数。即:这个指针指向的位置不是合理已经划分的范围里,那就是一个野指针,指向的是一个随机的地址,那么值也是那个随机的地址的值。

3.指针指向的空间释放了

先来张图片再解释一下:
在这里插入图片描述
这个程序看似很正常,但是,大家想一想,是不是函数最后一个花括号结束以后函数的生命周期就消亡了,但p仍然会去找到a的地址,但实际上这是一个野指针,因为a已经走向生命周期的尽头了,已经消亡了。就好比我去住一个如花酒店的房间202,就开了一天的房间,我跟张三说,明天你来这个202房间住,我给你开了房住,张三脑海里存储了如花酒店202房间这个位置,第二天早上我把房间一退,回家去了,中午张三屁颠屁颠的来这个房间的这个位置,发现,人呢!?张三大喊我要住这个房间!那保安直接把他轰走。

在这里插入图片描述

(二)野指针的规避

1.指针进行初始化

int main() {
	int a = 10;
	int* pa = &a; //指针的初始化

	int* p = NULL; //空指针,相当于0 - (void*)0,是专门用来初始化指针的
	
	return 0;
}

NULL - 空指针,是专门用来初始化指针的,就相当于先把那条野狗先栓到树上,不让它乱跑,但也不一定意味着就绝对安全了,因为如果你去靠近这棵树并进行挑衅,那还是很危险的。如果定义了一个指针为空指针,但是去解引用进行改变值,那程序就会崩掉。因为p是指向的是一个空的地方。
在这里插入图片描述

2.小心指针越界

当我们在运用指针指向的数组空间的时候,注意不要越界即可。

3.指针指向空间释放,及时置NULL

如果一个指针指向的空间释放掉了,那我们需要把它赋一个空指针,让这个指针变得安全。

4.避免返回局部变量的地址

出了局部变量指针就会销毁,因为局部变量是有周期性的。

5.指针使用前检查有效性

在指针使用之前要判断这个指针是不是能用的,只有指针能用代码才能跑起来。


四、指针运算

1.指针±整数

在这里插入图片描述

指针vp每往后移动一位就赋一个位为0。

代码如下:

#define N 5
int main() {
	float values[N];
	float* vp;
	for (vp = &values[0]; vp < &values[N];) {
		*vp++ = 0;
		//*vp=0;
		//vp++;
	}
	return 0;
}

2.指针-指针

运算条件:两个指着要指向同一块空间,且指针的类型是一样的。
在这里插入图片描述
该程序结果是9,不是10,因为指针减指针所算出来的结果为两个指针之间的元素个数,利用指针的箭头发现,只有9个元素。如下图被涂成紫色的方块个数。
在这里插入图片描述

来个例子关于统计字符串个数,如下图所示:指针-指针得到的是两个指针之间的元素个数。
在这里插入图片描述

3.指针的关系运算

简单来讲,就是比较指针之间的大小,就和1.指针±整数里的代码一样,指针之间在进行相互的比较,再在循环里进行比较。

4.标准

标准规定:
允许指向数组元素的指针与指向数组最后一个元素后面的那个内存位置的指针进行比较,但是不允许与指向第一个元素之前的那个内存位置进行比较。

如图:
在这里插入图片描述
解释越界但不非法:没有进行访问,只是超出范围,就相当于你不可以抢银行,但是允许在银行门口看看,转转,没抢银行,所以未非法访问,但确确实实是越界了。


五、指针和数组

1.指针和数组是不同的对象

指针是一种变量,存放地址的,大小是4/8个字节。
数组是一组相同类型元素的集合,是可以放多个元素的,大小是取决于元素个数和元素的类型的。

2.数组和指针的区别

数组的数组名是数组首元素的地址,地址是可以放在指针变量中。可以通过指针访问数组。
如下图:利用指针对数组内部元素赋值并进行打印。

在这里插入图片描述
优化:int* p=arr; p与arr首地址等价,所以arr[j]=*(p+j)= *(arr+j)= *(j+arr)=j[arr]; 原因很简单:[]是个操作符,而j和arr只是[]这个操作符的操作数而已。
在这里插入图片描述
在这里插入图片描述


六、二级指针

1.概念

二级指针就是取一级指针的地址,一级指针就是取原来数字的地址。
在这里插入图片描述

2.解引用操作

在这里插入图片描述
下图即为二级指针解引用后的值,与a的值一样。
在这里插入图片描述


七、指针数组

1.概念

指针数组是存放指针的数组,是一个完完全全的数组。

2.偏代码操作

举个例子,数组指针的操作:
在这里插入图片描述
在这里插入图片描述

3.常用代码

使用一维数组模拟一个二维数组。

int main() {
	//模拟一个三行四列数组
	int a[] = { 1,2,3,4 };
	int b[] = { 2,3,4,5 };
	int c[] = { 3,4,5,6 };
	int* arr[3] = { a,b,c };
	for (int i = 0; i < 3; i++) {
		int j = 0;
		for (j = 0; j < 4; j++) {
			printf("%d ", /**(arr[i]+j)*/arr[i][j]);
		}
		printf("\n");
	}
	return 0;
}

在这里插入图片描述
在这里插入图片描述

a,d,c表示的都是首元素的地址。
在这里插入图片描述


总结

大家从刚开始看到这里一定是对指针有了很深的理解,这篇博客从指针是什么往后讲到指针数组,总的来讲我们所说的指针就是指针变量,能够在内存中找一块空间,是很有趣的。


客官都看到这里了,来个三连支持一下吧!!!

评论 42
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

2022horse

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

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

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

打赏作者

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

抵扣说明:

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

余额充值