(3)指针指向的空间释放
我们看这个代码
#include<stdio.h> int* test() { int a = 10; return &a; } int main() { int* p = test(); printf("%d", *p); return 0; }
这个代码也是,a是一个局部变量,返回的时候a已经被销毁了,此时的这个地址就是一个野指针。
当然我们可以运行一下,我们会发现仍然是10,这是因为之前的函数栈帧还没有被破坏掉。我们加上个代码就能破坏掉这个函数栈帧,导致结果不一样
2.如何规避野指针
(1) 指针初始化
#include<stdio.h> int main() { int a = 0; int* pa = &a;//指针的初始化 int* pc = NULL;//空指针,专门用来初始化指针 return 0; }
如上代码所示,如果不知道该初始化成什么,可以初始化成NULL,当然要注意,空指针不可以解引用。解引用空指针会使程序崩溃。因为0地址处是不能让用户使用的
(2) 小心指针越界
(3) 指针指向空间释放,及时置NULL
(4) 避免返回局部变量的地址
(5) 指针使用之前检查有效性
四、指针运算
指针的运算共有三种
1.指针+- 整数
2.指针-指针
3.指针的关系运算
1.指针+-整数
这快的内容在前文中已经涉及过,这里在简单的讲解一个案例
#define N_VALUES 5 #include<stdio.h> int main() { float values[N_VALUES]; float* vp; //指针+-整数;指针的关系运算 for (vp = &values[0]; vp < &values[N_VALUES];) { *vp++ = 0; } return 0; }
这段代码唯一需要注意的就是一个表达式*vp++。++的优先级比较高,但是由于他是前置++,所以先解引用vp,然后vp++。最终的效果就是将这个数组置零。
2.指针-指针
指针-指针有个前提
两个指针要指向同一个空间,并且两个指针类型相同
指针-指针的绝对值是两个指针之间的元素个数
我们看这个代码
#include<stdio.h> int main() { int arr[10] = { 0 }; printf("%d", &arr[9] - &arr[0]); return 0; }
运行结果为
我们之前讲解过自己实现一个求字符串长度的解法,一种是计数器,另外一种是递归的思想,今天我们在采用一种指针-指针的方法,使用\0处的指针-起始点
代码如下
#include<stdio.h> int my_strlen(char* arr) { int* str = arr; while (*arr != '\0') { arr++; } return arr - str; } int main() { char arr[] = "abcdef"; int len = my_strlen(arr); printf("%d", len); return 0; }
当然,我们也可以将*,和++进行合并
#include<stdio.h> int my_strlen(char* arr) { int* str = arr; while (*arr++ != '\0') ; return arr - str - 1; } int main() { char arr[] = "abcdef"; int len = my_strlen(arr); printf("%d", len); return 0; }
3.指针的关系运算
#define N_VALUES 5 #include<stdio.h> int main() { float values[N_VALUES]; float* vp; for (vp = &values[N_VALUES]; vp > &values[0];) { *--vp = 0; } }
这段代码与前面一段代码很相似,功能就是将数组置零。这个是先--vp然后在解引用,最终vp停留的位置就是数组元素的起点
我们在看一下这段代码
#define N_VALUES 5 #include<stdio.h> int main() { float values[N_VALUES]; float* vp; for (vp = &values[N_VALUES - 1]; vp >= &values[0]; vp--) { *vp = 0; } }
这段代码,是将上面的*和--混用的部分代码给拆开写了。这样写确实可以实现我们的功能,但是要注意的是,第二种写法在绝大多数编译器是没有问题的,但是然而我们还是应该避免这样写,因为标准并不保证它可行。在少部分的编译器上还是会出问题的。
因为这个段代码最后会出现数组首元素的前一个地址与这个数组首元素地址进行比较。
而我们的标准是这样规定的:允许指向数组元素的指针与指向数组最后一个元素后面的那个内存位置的指针比较,但是不允许与 指向第一个元素之前的那个内存位置的指针进行比较。
五、 指针和数组
1.指针和数组是不同的对象
指针是一种变量,存放地址的,大小4/8字节的
数组是一组相同类型元素的集合,是可以放多个元素的,大小是取决于元素个数和元素类型的
2.数组的数组名是数组首元素的地址,地址是可以放在指针变量中
可以通过指针访问数组
比如下面的代码就可以实现指针访问数组
#include<stdio.h> 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 + 1; p++; } //打印 p = arr; for (i = 0; i < sz; i++) { printf("%d ", *p); p++; } return 0; }
或者也可以这样写
#include<stdio.h> 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 + 1; } //打印 for (i = 0; i < sz; i++) { printf("%d ", *(p+i)); } return 0; }
还有一点需要说明的是
我们在这里出现了
int arr[10];
int* p=arr;
这里说明arr是一个首元素地址
而地址就可以解引用。所以我们知道arr[i]--->*(arr+i)
而加法是满足交换律的,所以进而推出*(i+arr)
进而推出 i[arr]
事实上,这个确实是没有问题的,可以正常使用的,因为 [ ]他只是一个操作符,i和arr是这个操作符的操作数而已。在我们电脑里面arr[i]也会被翻译成*(arr+i)。
六、二级指针
1.什么是二级指针?
我们在之前说过的指针是这样的int* pa=&a;也就是将a的地址放入pa中,pa的类型是int*。
那么pa这个指针其实应该也有一个地址,我们如果取出他的地址,将他放入另外一个变量,这就叫做二级指针。
也就是int**ppa=&pa。其中第二颗*代表着他是一个指针,前面的int*代表着他指向的类型是一个int*类型的数据
如下代码所示
#define _CRT_SECURE_NO_WARNINGS 1 #include<stdio.h> int main() { int a = 10; int* pa = &a; int** ppa = &pa; return 0; }
如下图示关系
2.二级指针的解引用
对于二级指针的解引用需要两颗*
有如下等式成立
**ppa==*pa=a
举个例子
#define _CRT_SECURE_NO_WARNINGS 1 #include<stdio.h> int main() { int a = 10; int* pa = &a; int** ppa = &pa; printf("%d\n", **ppa); **ppa = 50; printf("%d\n", **ppa); return 0; }
运行结果为
七、指针数组
1.指针数组的概念
在这里,我们首先需要了解指针数组是数组还是指针呢?
其实,是数组,从语言的角度来思考,指针是修饰词,数组才是主语
比如字符数组,他是一个数组,里面存放的都是字符
比如整型数组,他是一个数组,里面存放的都是整型
所以我们便能猜测到,指针数组,他是一个数组,里面存放的都是指针
我们举一个例子
#include<stdio.h> int main() { int a = 10; int b = 20; int c = 30; int d = 40; int e = 50; int* arr[5] = { &a,&b,&c,&d,&e }; int i = 0; for (i - 0; i < 5; i++) { printf("%d ", *(arr[i])); } return 0; }
运行结果为
图解为
2.尝试模拟一个二维数组
我们了解了指针数组的概念以后,我们可以利用其模拟一个二维数组,假设我们要模拟三行四列的二维数组
我们是这样想的,先定义出三个一维数组a,b,c。然后定义一个指针数组,令指针数组的每个元素是这些一维数组的首元素地址。然后我们就可以通过两次遍历就能模拟出来这个二维数组了
代码如下
#include<stdio.h> int main() { int a[] = { 1,2,3,4 }; int b[] = { 2,3,4,5 }; int c[] = { 3,4,5,6 }; int* arr[] = { a,b,c }; int i = 0; for (i = 0; i < 3; i++) { int j = 0; for (j = 0; j < 4; j++) { printf("%d ", *(arr[i]+j)); } printf("\n"); } return 0; }
运行结果为
当然,如果我们将*(arr[i]+j)改为arr[i][j]也是正确的,因为这两种是可以相互转换的
总结
本节课我们主要详细讲解了指针与内存对于他们理解,指针和指针类型,指针类型的意义,野指针的成因,以及如何规避野指针,指针的三种运算,指针与数组的关系,二级指针,以及指针数组
如果对你有帮助的话,不要忘记点赞加收藏哦!!!