耕耘 :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=#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,收藏是复活甲,下次更新记得来取哦
完.