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

C语言——深度理解指针(2)

耕耘 :C、C++、嵌入式技术领域

🔥我的个人主页

❄️个人专栏:《C语言专栏》 《嵌入式专栏》

✨**不要等待机会,而要创造机会!**✨

📽博主简介:

✨✨一位热爱生活的阳光大男孩.✨✨

前言

本文系统梳理了C语言指针编程中的几个关键概念与实用技巧。首先,详细解析了const修饰指针的四种不同形式及其对指针变量和指向内容的约束。其次,深入探讨了野指针的成因(未初始化、越界访问、空间释放)及其规避方法。接着,介绍了assert断言宏在调试中的作用与用法。最后,通过模拟实现strlen函数和对比传值调用与传址调用,阐述了指针在函数参数传递中的核心价值。全文配有丰富的代码示例,旨在帮助读者巩固对指针安全、高效使用的理解。

文章目录

  • 前言
  • 1. const 修饰指针
    • 1.1 const修饰变量
    • 1.2 const 修饰指针变量
  • 2. 野指针
    • 2.1 野指针成因
      • 1. 指针未初始化
      • 2. 指针越界访问
      • 3. 指针指向的空间释放
    • 2.2 如何规避野指针
      • 2.2.1 指针初始化
      • 2.2.2 ⼩⼼指针越界
      • 2.2.3可以NULL判断是否是野指针
  • 3. assert 断⾔
  • 4. 指针的使⽤和传址调⽤
    • 4.1 strlen的模拟实现
    • 4.2 传值调⽤和传址调⽤
  • 结语

1. const 修饰指针

1.1 const修饰变量

const用于声明一个常量,其值在初始化后不可修改。

#include<stdio.h>intmain(){intm=0;m=20;//m是可以修改的constintn=0;//把变量改为了变常量n=20;//n是不能被修改的return0;}

被const修饰的n不可以被修改,但是我们可以绕过n,使⽤n的地址,去修改n就能做到了,虽然这样做是在打破语法规则

#include<stdio.h>intmain(){constintn=0;printf("原始值n = %d\n",n);int*p=&n;*p=20;//通过地址可以进行修改n的值printf("修改后n = %d\n",n);return0;}

1.2 const 修饰指针变量

为了限制上面的方法,我们可以通过const 修饰指针变量来实现。
注意:const可以放在的左边,也可以放在的右边,但意义是不⼀样的。

intconst*p;//const 放在*的左边做修饰constint*p;//const 放在*的左边做修饰//这两种书写方法都正确int*constp;//const 放在*的右边做修饰

*const放在*的左边做修饰,p不可以被修改,p可以被修改
(指针变量指向的内容不可以改,但是指针本身可以改。注意:只可以改变p,但通过 p 不能修改内容)
const放在*的左边做修饰,p可以被修改,p不可以被修改
(指针变量指向的内容可以改,但是指针本身不可以改。可以通过修改p的值来修改n的值)
**const放在
的左,右两边做修饰,p、p都不可以被修改

#include<stdio.h>//代码1 - 测试⽆const修饰的情况voidtest1(){intn=10;intm=20;int*p=&n;*p=20;//ok?p=&m;//ok?}//代码2 - 测试const放在*的左边情况voidtest2(){intn=10;intm=20;constint*p=&n;*p=20;//ok?//答案是不可以p=&m;//ok?//可以}//代码3 - 测试const放在*的右边情况voidtest3(){intn=10;intm=20;int*constp=&n;*p=20;//ok?//可以p=&m;//ok?//不可以}//代码4 - 测试*的左右两边都有constvoidtest4(){intn=10;intm=20;intconst*constp=&n;*p=20;//ok?//不可以p=&m;//ok?//不可以}intmain(){//测试⽆const修饰的情况test1();//测试const放在*的左边情况test2();//测试const放在*的右边情况test3();//测试*的左右两边都有consttest4();return0;}

2. 野指针

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

2.1 野指针成因

1. 指针未初始化

#include<stdio.h>intmain(){int*p;//局部变量指针未初始化,默认为随机值return0;}

2. 指针越界访问

#include<stdio.h>intmain(){intarr[10]={0};int*p=&arr[0];inti=0;for(i=0;i<=11;i++)//这里他会运行11次{//当指针指向的范围超出数组arr的范围时,p就是野指针*(p++)=i;}return0;}

3. 指针指向的空间释放

#include<stdio.h>int*test(){intn=100;return&n;}//n是一个局部变量,当出了这个函数,n的地址就还给操作系统了intmain(){int*p=test();printf("%d\n",*p);return0;}

2.2 如何规避野指针

2.2.1 指针初始化

如果明确知道指针指向哪⾥就直接赋值地址,如果不知道指针应该指向哪⾥,可以给指针赋值NULL.NULL 是C语⾔中定义的⼀个标识符常量,值是0,0也是地址,这个地址是⽆法使⽤的,读写该地址会报错

intmain(){intnum=10;int*p1=&num;int*p2=NULL;return0;}

2.2.2 ⼩⼼指针越界

⼀个程序向内存申请了哪些空间,通过指针也就只能访问哪些空间,不能超出范围访问,超出了就是越界访问

2.2.3可以NULL判断是否是野指针

