指针详解+剖析

一.指针是什么呢?

 指针是什么?
 指针理解:
 1.指针是内存单元的一个编号,也就是地址
 2.平时叫的指针通常是指针变量,是用来存放地址的变量。

总结:指针就是地址,口语中的指针通常是指针变量。
那么我们就可以这样理解内存
在这里插入图片描述

指针变量:
我们可以通过&取地址操作符取出变量的内存地址,然后把这个地址呢就存放在一个变量中,这个变量就是指针变量。

#include <stdio.h>
int main()
{
	int a=20;//在内存中开辟一个空间来存放变量a
	&a;//&取地址操作符可以把a的地址给取出来;
	int* p = &a;//a变量占用4个字节的空间,这里将a的4个字节的第一个字节地址存放在
	//pz中,p就是一个指针变量
	return 0;
}

总结:
指针变量,用来存放地址的变量。
那问题来了:
1.一个地址单元到底有多大呢?(1个字节)
2.地址是如何编址的呢?

经过仔细的计算和权衡我们发现一个字节给一个对应的地址是比较合适的。
对于32位的机器,假设有32根地址线,那么假设每根地址线在寻址的时候产生高电平(高电压)和低电平(低电压)就是(1或者0);
那么32根地址线产生的地址就会是:
在这里插入图片描述
这里就有2的32次方个地址
每个地址标识一个字节,那我们就可以给 (2^32Byte == 2^32/1024KB == 232/1024/1024MB==232/1024/1024/1024GB == 4GB) 4G的空闲进行编址。
同样,在64位机器上,如果给64跟地址线,那么能编址多大空间,可以自己算算。
这下我们可以理解

  • 在32位的机器上,地址是32个0或1组成的二进制序列,那么地址就得用4个字节的空间来存贮,所以一个指针变量的大小就是4个字节。
  • 那如果在64位机器上,就会有64个地址线,那一个指针变量的大小就是8个字节,才能存放一个地址。

总结

  • 指针是用来存放地址的,地址是唯一表示一块地址空间的
  • 指针的大小与指针类型无关,与机器平台有32位平台,指针大小是4个字节,平台有64为那么大小为8个字节

二.指针和指针有哪些类型?

指针有哪些类型呢?你知道嘛?
我们都知道,变量就有许多类型,整形,浮点型,长整形,字符类型,那指针有没有类型呢?
答案是肯定的。

int n = 10;
现在我们有一个变量n我们想把变量n的地址取出来该怎么弄呢?
需要用&(取地址操作符)存放在变量p中。
那是不是这样写呢?
p=&n;

答案是错误的,正确的写法应该是 int *p=&n;

指针变量类型:

char *p1=NULL;
int *p=NULL;
short *p=NULL;
long *p=NULL;
float *p=NULL;
double *p=NULL;

这里我们可以看到指针变量的定义是 type+这种形式并且前面的type就决定了它是什么样的指针类型:

char*类型的指针就是为了存放char类型变量的地址
int *类型的指针就是为了存放int类型变量的地址
double *类型的指针就是为了存放double类型变量的地址的

到这里你应该理解一些了吧。

不过问题又来了,指针类型有什么意义?


2.1指针的运算 ±整数

int main()
{
	int n = 20;
	char* ch = &n;
	int* p = &n;
	printf("%p\n", &n);
	printf("%p\n", ch);
	printf("%p\n", ch+1);
	printf("%p\n", p);
	printf("%p\n", p+1);

	return 0;
}

在这里插入图片描述
从上面的代码和结果可以看出来,不管是int类型的指针p还是char类型的指针ch都可以存放n的地址并且是一样的没有发生变化。但是加上1问题就出来了,同样是地址+1为什么指针p和指针ch找到的地址不一样呢?
这正是由于指针类型不同导致的,int类型指针+1走的距离是4个字节,而char类型指针+1走的距离是1个字节,由此我们可以推论指针的类型决定了指针向前或者向后走一步有多大(距离)
总结:指针的类型决定了指针向前或者向后走一步有多大(距离)

