过关斩将,擒“指针”(上)

美丽的外貌千篇一律,有趣的灵魂万里挑一

指针是C语言的灵魂

应该有不少小伙伴对指针还是比较迷迷糊糊的吧

本期 阿紫 带大家一起过关斩将,擒 “ 指针 ”


目录

1.指针是什么 ?

2.指针变量和指针变量类型 

2.1指针变量+-整数

2.2指针变量的解引用 

3.野指针

3.1野指针的成因 

3.2如何避免野指针 

4.指针变量的运算 

4.1指针+-整数 

4.2指针-指针 

4.3指针的关系运算 

5.指针变量和数组

6.二级指针 


1.指针是什么 ?

指针是什么?

我们知道计算机内存中的基本存储单位是字节,计算机对每个字节都会进行编号,我们把对字节的编号称为地址,那么这个地址也就称为指针

地址:地址是唯一标识一块内存空间的,我们可以通过地址找到对应的内存空间

注:我们通常所说的指针是指的指针变量 ,是用来存放地址的变量。

  • 指针:地址
  • 指针变量:存放地址的变量

内存编号图:

我们如何将变量的地址放入到一个指针变量中?

我们通常用 & (取地址符),将一个变量的地址放入到指针变量中 

	int a = 0;
	int* p = &a;

 注:a 是 int 类型,所以它占4个字节,将 4个字节中的的第 1 个字节地址放入指针 p 中。指针 p 的类型是int*,但并不代表指针 p 也占4个字节,而是代表着 p 找到了变量 a 的第一个字节的地址后,应该访问多少个字节。

我们现在知道了计算机会对内存中的每一个存储单元(字节)进行编址,那我们现在思考一下计算机是如何对进行编址的?

对于32位的机器,假设有32根地址线,那么假设每根地址线在寻址的时候产生高电平(高电压)和低电平(低电压)就是(1或者0), 那么32根地址线产生的地址就会是: 就会产生2的32次方个地址,每个地址由32位二进制进行编码

同样的方法对64位的机器,就会产生 2 的 64 次方个地址,每个地址由64位二进制进行编码

  • 32位的机器:一个地址由32位二进制组成,8个二进制等于一个字节,那么32位的机器产生的地址也就是4个字节 
  • 64位的机器:一个地址由64位二进制组成,8个二进制等于一个字节,那么64位的机器产生的地址也就是8个字节 

2.指针变量和指针变量类型 

我们知道变量有不同的类型,那么指针变量有没有类型呢?

准确来说:指针变量是有类型的,但它跟变量的类型却有所不同。变量的类型一般大多数是用来开辟多大的空间的,而指针变量的类型并不是用来开辟空间的,而是表示它能访问多大空间

接下来我们一起来看看指针变量类型的作用吧! 

 2.1指针变量+-整数

变量 a 是一个int类型的,打印 &a 打印的是变量 a 起始位置字节空间的地址,pi 和 pc 里面存放的都是 a 起始位置的地址 ,所以它们打印的也是 a 起始位置的地址。但是 pi 是 int* 类型的所以它可以访问 4 个字节 ,所以pi + 1跳过了四个字节的地址。pc 是char* 类型的所以它可以访问 1 个字节,所以pc + 1跳过了 1 个字节的地址

  • 指针类型决定了指针向前或向后走一步有多大距离 

2.2指针变量的解引用 

将n的地址强制类型转换为 char* 赋值给 char* pc 字符指针变量。因为每个变量的地址都是起始位置的地址,那么 pc 中也就存放了 n 变量的起始位置的地址,然后给 pc解引用 找到 pc 指向的地址对应的空间,因为 pc 是 char* 类型的它只能访问一个字节的空间大小,所以它只改变了起始位置空间里面的内容。

n 是一个整型变量,将 n 的地址赋值给 int* pi 整型指针变量。因为每个变量的地址都是起始位置的地址,那么 pi 中也就存放了 n 变量的起始位置的地址,然后给 pi解引用 找到 pi 指向的地址对应的空间,因为 piint* 类型的它可以访问四个字节的空间大小,变量n是整型占四个字节空间大小,所以它改变了n所有的空间里面的内容

  •  指针的类型决定了,对指针解引用的时候有多大的权限(能操作几个字节)

 比如: char* 的指针解引用就只能访问一个字节,而 int* 的指针的解引用就能访问四个字节

 3.野指针

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

3.1野指针的成因 

① 指针未初始化 

局部变量指针未初始化,默认为随机值 。如果不知道给指针初始化为什么,建议初始化为NULL

② 指针越界访问 

数组下标是从 0 开始的,arr 数组中有 10 个元素,所以下标到 9 就结束了。但它依然可以访问改变下标为 10 的空间里面的内容,这就是越界访问,当程序运行时程序会直接崩溃并提示崩溃原因。当指针指向的范围超出数组 arr 的范围时,p 就是野指针

③ 指针指向的空间释放  

malloc 开辟一块 int 类型大小的空间,让指针变量 p 指向这块空间。当释放了这块空间时应该让 p 指向 NULL ,要是 p 没有指向 NULL, p 依旧记得这块空间地址。但是这块空间已经释放了也就等于还给了操作系统,那就很可能被其他分配的动态内存所占用。但是 p 还是指向了这块空间,当使用者忘记了前面已经释放这块空间时,就很有可能误改变了已经属于其他空间的值。所以当指针指向一块动态内存开辟的空间时,当这块动态内存被释放,指针应该置为 NULL。

3.2如何避免野指针 

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

4.指针变量的运算 

4.1指针+-整数 

#include<stdio.h>
int main()
{
	int values[6] = {0};
	int *vp = &values[0];
	//指针+-整数;指针的关系运算
	while (vp < &values[6])
	{
		*vp++ = 1;
	}
	return 0;
}

*vp++ = 1++ 是一个后置。所以 vp  先和 * 结合找到 vp 指向的地址空间,将里面的内容改为1,然后再 vp++ ,指向下一个地址。  *vp++ = 1;相当于   *vp = 1;vp += 1; 

4.2指针-指针 

#include<stdio.h>
int main()
{
	int arr[7] = { 0 };
	int* begin = arr;//数组名代表第一个元素下标地址
	int* end = &arr[6];
	printf("%d", end - begin);
	return 0;
}

 指针 - 指针 = 它们中间元素的个数

begin 指针变量指向的是数组第一个元素的地址,数组中每个元素的类型都为 int 型占4个字节大小,每个元素的地址都为4个字节中第一个字节的地址,那 &arr[6] 则是最后一个元素所占的4个字节的第一个字节的地址,所以我们 end - begin = 6 

4.3指针的关系运算 

#include<stdio.h>
int main()
{
	int arr[7] = { 0 };
	for (int* vp = &arr[6]; vp >= &arr[0]; vp--)
	{
		*vp = 1;
	}
	return 0;
}

实际在绝大部分的编译器上是可以顺利完成任务的,然而我们还是应该避免这样写,因为标准并不保证它可行

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

 5.指针变量和数组

首先我们来看一个例子:

从上面的代码运行结果中我们可以看出:数组名跟数组首元素的地址是一样的 

那我们就可以得出一个结论:数组名表示数组首元素地址 

那我们再看两个例子:

例1: 

当数组名 和 数组首元素放在 sizeof 中求大小时,却不同了。数组名放在 sizeof 中求大小时,求的是整个数组的大小。而数组首元素放在 sizeof 中求大小时,求的是首元素的大小。

 例2:

arr 表示数组首元素地址,&arr 表示整个数组的地址,但是它们打印出来的结果是一样的地址。但是通过给它们分别 +1 就能看出区别,arr + 1 跳过的是一个元素大小,而 &arr + 1 跳过的是整个数组的大小 

结论:sizeof(数组名) &数组名 表示整个数组大小,其余的 数组名 都表示数组首元素的地址

既然可以把数组名当成地址存放到一个指针中,我们使用指针来访问数组就成为可能。 

所以 p+i 其实计算的是数组 arr 下标为i的地址 ,那我们就可以直接通过指针来访问数组。

如下: 

#include<stdio.h>
int main()
{
	char arr[6] = { 'a', 'b', 'c', 'd', 'e', 'f' };
	char* p = arr; //指针存放数组首元素的地址
	int sz = sizeof(arr) / sizeof(arr[0]);
	for (int i = 0; i<sz; i++)
	{
		printf("%c\n", *(p + i));
	}
	return 0;
}

 打印的时候也可以写成:printf("%c\n", p[i])。  我们知道 arr 表示数组首元素地址arr[0] 表示数组第一个元素,那 p 指针变量中也是 arr 数组中首元素地址,那它同样 p[0] 表示数组第一个元素地址。

6.二级指针 

 二级指针:指针变量也是变量,那它也就有地址,存放一级指针地址的指针就是二级指针。

注:存放一级指针地址的指针称二级指针,存放二级指针地址的指针称三级指针...

 把 a 的地址放在 p 中,p 就是一级指针。把 p 的地址放在 pp 中,pp 就是二级指针

对二级指针的运算:  

  • *pp 通过对 pp 中的地址进行解引用,这样找到的是 p , *pp 其实访问的就是 p
  •  **pp 先通过 *pp 找到 p ,然后对 p 进行解引用操作: *p ,那找到的是 a .

  • 40
    点赞
  • 30
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 57
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

拼命阿紫

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

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

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

打赏作者

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

抵扣说明:

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

余额充值