news 2026/5/8 9:46:37

C语言指针深入浅出2

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
C语言指针深入浅出2

目录

  • 1.const 修饰指针
    • 1.1 const修饰变量
    • 1.2 const 修饰指针变量
  • 2.野指针
    • 2.1 野指针的成因
    • 2.2 如何规避野指针
      • 2.2.1 指针初始化
      • 2.2.2. 小心指针越界
      • 2.2.3 指针变量不再使用时,要及时的置为NULL,指针在使用前要检查有效性
      • 2.2.4 避免返回局部变量的地址
  • 3. assert 断言
  • 4 指针的使用和传址调用
    • 4.1 strlen 的模拟实现
      • 4.1.1 讲解算法原理
      • 4.1.2 代码实现
    • 4.2 传值调用和传址调用
      • 4.2.1 传值调用
      • 4.2.2 传址调用

1.const 修饰指针

1.1 const修饰变量

如果给变量加上一些限制,使得变量的值被限制,不能被修改,这时候,const就起作用了,const是常属性的,不能被修改的

intmain(){intm=0;m=100;//变量m 的值可以修改constintn=0;n=100;//变量n的之不可以修改return0;}

但是,如果我们得到了n的地址,绕过n,去修改n就可以了,这样做不推荐(打破了语法规则)。

intmain(){constintn=0;//常变量 - 具有常属性的变量(不能被修改了)骨子里还是一个变量int*p=&n;//取出n的地址,将n的地址赋值给指针变量p*p=20;// 通过解引用p,访问n,并将n的值修改为 20printf("%d\n",n);return0;}

输出结果:

n被const修饰,就是为了不被修改,如果拿到了p的地址就能修改n,这样就打破了const的限制,是不合理的,下面我们来结合图片更好的解释一下:


1.2 const 修饰指针变量

一般来讲,const修饰指针变量,可以放在*的左边,也可以放在*的右边,意义是不一样的。

int*p;int*constp;// const 放在 * 的右边intconst*p// const 放在 * 的左边

下面,我们通过代码来具体分析一下:

  • 代码1 - 测试无 const 修饰的情况
intmain(){intn=20;int*p=&n;*p=50;printf("%d\n",n);return0;}

这段代码中通过指针变量来访问n,并对n的值进行修改,再无const的修饰下,这段代码是正确的。

  • 代码2 - 测试 const 放在 * 的左边的时候
intmain(){intn=101;inta=100;constint*pa=&n;//const放在*的左边*pa=100;//errorprintf("%d\n",*pa);return0;}


这段代码中,const 放在 * 的左边,表示不能通过指针 pa 修改它所指向的内容。因此*pa = 100; 会报错。但 pa 本身不是常量指针,它仍然可以重新指向其他变量。

  • 代码3 - 测试 const 放在 * 右边的情况
intmain(){inta=10;intb=20;int*constpa=&a;pa=&b;//errorprintf("%d\n",*pa);return0;}


这段代码中,const 放在 * 的右边,表示 pa 是常量指针,pa 的指向不能被修改。因此pa = &b;会报错。但可以通过 *pa 修改它所指向对象中的内容。

  • 代码4 - 测试const 同时放在 * 的左右两边
intmain(){intn=100;inta=50;constint*constpa=&n;pa=&a;//error*pa=100;//errorreturn0;}

const 同时放在 * 的左边和右边,左边的 const 限制 *pa,表示不能通过 pa 修改指向的内容;右边的 const 限制 pa,表示 pa 的指向不能改变。因此pa = &a;*pa = 100;都会报错。

结论:

const修饰指针变量
放在 * 的左边:const修饰的指针变量指向的内容,不能通过指针变量修改它指向的内容,但是指针变量的指向是可以修改的。
放在 * 的右边:const修饰的指针变量本身,指针变量的指向是不能修改的,但是指针指向的内容,是可以通过指针来改变的。


2.野指针

野指针就是指针指向的位置是不可知的(随机的、不正确的、没有明确限制的),也就是指针指向的内容不属于当前程序。

2.1 野指针的成因

  1. 指针未初始化
intmain(){intnum=10;int*p;//局部指针变量未初始化,默认为随机值*p=20;// error//*p就是野指针了return0;}
  1. 指针越界访问
intmain(){intarr[10]={1,2,3,4,5,6,7,8,9,10};int*p=&arr[0];//正常打印数组的下标为0 ~ 9for(inti=0;i<=11;i++){//当指针指向的范围超出数组arr的范围时,p就是野指针printf("%d ",*(p+i));//i 就是偏移量}return0;}


3. 指针指向的内存空间释放了

int*test(){intm=100;return&m;//返回指针变量的类型是int*}intmain(){int*p=test();printf("%d\n",*p);//指针变量p就是野指针return0;}

这段代码中,test()函数返回了局部变量m的地址。
m是局部变量,存放在栈区,只在test()函数执行期间有效。
test()执行结束后,m的生命周期结束,它所在的栈空间不再有效。
此时main()中的指针p虽然保存了这个地址,但该地址已经不可靠,p成为悬空指针
继续使用*p访问该地址属于未定义行为,可能输出旧值、随机值,也可能导致程序异常。

由于这里用到了函数的知识点,我在之前详细讲解了函数相关知识点,相关链接:C语言函数


2.2 如何规避野指针

2.2.1 指针初始化

如果已经知道它要指向哪个变量,就让它保存那个变量的地址。如果暂时不知道它要指向哪里,就先赋值为NULLNULL在C语言中是一个标识符常量,NULL表示“什么也不指向”,它的值是0。这个地址不能被访问,如果对NULL指针进行读写,程序就会出错。

//NULL的定义#ifndefNULL#ifdef__cplusplus#defineNULL0#else#defineNULL((void*)0)#endif#endif
intmain(){intnum=0;int*p=NULL;//不知道p指向哪个地址,就可以将p这个指针变量变为NULL//NULL 专门用来初始化指针变量,初始化为NULL的指针变量,意味着:这个指针没有指向有效的空间,我们暂时不能使用它printf("%d\n",num);return0;}

2.2.2. 小心指针越界

指针只能访问程序允许访问的内存空间。如果访问了范围之外的内存,就是指针越界。指针越界可能导致数据错误、程序异常,甚至崩溃

2.2.3 指针变量不再使用时,要及时的置为NULL,指针在使用前要检查有效性

指针不用时,要及时赋值为NULL。这样可以表示这个指针暂时不指向任何有效空间,避免它继续乱访问内存。使用指针之前,也要先判断它是不是NULL。如果是NULL,就不要使用;如果不是NULL,再继续访问。

intmain(){intarr[10]={1,2,3,4,5,6,7,8,9,10};int*p=&arr[0];inti=0;for(i=0;i<10;i++){*(p++)=i;}//此时p已经越界了,可以把p置为NULLp=NULL;//下次使⽤的时候,判断p不为NULL的时候再使⽤//...p=&arr[0];//重新让p获得地址if(p!=NULL)//使用之前检查有效性{//...}return0;}

2.2.4 避免返回局部变量的地址

如上述野指针成因的第三个例子,不要返回局部变量的地址。


3. assert 断言

assert.h头文件中定义了宏assert(),assert是一种检查工具,用来判断程序运行时是否满足指定田间。如果条件成立,程序继续运行;如果条件不成立,程序就会报错并停止,这个宏常常被称为断言
assert函数原型:

assert(p != NULL);用来检查指针p是否为空。

如果p不等于NULL,程序继续运行;
如果p等于NULL,程序会报错并终止。

assert()的作用

assert()接收一个表达式作为判断条件:

  • 条件为真:程序继续运行
  • 条件为假:程序报错并终止

同时,程序还会提示出错的文件名和行号,方便我们定位问题。

禁用assert()

assert()常用于调试阶段。
如果不想启用断言,可以在包含头文件前定义NDEBUG

#defineNDEBUG#include<assert.h>

定义NDEBUG后,程序中的assert()语句会被禁用。

#defineNDEBUG//禁用assert断言#include<assert.h>//使用assert要包含头文件assert.hintmain(){intm=100;int*p=NULL;//指针变量的初始化assert(p!=NULL);//禁用后,assert断言就失效了printf("%d\n",m);return0;}

一般建议:

  • Debug版本中使用assert()检查问题
  • Release版本中禁用assert(),避免影响程序运行效率

总结

assert()是调试用的检查工具,条件不成立就报错终止,发布版本一般关闭。


4 指针的使用和传址调用

4.1 strlen 的模拟实现

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

size_tstrlen(constchar*str)

4.1.1 讲解算法原理

strlen的作用就是计算字符串有多少个字符。它从字符串的第一个字符开始,一个一个往后数。只要当前字符不是'\0',计数器就加 1。当遇到'\0'时,说明字符串结束,停止计数。最后返回计数器的值,就是字符串长度。

4.1.2 代码实现

//这里的const用来修饰*p , *p 时所指向对象的内容不能进行修改size_tmy_strlen(constchar*p)//指针变量p接收的是数组arr首元素的地址{size_tcount=0;//用来计数assert(p!=NULL);//检查p是否为空指针while(*p!='\0'){count++;p++;}p=NULL;// p 是局部指针变量,函数结束后会自动销毁returncount;}intmain(){chararr[]="abcdef";size_tlen=my_strlen(arr);//数组名是数组首元素的地址 arr == &arr[0]printf("%zu\n",len);return0;}

4.2 传值调用和传址调用

4.2.1 传值调用

举一个例子:写一个函数,交换两个整型变量的值。

voidswap1(intx,inty){inttmp=x;x=y;y=tmp;}intmain(){inta=10;intb=20;printf("交换前:a = %d b = %d\n",a,b);swap1(a,b);printf("交换后:a = %d b = %d\n",a,b);return0;}


我们发现结果有问题,这时候我们就可以进行调试,如果忘了调试相关技巧,可以看我之前讲解的,相关链接:VS实用调试技巧。

main函数中,创建了变量ab

  • a = 10
  • b = 20

其中:

  • a的地址是0x0000004da5d5fa84
  • b的地址是0x0000004da5d5faa4

调用Swap1(a, b)时,采用的是传值调用

也就是说,程序只是把ab的值分别复制给形参xy

  • a的值复制给x
  • b的值复制给y

Swap1函数中:

  • x的地址是0x0000004da5d5fa60
  • y的地址是0x0000004da5d5fa68

可以看出,xyab的地址并不相同。

因此,xy是函数内部新创建的变量,和main函数中的ab不是同一块内存空间。所以在Swap1函数中交换的是xy的值,不会影响main函数中的ab

函数调用结束后:

  • a仍然是10
  • b仍然是20

    结论:实参传递给形参时,形参是实参的一份临时拷贝,对形参的修改,是不影响实参的。

4.2.2 传址调用

voidswap2(int*p1,int*p2){inttmp=*p1;// tmp = p1*p1=*p2;// p1 = p2*p2=tmp;// p2 = tmp}intmain(){inta=10;intb=20;printf("交换前:a = %d b = %d\n",a,b);swap2(&a,&b);printf("交换后:a = %d b = %d\n",a,b);return0;}


调用Swap2(&a, &b)时,传入的是变量ab的地址。

函数中的指针p1p2分别指向ab,所以可以通过*p1*p2直接修改main函数中的变量值。

因此,交换后:

  • a = 20
  • b = 10

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

别再只会画折线图了!用Qt Charts搞定5种实用图表(附完整C++源码)

用Qt Charts打造专业级数据可视化仪表盘&#xff1a;5种图表实战集成 在数据驱动的时代&#xff0c;如何将枯燥的数字转化为直观的视觉呈现&#xff0c;是每个开发者都需要掌握的技能。Qt Charts作为Qt官方提供的图表模块&#xff0c;为C开发者提供了一套强大而灵活的数据可视化…

作者头像 李华
网站建设 2026/5/8 9:32:24

多模态大语言模型的跨模态挑战与优化实践

1. 多模态大语言模型的跨模态挑战现状当我们在手机上同时看到图片和文字描述时&#xff0c;大脑能瞬间理解两者的关联。但让AI系统做到这一点却异常困难——这正是多模态大语言模型&#xff08;MLLM&#xff09;面临的核心挑战。去年调试CLIP模型时&#xff0c;我遇到过这样一个…

作者头像 李华
网站建设 2026/5/8 9:31:06

为AI智能体赋能视觉:zeuxis本地截图服务器的MCP协议实践

1. 项目概述&#xff1a;为AI智能体装上“眼睛”的本地截图服务器 如果你正在开发或使用基于MCP&#xff08;Model Context Protocol&#xff09;的AI智能体&#xff0c;并且希望它能“看见”你屏幕上的内容&#xff0c;那么 zeuxis 这个工具绝对值得你深入了解。简单来说&am…

作者头像 李华