char类型指针一步 -------------------------------- 1个字节
int类型指针一步 -----------------------------------4个字节
long long类型指针一步---------------------------8个字节
float类型指针一步---------------------------------8个字节
short类型指针一步--------------------------------2个字节

2.2指针的解引用

解引用操作符* :

int main()
{
	int n = 10;//向内存开辟一个空间给n
	int* p = &n;//将n的地址取出来放进p中。
	//那如何找到这个地址呢?如何访问这个变量呢?
	//我们可以根据地址来找到这个变量,而怎么找呢?需要解引用操作符*的帮助
	*p = 20;//*+指针变量(地址)也就是找到该地址的位置了,然后就可以访问该地址的变量了
	//p就修改成20了
	return 0;
}

上面只是简单介绍*解引用操作符,下面我要介绍下,解引用操作在不同指针类型的功能。

int main()
{
	int n = 0x11223344;//这个是16进制的表示0x是16进制的标志, 11  22  33  44 各占一个字节总共4个字节。
	char* ch = &n;
	int* p = &n;
	*p = 0;//访问p的地址也就是n  将n改成0了吗?
	*ch = 0;//访问p的地址也即是n,也把n改成0了吗?
	return 0;
}

在这里插入图片描述
我们可以看出对指针变量p修改为0,而对指针变量ch修改却变成一个奇怪的数字,这是为什么呢?通过的对n的内存我们来看一看:
在这里插入图片描述
在vs2022中按发f11进入调试,然后打开内存窗口查看n的内存,当箭头走向1,2时显示n的内存为 44 33 22 11(变量的内存是倒着放的,实际是11 22 33 44后面的文件管理会讲到),然后继续调试当完成3时发现44变成了00在这里插入图片描述
这说明什么呢?这说明对指针变量ch解引用然后访问n,只访问了一个字节的内存,所以只把44 置换成了00,那为什么会只访问一个字节呢?当然是因为它是char类型的指针啦然后让我们再看p指针的解引用。在这里插入图片描述
与上述操作一致,进行调试,发现当完成*p=0时,内存44 33 22 11变成了00 00 00 00,这说明什么?这说明了指针变量p解引用访问了4个字节内存,把4个字节内存都置换成0了。
在这里插入图片描述
总结:
指针的类型决定了,对指针解引用的时候权限有多大,能访问几个字节。

比如:
char指针解引用只能访问1个字节
int
指针解引用只能访问4个字节
double*指针解引用可以访问8个字节等

三.野指针

概念:野指针就是指针指向的位置不可知(是随机的,不正确的,没有明确限制的)

3.1野指针的成因

1.指针未初始化

int main()
{
	int* p;//局部变量指针未初始化,默认随机值
	*p = 10;//也就是一个随机值被改成了10,,这可不兴乱搞啊
	return 0;
}

2.指针访问越界

int main()
{
	int arr[] = { 1,2,3,4,5,6,7,8,9,10 };
	int* p = arr;
	int i;
	for (i = 0;i <= 11;i++)
	{
		//当指针指向的范围超过数组arr的范围时,p就是野指针
		*(p++) = 0;
	}
	
	return 0;
}

3.指针指向的空间释放
这个就是指指针所维护的那个地址没了,销毁了,既然空间都销毁了,那这个指针指向谁啊,所以是个也指针。举个简单例子

void print(int n)//进入函数内部,想一想n的生命域是什么
{
	int* p = &n;
	*p = 20;
}
int main()
{
	int n = 15;
	print(n);
	//n的生命域就是函数内部,出了函数,变量n的就不在存在,内存空间就被销毁了
	//所以在函数内部进行解引用赋值成20,但出了函数外部,那指针指向的那部分空间不存在了就变成野指针了
	printf("%d\n", n);
	
	return 0;
}

3.2如何规避野指针呢

1.指针要初始化
2.小心指针越界访问
3.指针指向空间释放即使之未NULL;
4.避免返回局部变量的地址
5.指针使用之前检查有效性。

