news 2026/8/20 14:47:06

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

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
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,收藏是复活甲,下次更新记得来取哦
完.

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/8/20 14:46:05

车企海外研发中心战略解析:从本地化适配到全球化研发体系重构

1. 从一则新闻说起&#xff1a;车企海外研发中心的“冷思考”今天早上&#xff0c;一则关于宝沃汽车在北美成立研发中心&#xff0c;并由杨嵩再度挂帅的新闻&#xff0c;在行业圈子里激起了一些讨论。新闻本身不长&#xff0c;信息量也有限&#xff0c;但背后折射出的&#xff…

作者头像 李华
网站建设 2026/8/20 14:46:03

长期智能体记忆管理:基于类型化表示解决来源-角色混淆

1. 项目概述&#xff1a;当长期智能体“记混了”自己的身份 最近在折腾一些需要长期运行的智能体项目&#xff0c;比如自动化的客服助手、持续学习的文档分析工具&#xff0c;或者能陪你打游戏、管理日程的“数字伙伴”。这些智能体不像一次性的问答机器人&#xff0c;它们需要…

作者头像 李华
网站建设 2026/8/20 14:45:09

特斯拉智能百叶窗HVAC系统:软件定义汽车座舱环境控制新范式

1. 从专利到产品&#xff1a;特斯拉HVAC系统革新的行业背景 最近&#xff0c;特斯拉一项关于“空调百叶窗”的新HVAC&#xff08;暖通空调&#xff09;系统专利被曝光&#xff0c;再次引发了汽车圈和科技圈的热议。对于很多普通车主来说&#xff0c;这可能只是一个关于“空调出…

作者头像 李华
网站建设 2026/8/20 14:42:25

什么是 RAG 中的分块?为什么需要分块?

RAG中的分块&#xff1a;核心概念与必要性 分块&#xff08;Chunking&#xff09;是RAG系统中将长文档切分成多个小片段的过程。每个片段称为一个"块"&#xff08;Chunk&#xff09;&#xff0c;是后续向量化和检索的基本单元。一、什么是分块&#xff1f; 1.1 直观理…

作者头像 李华