一起养成写作习惯!这是我参与「掘金日新计划 · 4 月更文挑战」的第10天,点击查看活动详情。
目前在不断更新《c语言自学教程》
未来会讲到《数据结构算法》,《C++语言》,《Linux系统编程》,《Linux网络编程》,《MySQL数据库》等。
期待系统学习编程的小伙伴可以关注我,不错过!
1. 野指针
概念: 野指针就是指针指向的位置是不可知的(随机的、不正确的、没有明确限制的)
1.1 野指针成因
- 指针未初始化
#include <stdio.h>
int main()
{
int *p;//局部变量指针未初始化,默认为随机值
*p = 20;
return 0;
}
复制代码
- 指针越界访问
#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;
}
复制代码
- 指针指向的空间释放
放在动态内存开辟的时候详细讲解,这里简单提示一下 在调用完test函数后a被销毁,0x0012ff40这块空间的使用权被收回,此时p就是一个野指针,但后续代码仍然访问这个空间,但那块空间内容没有被改变,所以凑巧能打印出100。
1.2 如何规避野指针
1. 指针初始化
2. 小心指针越界
3. 指针指向空间释放及时放置NULL
4. 避免返回局部变量的地址
5. 指针使用之前检查有效性
#include <stdio.h>
int main()
{
int *p = NULL;//指针指向空间释放及时放置NULL
//....
int a = 10;
p = &a;
if(p != NULL)//指针使用之前检查有效性
{
*p = 20;
}
return 0;
}
复制代码
2. 指针运算
2.1 指针+-整数
int main()
{
int arr[10] = { 0 };
int*p = arr;
int i = 0;
int sz = sizeof(arr) / sizeof(arr[0]);
for (i = 0; i < sz; i++)
{
*(p + i) = i;
}
for (i = 0; i < sz; i++)
{
printf("%d ", *(p + i));
}
return 0;
}
复制代码
2.2 指针-指针
int main()
{
int a[10] = { 0 };
printf("%d\n", &a[9] - &a[0]);//9
printf("%d\n", &a[0] - &a[9]);//-9
return 0;
}
复制代码
2.3 指针的关系运算
//从后往前初始化
float values[5];
float *vp;
for(vp = &values[5]; vp > &values[0];)
{
*--vp = 0;
}
复制代码
代码简化, 这将代码修改如下:
for(vp = &values[4]; vp >= &values[0];vp--)
{
*vp = 0;
}
复制代码
简化后的代码实际在绝大部分的编译器上是可以顺利完成任务的,然而我们还是应该避免这样写,因为标准并不保证它可行。
标准规定: 允许指向数组元素的指针与指向数组最后一个元素后面的那个内存位置的指针比较,但是不允许与 指向第一个元素之前的那个内存位置的指针进行比较。
3. 指针和数组
我们看一个例子:
#include <stdio.h>
int main()
{
int arr[10] = {1,2,3,4,5,6,7,8,9,0};
printf("%p\n", arr);
printf("%p\n", &arr[0]);
return 0;
}
复制代码
运行结果: 可见数组名和数组首元素的地址是一样的。 结论:数组名表示的是数组首元素的地址。(2种情况除外,数组章节讲解了) 那么这样写代码是可行的:
int arr[10] = {1,2,3,4,5,6,7,8,9,0};
int *p = arr;//p存放的是数组首元素的地址
复制代码
既然可以把数组名当成地址存放到一个指针中,我们使用指针来访问一个数组就成为可能。 例如:
#include <stdio.h>
int main()
{
int arr[] = {1,2,3,4,5,6,7,8,9,0};
int *p = arr; //指针存放数组首元素的地址
int sz = sizeof(arr)/sizeof(arr[0]);
for(i=0; i<sz; i++)
{
printf("&arr[%d] = %p <====> p+%d = %p\n", i, &arr[i], i, p+i);
}
return 0;
}
复制代码
运行结果: 所以 p+i 其实计算的是数组 arr 下标为i的地址。 那我们就可以直接通过指针来访问数组。 如下:
int main()
{
int arr[] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 0 };
int *p = arr; //指针存放数组首元素的地址
int sz = sizeof(arr) / sizeof(arr[0]);
int i = 0;
for (i = 0; i<sz; i++)
{
printf("%d ", *(p + i));
}
return 0;
}
复制代码
4. 二级指针
指针变量也是变量,是变量就有地址,那指针变量的地址存放在哪里?
这就是 二级指针
。 对于二级指针的运算有: *ppa 通过对ppa中的地址进行解引用,这样找到的是 pa , *ppa 其实访问的就是 pa .
int b = 20;
*ppa = &b;//等价于 pa = &b;
复制代码
**ppa 先通过 *ppa 找到 pa ,然后对 pa 进行解引用操作: *pa ,那找到的是 a .
**ppa = 30;
//等价于*pa = 30;
//等价于a = 30;
复制代码
好家伙,这就是套娃呀。
7. 指针数组
指针数组是指针还是数组? 答案:是数组。是存放指针的数组。 数组我们已经知道整形数组,字符数组。
int arr1[5];
char arr2[6];
复制代码
那指针数组是怎样的?
int* arr3[5];//是什么?
复制代码
arr3是一个数组,有五个元素,每个元素是一个整形指针。
本章内容到此结束啦,但指针的内容还没说完呢,那就在这来个下期预告吧,下一期我们还要详细讲指针,内容更是有重点难点,相信我们能够通过努力击败大魔王指针! 感兴趣的朋友可以关注我不迷路!