int main()
{
      int *p=NULL;//不知道的指针先置成NULL;(NULL就是0的意思)
      int a=20;
      p=&a;
      if(p!=NULL)//使用之前检查有效性
      {
      *p=10;
      }
}

四.指针运算

  • 指针+ -整数
  • 指针+ -指针
  • 指针关系运算

4.1指针+ -整数

让指针走的距离

int main()
{
	int arr[10];
	int* p = NULL;
	for (p = arr;*p < arr[9];)
	{
		*(p++) = 0;//把10个数都置成0;
	}
	return 0;

4.2指针-指针

指针-指针得到的是一个整数,这个整数的绝对值是指针与指针之间元素的个数

int my_strlen(char* arr)
{
	char* ch = arr;
	while (*ch != '\0')
	{
		*(ch++);
	}
	return  ch - arr;

}
int main()
{
	char arr[] = "abcdef";
	int ret=my_strlen(arr);
	printf("%d", ret);//ret等于6
	return 0;
}

4.3指针的关系运算

后面和前面比较

int main()
{
	int arr[5];
	int* p = NULL;//数组最后一个元素后面个内存位置的指针
	for (p = &arr[5];p > &arr[0];)
	{
		*(--p) = 0;
	}
	return 0;
}

在这里插入图片描述
前面与后面比较:

int main()
{
	int arr[5];
	int* p = NULL;//数组最后一个元素后面个内存位置的指针
	for (p = &arr[4];p >= &arr[0];p--)
	{
		*p = 0;
	}
	return 0;
}

在这里插入图片描述
第二种情况在绝大多数的编译器上是可以顺利完成的,但是我们还是应该避免这样写,因为标准并不保证它可行。

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

五.指针和数组

我们来看一个例子:

int main()
{
	int arr[] = { 1,2,3,4,5 };
	printf("%p\n", &arr[0]);
	printf("%p\n", &arr);
	return 0;
}

结果为:在这里插入图片描述
这表明数组名和数组首元素的地址是一样的;
结论:数组名就表示首元素的地址。(sizeof(数组名)和。。。除外)。

int main()
{
  int arr[]={1,2,3,4,5};
  int *p=arr;//p里存放的就是数组首元素的地址
}

既然可以把数组名当初首元素地址来看,那么用指针访问数组就有可能了:

int main()
{
	int arr[] = { 1,2,3,4,5 };
	int* p = arr;
	int sz = sizeof(arr) / sizeof(arr[0]);
	int i;
	for (i = 0;i < sz;i++)
	{
		printf("arr[%d]=%p\n", i, p+i);
	}
	return 0;
}

结果为在这里插入图片描述
这是访问数组每一个地址接下来我们来访问数组每一个元素,代码如下:

int main()
{
	int arr[] = { 1,2,3,4,5 };
	int* p = arr;
	int sz = sizeof(arr) / sizeof(arr[0]);
	int i;
	for (i = 0;i < sz;i++)
	{
		printf("%d ", *(p + i));
	}
	return 0;
}

结果为:
在这里插入图片描述

六.二级指针

指针变量也是变量,变量就有地址,那么指针变量的地址在哪呢?
把存放指针变量的地址的变量称为二级指针:
在这里插入图片描述
对于二级指针的运算有:

  • **p可以通过解引用操作,对p中的地址寻找,能找到p,**p其实访问的就是p;
int mian()
{
   int b=10;
   int *p=&b;
   int **p2=&p;
   再找回来
   对**p2)解引用 找到是就是b的地址

七.指针数组

指针数组是指针还是数组呢?
答案是:数组。是存放指针的数组。
我们知道数组有整形数组,字符数组等
在这里插入图片描述
那指针数组是怎么的?
在这里插入图片描述

arr是个数组,有7个元素,每个元素都是一个整形指针。

在这里插入图片描述

评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

tew_gogogo

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

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

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

打赏作者

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

抵扣说明:

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

余额充值