你好
目录
指针的定义
谈到指针很多人不会陌生,毕竟它是我们C语言的老朋友了。但是要问起指针到底是个什么东西,很多人恐怕就会一知半解、答非所问。那么指针到底是什么东西呢?
要想搞清指针是什么就必须先搞清为什么要创造指针。
在编程中,我们要定义一个变量,得知道我们要把它存储在什么地方,只有这样以后再用到这个变量的时候就不会找不到。那么问题就来了,我们怎么知道这个变量的位置在哪里呢?
#define _CRT_SECURE_NO_WARNINGS 1
#include<stdio.h>
int main()
{
int a = 16;
return 0;
}
例如这个例子,我们定义了一个整型变量,并且给它赋值为16,那么我们是如何把16存入a的内存里面的?
其实计算机对内存进行了划分,规定内存的最小单元是1 个字节,变量定义了几个字节,就给你分配几个字节的内存单元。譬如a是整型,所以占4个字节,那么内存就会给它分配4个字节的内存单元。看到这里也许有人估计会有疑问,我们只是给a分配了足够的内存空间,但是我们并没有记录这些内存空间的位置,那么下一次我还是找不到a的位置。
的确,上述的担忧是有道理的。比如我又定义了一个整型变量b,他也占用4个字节的空间,那么我怎么知道内存中哪4个字节是属于b的,哪4个字节是属于a的?看来,我们需要给每个内存单元贴上独一无二的标签才行。于是我们就有了内存编号的概念。
如下图所示我们用a的内存空间为例。可以看到我们的内存空间是有他们自己的编号的,像0X000000B43DEFF8E4,这就是一个编号,每个编号之间相差1,这里的1代表的是1 个字节,在VS2022的编辑环境下,采用的是16进制计数方式。
由此,通过给内存空间设置编号,每个内存空间就是独一无二的,那么我们只要能记住a所占用的内存空间对应的编号,就能找到a的内存空间。
嗯,看到这里相信你对变量的存储过程有了更深的理解,但是这些与我们讲的指针又有什么关系呢?嘿嘿,别急,这就给你介绍指针。
上文我们说了,编号很重要,有了它就能找到指定的内存空间。那么既然它如此重要,为什么我们不把它存储起来呢?于是,为了存储这个编号,我们就创造了指针。
这里补充几个概念:
1.内存的编号也叫内存地址(通过编号可以找到内存,就像知道内存的地址一样)。
2.指针存储编号,编号就是地址,因此指针存储地址。我们通常直接把指针和地址当成是一个东西,也就是“指针就是地址,地址就是指针”(大家搞清楚底层逻辑就行,在叫法上没严格的规定)3.口语中说的指针通常指的是指针变量。
指针的类型与定义一个指针
我们知道整型是int 浮点型是float 字符是char 此外还有double、long long......
那么我们联想到指针,指针是不是也有不同的类型呢?
这里可以看到,指针的定义方式是: type + * 。
其实: char* 类型的指针是为了存放 char 类型变量的地址。 short* 类型的指针是为了存放 short 类型变量的地址。 int* 类型的指针是为了存放 int 类型变量的地址
那么这里我有一个问题:不管是int*还是char*,他们不都是地址吗,不管你指向的是int还是char存储的不都是地址吗,只不过是地址不同而已,那为何还要大费周章设定不同的指针类型呢?
要解决上述问题,我们先来看看这个代码。
#include<stdio.h>
int main()
{
int a = 16;
char b = '1';
int* p1 = &a;
char* p2 = &b;
printf("%p\n",&a);
printf("%p\n",p1);
printf("%p\n", p1+1);
printf("%p\n", &b);
printf("%p\n", p2);
printf("%p\n", p2+1);
return 0;
}
请输出结果:
&a就是a的地址
P1是a的指针,指针就是地址,所以P1与&a一样
P1+1 是a所占的内存空间的下一个内存地址
&b是b的地址
P2是b的指针,指针就是地址,所以P1与&b一样
P2+1 是b所占的内存空间的下一个内存地址
根据结果我们发现,给指针设定不同的类型原因1是规范了指针向前走一步或向后走一步具体走几个字节。int is 4byte,char is 1 byte. it depends on type.
这里插一句,很多同学有一个误解,他们认为,p存储的是a的地址,a是int型的,那么就占4个字节的内存,一个字节的内存有一个地址,那么p应该存储4 个地址。注意啊,不是这样想的啊!a占4个字节的内存不假,但是这不代表着p就要存储4个地址。p只要存储a的第一个地址就行了,为什么?因为内存空间是一个一个挨在一块的,我有了头的地址,又知道总共占多少字节,那不就能找到每一个地址了吗?这里请大家要注意。
指针设定不同的类型原因2 是在解引用操作时有了规范
#include<stdio.h>
int main()
{
int n = 0x11223344;
char* pc = (char*)&n;
int* pi = &n;
*pc = 0; //重点在调试的过程中观察内存的变化。
*pi = 0; //重点在调试的过程中观察内存的变化。
return 0;
}
如图可知,不同的指针类型解引用操作是有区别的。
讲完了指针的类型,我们来讲讲如何定义一个指针。
int main()
{
int a=10;
int *p=&a;
return 0
}
首先指针是地址,所以你要用&,来将变量的地址赋给指针,此外我们还要明确指针的类型。满足这两点你就会定义指针了!
野指针的问题
在使用指针时很多人会遇到程序报错的情况,指针是一个无情杀手,一不小心你就会被它偷袭。在这里我就分析几种常见的问题,帮助大家避坑。
野指针的定义:野指针就是指向的内存是不可知的、非法的指针。
出现野指针的常见情形:
1.指针未初始化
#include <stdio.h>
int main()
{
int *p;//局部变量指针未初始化,默认为随机值
*p = 20;
return 0;
}
未初始化的指针指向的是任意空间,有些不属于你的空间你也要占用这是不合法的。就像是你拿着一把可以打开所有酒店房间的钥匙,然后你拿着钥匙随意进入别人的房间,你说,这好吗?--_--
2.指针越界访问
#include <stdio.h>
int main()
{
int arr[10] = {0};
int *p = arr;
int i = 0;
for(i=0; i<=11; i++)
{
//当指针指向的范围超出数组arr的范围时,p就是野指针
*(p++) = i;
}
return 0;
}
一个指针是有作用范围的,超出这个范围你还在使用那就是违法的。就像例子中的对指针进行解引用,在指针指向的内存中你咋玩都没事,但是一旦这个内存空间不属于你这个指针了,那你就不能在对人家解引用了。
3.空间释放后,指针未置空
#include<stdio.h>
int main()
{
int a=0;
int *p=&a;
free(p);
p=NULL;
return 0;
}
当你释放了指针所指向的空间时,意味着那片空间已经不再属于你了,但是你的指针仍然保存着那片空间的地址,这是违法的。就像你把酒店的房间退了但是房卡你还留着,你说,这样好吗?--_--
4.指针所指的对象已经消亡
指针指向某个对象之后,当这个对象的生命周期已经结束,对象已经消亡后,仍使用指针访问该对象,将出现运行时错误。
int* retAddr()
{
intnum=10;
return#
}
intmain()
{
int* p=NULL;
p=retAddr();
这是函数的栈地址返回问题,在这里不做过多的介绍。大家只要记得函数一出作用域就会销毁,虽然地址返回了,但是地址中的数据早就被销毁了,这也是违法的。
讲了这么多,大家是否对小小指针刮目相待了?没想到这小子外表平平无奇,实则心狠手辣。那么我们又该怎样才能避免受到伤害呢?
1. 指针初始化
2. 小心指针越界
3. 指针指向空间释放,及时置NULL
4. 避免返回局部变量的地址
5. 指针使用之前检查有效性
其实作者本人在一开始使用指针时也吃了好多亏,但是那些血与泪的教训反而让我对指针的理解逐渐加深,bug并不可怕,当你以积极的态度面对它时,它就会成为你成长的养料,让你不断变强!
结语
好了以上就是本次博客的全部内容,大家要想掌握的话一定要多敲代码哦!
都看到这了,不给个赞吗?
这份博客👍如果对你有帮助,给博主一个免费的点赞以示鼓励欢迎各位🔎点赞👍评论收藏⭐️,谢谢!!!
如果有什么疑问或不同的见解,欢迎评论区留言欧👀。
好了我是Happysky,编程之路你我一起探索,让我们下期JAN。