当前位置: 首页 > news >正文

深挖C语言:深入理解指针(2)

目录

1.const修饰指针

2.野指针

2.1 野指针的成因:

2.2 规避野指针

3.assert断言

4.指针的使用和传值调用

4.1 库函数strlen的实现

4.2 传值调用和传址调用

1.const修饰指针

作用:使操作对象不能被随意修改

操作对象:普通变量,指针,函数形参,全局常量。

int m = 10; m = 20; // m 能被修改 const int n = 20; n = 10;// n 不能被修改

这里虽然 n 被 const 修饰,强行修改会报错,但是我们可以通过指针的方式绕过const的限制修改变量n,代码如下:

const int n = 10; int* pn = &n; *pn = 20;// n 成功被修改

这种行为显然打破了语法规则,如果我们不想让变量通过指针被修改,那么就对指针也进行 const 修饰,代码如下:

const int n = 10; const int* pn = &n; * pn = 20;// n不能被修改,代码报错 int* const pn = &n; *pn = 20;// n可以被修改

虽然说对指针变量进行了const修饰,但是const放在 * 的前面或者后面,修饰的对象不相同,所加的范围限制也不相同。

const int n = 10; int m = 20; int* const pn = &n; *pn = 30;//没问题 pn = &m;//报错,指针变量pn不能被修改 const int* pm = &m; *pm = 30;//报错,*pm不能被修改 pm = &n;//没问题

结论:const修饰指针变量时:

1.如果放在 * 的左边,修饰的是所存指针指向的内容,使指向的内容不能被修改。

2.如果放在 * 的右边,修饰的是指针变量所存的内容,指针变量的内容不能被修改

2.野指针

概念:指针指向的位置是不可知的(随机的,不正确的,没有明确限制的),称为野指针。

2.1 野指针的成因:

(1)指针未初始化

int n; int* pn = &n;// pn因变量未初始化成为野指针

(2)指针越界访问

int arr[10] = { 0 }; int* parr = &arr[10];// parr越界访问成为野指针

(3)指针指向的空间被释放

int* test() { int n = 100; return &n; } int main() { int* p = test();// n的空间被释放,p成为野指针 printf("%d\n",*p); return 0; }

注意:一个程序向内存申请了哪些空间,通过指针也就只能访问这些空间,超出这个范围了就是越界访问。

2.2 规避野指针

(1)指针使用之前检查指针的有效性,指针变量不再使用时,及时置NULL

(2)不要返回局部变量的地址。

野指针就好比是一条没有拴绳的野狗,对其放任不管是很危险的,对指针及时赋予NULL,相当于把这条野狗给栓了起来,对NULL约定俗成的一个规矩就是:只要是NULL指针就不去访问,规避了野指针的形成。

3.assert断言

使用代码如下:

#include<assert.h> //使用assert不要忘记带上头文件 assert(p != NULL);

代码运行时,assert()宏接受一个表达式作为参数,如果该表达式为真(返回结果非零),assert()就不会产生任何作用,程序继续执行。如果表达式为假(返回结果为零),assert()就会中止代码的运行,并进行报错

如果已经确定程序没有任何问题,不需要再进行断言,此时在#include<assert.h>前定义一个宏NDEBUG即可,代码如下:

#define NDEBUG //消除assert() #include<assert.h>

注意:(1)assert因为对程序有了检查,会增加程序运行的时间

(2)assert一般是在Debug中使用在Release中选择禁用assert,这样在Debug版本中有利于程序员排查问题,在Release版本中不影响用户使用程序的效率。

4.指针的使用和传值调用

4.1 库函数strlen的实现

作用:求字符串的长度。代码如下:

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; }

4.2 传值调用和传址调用

为了区分传值调用和传址调用,简写交换整型变量的函数便于理解:

传值调用代码:

#include<stdio.h> void Swap1(int x,int y) { int tmp = x; x = y; y = tmp; } int main() { int a = 10; int b = 20; Swap1(a,b); printf("%d %d",a,b);//a,b并未被交换 return 0;

传址调用代码:

#include<stdio.h> void Swap2(int* x,int* y) { int tmp = *x; *x = *y; *y = tmp; } int main() { int a = 10; int b = 20; Swap2(&a,&b); printf("%d %d",a,b);//a,b的值成功被交换 return 0; }

由此我们可知,实参传递给形参的时候,形参会单独创建一份临时空间变量来接受实参,对形参的修改不影响实参。

这是Swap1失败的原因,在上述函数中,只能使用传址调用。

结论:传址调用可以让函数和主调函数之间建立真正的联系,在函数内部可以修改主调函数的变量;所以函数只需要主调函数中的变量值来进行计算,就可以使用传值调用;如果函数内部要修改主调函数中的变量值,就需要使用传址调用。

最后,感谢阅读,欢迎大家交流与指正。

http://www.cnnetsun.cn/news/4106183.html

相关文章:

  • 线性可调双路输出电源:原理、设计与噪声抑制全解析
  • 当“让用户满意”变成“让用户更不满意”:AI防御误触发的根源与破解之道
  • OptiCommPy模拟光马赫-曾德尔调制器
  • CAD绘图效率提升:从练习图29拆解系统绘图流程与高效命令组合
  • snpe-VGG案例(uv环境-全流程 教程)
  • 实测通勤15分钟出片全流程,不用电脑不用大内存手机
  • 基于树莓派的智能交互装置:从硬件搭建到AI对话引擎实现
  • 企业信息管理:从“罗生门”到一致性回应的体系构建
  • OPOR-Bench: Evaluating Large Language Models on Online Public Opinion Report Generation
  • AE字体动画核心技巧:从基础动画器到批量预设应用
  • 2026毕业论文AI生成工具打分:6款谁更靠谱
  • 百度网盘Mac版免费提速完整方案:1个开源插件解锁SVIP高速下载(附避坑指南)
  • 使用CD4051实现8路模拟信号复用:硬件设计与Arduino编程指南
  • 用Python打造智能双模机械键盘:从硬件焊接、CircuitPython编程到高级功能实现
  • 基于世界模型与持续学习的自适应机器人系统构建指南
  • meteor-collection-hooks教程:remove钩子实现级联删除的完整指南
  • 基于RP2040与MicroPython的Pi Day灯光项目:从硬件选型到交互实现
  • 基于UNIHIKER K10的财富雨互动游戏开发:触摸、声音与姿态控制
  • OneNote 页面编号终极指南:用 OneMore 三步搭出专业级文档结构
  • G-Helper 上手指南:用一款开源轻量工具,让华硕笔记本性能、静音与续航兼得
  • Autojs基础-Shell命令(shell)
  • 在ARM和国产芯片上部署容器化项目总是失败?这份跨架构实战指南帮你一次过关
  • STM32CubeIDE与RT-Spark开发板:从零实现LED闪烁的完整指南
  • 基于MAX7219与MD_Parola库实现多块8x8 LED点阵屏文字滚动
  • CentOS7 tar包安装mysql8.0
  • Habitat-Sim 从零到实战:具身AI 3D仿真器的完整部署与应用指南
  • 免费开源AI修图工具IOPaint:三步抹掉照片中的路人、水印和文字
  • STA静态时序检查笔记整理
  • BenchmarkTools.jl 可视化:用 BenchmarkPlots 让性能结果一目了然
  • Kindle漫画转换工具KCC完整指南:6个步骤让整卷漫画在电子墨水屏上完美呈现