深入理解指针(2)

文章目录

这里是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 = &num;
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;
}

这里如果传的是值的话,形参的改变是影响不了实参的,形参是实参的临时拷贝。

如果想深入了解一下为什么是这样的,可以看看我的之前的博客函数栈帧的创建和销毁

如果传的是指针的话,就可以通过指针来访问要交换的值,以达到交换的目的。

总结

谢谢观看!

相关推荐
CodeStats11 小时前
《源纹天书》第三百六十三章至第三百六十四章
java·开发语言·源纹天书
万法若空11 小时前
C/C++ 类型别名定义方式对比
java·c语言·c++
Java后端的Ai之路11 小时前
03、Python - 抽象工厂模式
开发语言·python·设计模式·抽象工厂模式·通用智慧
吴声子夜歌11 小时前
Java——开发中通用的方法和准则(一)
java·开发语言
常驻客栈11 小时前
B-深入理解计算机系统第3版之第11章:网络编程
开发语言·笔记·学习笔记·常驻客栈·深入理解计算机系统
旧梦952711 小时前
Java 单例模式:从基础到实战的完整指南
java·开发语言·单例模式
小小晓.12 小时前
C++ 值类别与完美转发深度剖析:从左值右值到 std::forward 源码解密
开发语言·c++·算法
格林威13 小时前
C# 相机Burst模式图像采集:使用相机内存配合OpenCvSharp和Halcon实现短时间的高速采集的方法
开发语言·人工智能·数码相机·计算机视觉·c#·视觉检测·工业相机
chen<>13 小时前
C++ 六种 memory_order:从原子性到线程间可见性.md
java·linux·开发语言·c++
一直C13 小时前
Linux系统编程|进程间通信IPC全套详解(1)(管道、信号)
linux·c语言·开发语言·网络·青少年编程·visual studio code