
文章目录
这里是think的博客
希望可以一起交流知识,一起think
今天我们来学习**指针(2)**吧
一起来think吧
const修饰指针
const修饰变量
c
#include <stdio.h>
int main()
{
int m = 0;
m = 20;//m是可以修改的
const int n = 0;
n = 20;//n是不能被修改的
return 0;
}
加了const,n就不能修改了,此时的n是变量,但是不能修改,具有常性,有被叫做常变量,即具有常性的变量,const是在语法上限制了我们对变量的修改。
但是如果你用指针找到了n的值,是可以改变的,但是这是未定义行为,最好不要这样。
const修饰指针变量
int * p;//没有const修饰?
int const * p;//const 放在*的左边做修饰
int * const p;//const 放在*的右边做修饰
很好理解的,1.const在*之前就是对*p进行限制,即p指向的那个变量的值不可改变。2.const在*之后就是对p进行限制,即p的指向不可改变。

我们可以看到p1和p3中指向的变量值不可改变,而p2是指向不可改变。
野指针
概念: 野指针就是指针指向的位置是不可知的(随机的、不正确的、没有明确限制的)
野指针成因
指针未初始化
c
#include <stdio.h>
int main()
{
int *p;//局部变量指针未初始化,默认为随机值
*p = 20;
return 0;
}
指针越界访问
c
#include <stdio.h>
int main()
{
int arr[10] = {0};
int *p = &arr[0];
int i = 0;
for(i = 0; i <= 11; i++)
{
//当指针指向的范围超出数组arr的范围时,p就是野指针
*(p++) = i;
}
return 0;
}
指针指向的空间释放
c
#include <stdio.h>
int* test()
{
int n = 100;
return &n;
}
int main()
{
int*p = test();
printf("%d\n", *p);
return 0;
}
如何规避野指针
指针初始化
当创建一个指针的时候就给他初始化,如果现在创建的指针,现在还用不到,就int *p =NULL;即可
NULL是什么?
#ifdef __cplusplus
#define NULL 0
#else
#define NULL ((void *)0)
#endif
在C++中就是0,在C语言中是被强制类型转换为void*的0。
c
#include <stdio.h>
int main()
{
int num = 10;
int*p1 = #
int*p2 = NULL;
return 0;
}
小心指针越界访问
一个程序向内存申请了哪些空间,通过指针也就只能访问哪些空间,不能超出范围访问,超出了就是越界访问。
指针使用后置为NULL,使用前检验是否为NULL
c
int main()
{
int arr[10] = {1,2,3,4,5,6,7,8,9,10};
int *p = &arr[0];
int i = 0;
for(i = 0; i < 10; i++)
{
*(p++) = i;
}
//此时p已经越界了,可以把p置为NULL
p = NULL;
//下次使⽤的时候,判断p不为NULL的时候再使⽤
//...
p = &arr[0];//重新让p获得地址
if(p != NULL) //判断
{
//...
}
return 0;
}
不要返回局部变量的地址
看看上面的第三个例子即可,局部变量是出来作用域就会被销毁的。
assert断言
assert.h 头文件定义了宏 assert() ,用于在运行时确保程序符合指定条件,如果不符合,就报错终止运行。这个宏常常被称为"断言"。
assert(p != NULL);
if(p!=NULL){...
}
else{
...
}
这里有两种验证方式,assert是比较暴力的,直接报错,并将assert中的内容,当前文件的绝对目录和行号都打印出来,非常便于寻找bug。
if,else显然是比较温和的,出错了,程序也不会终止,但是如果不在else中写提示信息的话,代码量如果多了,bug就会比较难找。


并且assert是可以关闭的,当bug被排完了以后,assert又写了很多个的时候可以在#include <assert.h>之前加上这句代码
#define NDEBUG
#include <assert.h>
即可直接关闭assert断言,这句的意思就是不要debug了,bug都找到了,不用assert了。
有了这一句代码,编译器就直接跳过了这个assert,不会执行了,当然release的时候assert也是不管用的,就算不加#define NDEBUG ,因为release的时候就代表debug已经完成了。

为什么要禁用?
因为使用assert引入了额外的检查,增加了程序的运行时间。
指针的使用和传址调用
strlen的模拟实现
库函数strlen的功能是求字符串长度,统计的是字符串中 \0 之前的字符的个数。
函数原型如下:
size_t strlen ( const char * str );
c
int my_strlen(const char * str)
{
int count = 0;
assert(str);
while(*str)
{
count++;
str++;
}
return count;
}
int main()
{
int len = my_strlen("abcdef");
printf("%d\n", len);
return 0;
}
这里我们在参数上加了const,防止函数内部修改我们的str,这个函数逻辑上是不能修改我们的str的,又加了断言,这两步增强了我们程序的健壮性。
传值调用和传址调用
有没有什么情况是必须传地址的呢?
c
#include <stdio.h>
void Swap2(int*px, int*py)
{
int tmp = 0;
tmp = *px;
*px = *py;
*py = tmp;
}
int main()
{
int a = 0;
int b = 0;
scanf("%d %d", &a, &b);
printf("交换前:a=%d b=%d\n", a, b);
Swap2(&a, &b);
printf("交换后:a=%d b=%d\n", a, b);
return 0;
}
这里如果传的是值的话,形参的改变是影响不了实参的,形参是实参的临时拷贝。
如果想深入了解一下为什么是这样的,可以看看我的之前的博客函数栈帧的创建和销毁
如果传的是指针的话,就可以通过指针来访问要交换的值,以达到交换的目的。
总结
谢谢观看!