伙伴们好久不见,这一期是我们指针的第二期,上一期指针收到了不错的反馈,我能感受的小伙伴们学习的热情,话不多说,我们马上开始这一期的内容吧。
在学习新知识之前,也不要忘记复习学过的知识,上一期我们讲了指针就是地址,人们口中的指针就是一个存放地址的变量,被称为指针变量,指针变量指向某个变量的首地址,还讲了指针变量分不同类型的意义是不同类型的指针决定它能对它指向的变量一次性可以操作的步长,我们还讲了&(取地址符)可以对指针指向的变量操作,*(解引用符)可以对指针指向的变量的值操作。
1.野指针
什么是野指针: 野指针就是一个指针变量指向的地址是未知的(随机的,没有明确限制的)
问题:野指针是怎么来的?
1.1野指针的成因
1.指针未初始化
我们知道,如果我们创建一个局部变量不初始化的话,它的值是未知的,而指针变量如果不初始化,那么它指向的地址也是未知的
#include <stdio.h>
int main()
{
int *p;//局部变量指针未初始化,默认为随机值
*p = 20;
return 0;
}
以这段代码为例,我们创建了一个没有初始化的指针,当我们在vs 2022环境下编译,会发生什么呢?
可以看到在vs2022环境下编译无法通过.
2.指针越界访问
什么是指针越界访问呢?指针越界访问就是指针变量超出它指向变量或数组的地址范围,假设我们创建一个指向int类型变量的指针变量,它默认可以访问的地址是它指向的变量的四个字节,如果超出这四个字节去访问其他地址,那么我们称为指针越界访问.
越界示范
画图演示:
1.2如何规避野指针
1.指针初始化
一个指针指向的地址未知,我们称为野指针,如果我们在创建指针的同时对它初始化,那么它指向的地址是不是就可知了呢
2.避免指针越界
这就要求我们日常代码编写的过程中,不要写出让指针越界的语句
3.指针指向的空间销毁之前,及时将指针置NULL
我们使用一个自己创建的函数,系统会在我们执行调用函数动做之前,在栈(一个内存空间)上为函数开辟一块空间,当这个函数执行结束以后,这块开辟的函数也会被系统回收,这时如果我们在里面创建了指针,那么当函数执行结束时,它指向的地址也会被回收,它指向的地址也未知了,我们要在函数空间被回收之前让这个指针置为NULL(空).
4.避免返回局部变量的地址
原理同上一条
5.指针使用之前检查指针的有效性
在使用指针之前,检查指针的有效性,也能规避野指针的出现,如我们在使用它之前,不知道这个指针是不是空指针,我们就可以用if语句判断,是空指就不使用它,不是空指针则正常使用
#include <stdio.h>
int main()
{
int *p = NULL;
//....
int a = 10;
p = &a;
if(p != NULL)
{
*p = 20;
}
return 0;
}
2.指针运算
2.1指针+-整数
指针的+-运算通常是对指针指向的地址的移动,前面我们讲过,指针类型决定指针可以操作的步长,如果一个指针pa+1,那么它指向的地址会向前走四个字节由第一个字节的地址变成指向第五个字节的地址,假设我们创建一个整型数组,把它的首地址&arr[0]给指针变量pa,而pa+1,那么此时pa指向arr[1],pa-1同理.
2.2指针-指针
指针-指针通常用在两个指针之间元素的个数,指针-指针计算的前提是两个指针指向的是同一块空间,例如我们我们创建一个数组,再创建两个指针变量,分别让这两个指针指向它的首地址和尾地址,用指向尾地址的指针-指向首地址的指针就可以得到它们之间元素的个数,那么我们是不是不知不觉把这个数组的长度计算出来了呢.
int main()
{
int arr[] = { 1,2,3,4,5,6,7,8,9,10 };
int* pa1 = arr;
int* pa2 = &arr[9];
int len = pa2 - pa1+1;
printf("%d", len);
}
我们运行代码试试
代码中的数组是10个元素,尾地址指针-首地址指针为数组长度-1,那么我们让这个结果+1就是数组长度.
2.3指针的关系运算
指针不仅可以进行+-运算,指针-指针运算,也可以进行进行关系运算,如指针>指针,指针!=指针
for(vp = &values[N_VALUES]; vp > &values[0];)
{
*--vp = 0;
}
标准规定: