一.野指针
1.野指针的定义
野指针即只想位置不知道的指针(随机的,不正确的,没有明确限制的)
2.野指针的成因以及规避方法
(1).指针未初始化
int main()
{
int* p;//局部变量p没有初始化,默认为随机值
*p = 20;
return 0;
}
规避方法很简单,我们初始化就可以了;不知道初始化为什么,我们初始化为空(NULL)即可。
int main()
{
int n = 10;
int* p = &n;
int* pp = NULL;
return 0;
}
(2).指针越界访问
这个和数组越界访问差不多,越界了之后就不知道指向谁了。
int main()
{
int arr[10] = { 0 };
int* p = &arr[0];
int i = 0;
for (i = 0; i <= 11; i++)
{
*(p + i) = i;
}
return 0;
}
这个的预防措施也很简单,我们小心点不越界即可。
(3).指针指向的空间释放
#include<stdio.h>
int* test()
{
int n = 1;
return &n;
}
int main()
{
int* p = test();
printf("%d", *p);
return 0;
}
我们要养成一个指针使用完毕之后置空的好习惯,还要养成一个判断指针是否为空的好习惯,下面我们来写一段代码。
int main()
{
int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
int* p = &arr[0];
for (int i = 0; i <= 10; i++)
{
*(p + i) = i;
}
//此时p已经越界了,所以我们要置空,以免下次使用出现bug
p = NULL;
//下次使用时,先判断是否为空,然后赋值。
if (p == NULL)
{
p = &arr[10];
}
return 0;
}
2.assert断言
在C语言库中,assert.h头文件定义了宏assert(),用于在运行时确保程序可以符合指定条件,如果不符合就终止运行,并将报错信息打印到屏幕上。这个宏被称为“断言”。
assert(p!=NULL);
上述代码的意思是,如果p的确不等于空,那么程序就可以继续执行;如果p等于空,则则会报出错误信息。即括号内的条件如果为真则继续执行,如果为假则报错。我们现在可以修改一下1.(3)中的判断代码来使用它。
可以在截图的右上角看到,问题出现在第92行,即我们的assert断言处。
二.指针的使用和传址调用
1.strlen的模拟实现
库函数strlen的功能是求字符串长度,统计的是字符串中\0之前字符的个数。
实现思路是,参数接受一个字符串的起始地址,然后开始遍历整个字符串,直到遇到、0/
参考代码如下:
int my_strlen(char* str)
{
int count = 0;
assert(str);//当str为NULL时,判断为假。则报错。
while (*str)// '\0'的ASCII码代表0,即假。
{ //因此当遇到\0时,循环结束。
count++;
*str++;
}
return count;
}
int main()
{
char a[] = "abcdef";
int len = my_strlen(&a[0]);
printf("%d", len);
return 0;
}
2.传值调用和传址调用
我们学习指针是为了解决问题的,那么什么问题是非指针不可的呢?
在没用学习指针前,我们可以是这样写代码的
例如:写⼀个函数,交换两个整型变量的值
⼀番思考后,我们可能写出这样的代码:
#include <stdio.h>
void Swap1(int x, int y)
{
int tmp = x;
x = y;
y = tmp;
}
int main()
{
int a = 0;
int b = 0;
scanf("%d %d", &a, &b);
printf("交换前:a=%d b=%d\n", a, b);
Swap1(a, b);
printf("交换后:a=%d b=%d\n", a, b);
return 0;
}
但是我们运行之后,却发现并没有产生交换的效果。这是为什么呢?
我们先调试一下
我们发现在main函数内部,创建了a和b,a的地址是0x00cffdd0,b的地址是0x00cffdc4,在调⽤
Swap1函数时,将a和b传递给了Swap1函数,在Swap1函数内部创建了形参x和y接收a和b的值,但是
x的地址是0x00cffcec,y的地址是0x00cffcf0,x和y确实接收到了a和b的值,不过x的地址和a的地址不⼀样,y的地址和b的地址不⼀样,相当于x和y是独⽴的空间,那么在Swap1函数内部交换x和y的值,⾃然不会影响a和b,当Swap1函数调⽤结束后回到main函数,a和b的没法交换。Swap1函数在使⽤的时候,是把变量本⾝直接传递给了函数,这种调⽤函数的⽅式我们之前在函数的时候就知道了,这种叫传值调⽤。
结论:实参传递给形参的时候,形参会单独创建⼀份临时空间来接收实参,对形参的修改不影响实
参。
那么我们应该怎么办呢?当然是我们不传数值了,我们传地址,我们让这个函数去操纵地址不就完了吗!
void swap(int* a,int* b)
{
int tmp = 0;
tmp = *a;
*a = *b;
*b = tmp;
}
int main()
{
int a = 10;
int b = 100;
swap(&a, &b);
printf("a=%d\nb=%d", a, b);
return 0;
}
我们使用这个函数即可完成任务,这个函数是通过传递地址而间接修改数据的。这种函数调用方式叫:传址调用。
传址调用,可以让函数和主函数之间建立起真正的联系,在函数内部就可以修改主函数中的数值。