intmian(){intn=10;int*p=&n;.....if(P!=NULL)//这一步可以判断p经过上面一系列操作后,是否变成了野指针,不是就可以进行下一步,加强了程序的安全性。{....}return0}

3. assert 断⾔

assert.h 头⽂件定义了宏 assert(),⽤于在运⾏时确保程序符合指定条件,如果不符合,就报错终⽌运⾏。这个宏常常被称为“断⾔”。

assert(p!=NULL);//如果括号里面的程序不成立,程序就会报错终止

如果已经确认程序没有问题,不需要再做断⾔,就在 #include <assert.h> 语句的前⾯,定义⼀个宏NDEBUG

#defineNDEBUG//它的作用,就是使下面所有的assert失去作用//还想用assert的话,将这行注释掉就行#include<assert.h>intmain(){assert(....);assert(....);......assert(....);assert(....);......return0;}

assert() 有一个小缺点就是,因为引⼊了额外的检查,增加了程序的运⾏时间

4. 指针的使⽤和传址调⽤

4.1 strlen的模拟实现

strlen的功能是求字符串⻓度,统计的是字符串中 \0 之前的字符的个数

intmy_strlen(constchar*str){intcount=0;assert(str);//判读,过来的值是否为真,相当于加了一个保障while(*str){count++;str++;}returncount;}intmain(){intlen=my_strlen("abcdef");printf("%d\n",len);return0;}

4.2 传值调⽤和传址调⽤

写⼀个函数,交换两个整型变量的值

#include<stdio.h>voidSwap1(intx,inty){inttmp=x;x=y;y=tmp;}intmain(){inta=0;intb=0;scanf_s("%d %d",&a,&b);printf("交换前:a=%d b=%d\n",a,b);Swap1(a,b);// 这就是传值调⽤printf("交换后:a=%d b=%d\n",a,b);return0;}


那为什么没有交换成功呢,是因为函数Swap1是值传递,就是把a,b的值复制一份给x,y,而函数内部交换的只是副本x、y,main里面的a、b完全不受影响。实参传递给形参的时候,形参会单独创建⼀份临时空间来接收实参,对形参的修改不影响实参。所以Swap1是失败的了
简单来说就是函数和主函数之间没有联系。

那怎么办呢?我们可以用指针来构建联系。

#include<stdio.h>voidSwap2(int*px,int*py){inttmp=0;tmp=*px;*px=*py;*py=tmp;}intmain(){inta=0;intb=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);return0;}

传址调用,它的原理就是通过指针,让函数和主函数构建真正的联系,在函数内部可以修改主调函数中的变量。

结语

“别走!你的三连还在我这儿寄存着呢~点赞是门票,投币是VIP,收藏是复活甲,下次更新记得来取哦
完.

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

相关文章:

  • 车企海外研发中心战略解析:从本地化适配到全球化研发体系重构
  • 长期智能体记忆管理:基于类型化表示解决来源-角色混淆
  • 特斯拉智能百叶窗HVAC系统:软件定义汽车座舱环境控制新范式
  • 什么是 RAG 中的分块?为什么需要分块?
  • Axure 汉化原来这么简单:axure-cn 中文语言包 9/10/11 全版本速通指南
  • 锤子助手第124个开关:启用自动下载图片的位置、安全验证与图片隐私边界
  • Capture软件原理图信号联通笔记
  • 电脑掌柜库存管理实战:进销存、库存预警、供应商管理一条龙,0基础电脑店老板 5 分钟上手
  • Unity独立开发艺术展馆漫游:从基础功能到工程化实战
  • XMC1300 ADC读数不稳?从硬件到软件的完整排查与优化指南
  • Python自动化:基于文件名与正则表达式批量分类PDF发票文件
  • 计算机单片机毕设实战-基于 STM32 单片机的防干烧多模式烧水控制系统设计 基于 STM32 的自动手动双模式智能出水装置设计(012104)
  • 构建自主AI智能体的因果推理引擎:反事实思考与时间一致性
  • 算法竞赛制胜关键:构建高效数据结构工具箱,实现降维打击
  • 调度器多副本,不引 ZooKeeper:DB CAS + slot 分片就够了
  • RTL8720DN双模物联网SoC开发:从硬件架构到低功耗实战
  • 脑机接口实战:用Python实现脑电波意念控制与信号处理
  • Sib:用Git版本控制管理AI对话历史的命令行LLM客户端
  • 1.6T光模块:技术演进、市场现状与工程挑战深度解析
  • 【YOLO26创新改进】TIP顶刊 2023 | Conv创新改进篇 | 利用 CSFCN 上下文与空间特征校准网络,使网络能够获得更准确的语义信息,适合目标检测、语义分割、图像分割任务,高效涨点
  • TrenchesWIP:一战战术射击游戏入门指南与BOT对战技巧
  • 信息技术EI期刊发表全攻略:从选刊到检索的实战指南
  • B站视频下载终极指南:免费开源工具一键保存4K大会员与充电专属视频
  • 华为认证高频易错题库解析:攻克IP计算、网络设备与协议核心难点
  • Python实现LSTM时间序列预测教程
  • 让2011年的老Mac跑上macOS Sequoia:OpenCore Legacy Patcher 一次点亮的完整上手指南
  • 电视浏览器(CCTV_Viewer):观看央视卫视直播的安卓 TV 盒子
  • Go源码分析:Mutex与读写锁实现
  • AI如何从混沌中看见世界?解析非结构化数据处理的算法演进与工程实践
  • 保时捷Taycan核心技术解析:800V架构与两速变速箱如何重塑电动性能标杆