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

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

1. 字符指针变量

在指针的类型中我们知道有⼀种指针类型为字符指针 char*
⼀般使⽤:

intmain(){charch='w';char*pc=&ch;*pc='w';return0;}

还有⼀种使⽤⽅式如下:

intmain(){char*pstr="hello.";//这⾥是把⼀个字符串放到pstr指针变量⾥了吗?不是,放的是h的地址printf("%s\n",pstr);//%s打印的是字符串,当拿到首元素的地址的时候,会依次往后打印,直到遇到\0printf("%c\n",*pstr);//这才是打印一个字符return0;}

上面代码中是把⼀个字符串放到pstr指针变量⾥了吗?其实不是的,他的本质是把字符串hello⾸字符h的地址放到了pstr中。

下面我们看一段有趣的代码:判断str1和str2、str3和str4是否相同

#include<stdio.h>intmain(){charstr1[]="hello";charstr2[]="hello";constchar*str3="hello";constchar*str4="hello";if(str1==str2)printf("str1 and str2 are same\n");elseprintf("str1 and str2 are not same\n");if(str3==str4)printf("str3 and str4 are same\n");elseprintf("str3 and str4 are not same\n");return0;}

答案是str1和str2不相同、str3和str4相同。原因是这⾥str3和str4指向的是⼀个同⼀个常量字符串C/C++会把常量字符串存储到单独的⼀个内存区域,当⼏个指针指向同⼀个字符串的时候,他们实际会指向同⼀块内存。但是⽤相同的常量字符串去初始化不同的数组的时候就会开辟出不同的内存块。所以str1和str2不同,str3和str4相同

2. 数组指针变量

2.1 数组指针变量是什么?

之前我们学习了指针数组,指针数组是⼀种数组,数组中存放的是地址(指针)。
我们已经熟悉:
• 整形指针变量: int * pint; 存放的是整形变量的地址,能够指向整形数据的指针。
• 浮点型指针变量: float * pf; 存放浮点型变量的地址,能够指向浮点型数据的指针。
• 同理数组指针变量应该是:存放的应该是数组的地址,能够指向数组的指针变量

数组指针变量

int(*p)[10]

解释: p 先和 * 结合,说明 p 是⼀个指针变量,然后指针指向的是⼀个⼤⼩为10个整型的数组。所以 p 是⼀个指针,指向⼀个数组,叫 数组指针
注意不可以写成下面的这种情况:

int*p[10]//这表示一个指针数组

**intp[10] 与 int (p)[10] 的区别
int *p[10]:指针数组,10 个指针元素。
int (*p)[10]:数组指针,一个指针指向包含 10 个 int 的数组
这⾥要注意: [] 的优先级要⾼于 * 号的,所以必须加上 () 来保证 p 先和 * 结合。

2.2 数组指针变量怎么初始化

数组指针变量是⽤来存放数组地址的,那数组地址是什么呢?就是上一篇我们说的**&数组名**

intarr[10]={0};&arr;//得到的就是数组的地址int(*p)[10]=&arr;//这就是数组指针变量的初始化

数组指针类型解析:

理解指针类型
int* p的指针类型:int*
intarr[10]指针类型:int[10]
同理数组指针变量:int(p)[10]的指针类型就是:int()[10]
总结:对于简单的指针声明来说,去掉指针变量的函数名,剩下的就是指针类型

3. ⼆维数组传参的本质

⼆维数组的需要传参给⼀个函数的时候,我们之前这样写的:

#include<stdio.h>voidtest(inta[3][5],intr,intc)//这里的形参,是数组类型{inti=0;intj=0;for(i=0;i<r;i++){for(j=0;j<c;j++){printf("%d ",a[i][j]);}printf("\n");}}intmain(){intarr[3][5]={{1,2,3,4,5},{2,3,4,5,6},{3,4,5,6,7}};test(arr,3,5);return0;}

这⾥实参是⼆维数组,形参也写成⼆维数组的形式,那还有什么其他的写法吗?
⾸先我们再次理解⼀下⼆维数组,⼆维数组其实可以看做是每个元素是⼀维数组的数组,也就是⼆维
数组的每个元素是⼀个⼀维数组
。那么⼆维数组的⾸元素就是第⼀⾏,是个⼀维数组。
如下图:

所以,根据数组名是数组⾸元素的地址这个规则,⼆维数组的数组名表⽰的就是第⼀⾏的地址,是⼀
维数组的地址。根据上⾯的例⼦,第⼀⾏的⼀维数组的类型就是 int [5] ,所以第⼀⾏的地址的类
型就是数组指针类型int(*)[5]。那就意味着⼆维数组传参本质上也是传递了地址,传递的是第⼀
⾏这个⼀维数组的地址,那么形参也是可以写成指针形式的。如下:

#include<stdio.h>voidtest(int(*p)[5],intr,intc)//这里的形参,是指针类型{inti=0;intj=0;for(i=0;i<r;i++){for(j=0;j<c;j++){printf("%d ",*(*(p+i)+j));//(p + i)相当于跳过一行,而+j相当于在每一行中依次向后移动一个元素}printf("\n");}}intmain(){intarr[3][5]={{1,2,3,4,5},{2,3,4,5,6},{3,4,5,6,7}};test(arr,3,5);return0;}

总结:⼆维数组传参,形参的部分可以写成数组,也可以写成指针形式。

4. 函数指针变量

4.1 函数指针变量的创建

什么是函数指针变量呢?
用前面指针变量的类比来说,函数指针变量就是⽤来存放函数地址的,未来通过地址能够调⽤函数的。
且和数组一样,函数名就是函数的地址,当然也可以通过 &函数名 的⽅式获得函数的地址。

如果我们要将函数的地址存放起来,就得创建函数指针变量咯,函数指针变量的写法其实和数组指针
⾮常类似。如下:

voidtest(){printf("hehe\n");}void(*pf1)()=&test;void(*pf2)()=test;intAdd(intx,inty){returnx+y;}int(*pf3)(int,int)=Add;int(*pf3)(intx,inty)=&Add;/

函数指针类型解析:

4.2 函数指针变量的使⽤

通过函数指针调⽤指针指向的函数。

#include<stdio.h>intAdd(intx,inty){returnx+y;}intmain(){int(*pf3)(int,int)=Add;printf("%d\n",(*pf3)(2,3));printf("%d\n",pf3(3,5));//pf3和(*pf3)等价return0;}

当使用(*pf3)时,解引用操作得到的仍然是函数地址(即pf3本身),因此两种写法最终都指向同一个函数入口

4.3 两段有趣的代码

(1)、

(*(void(*)())0)();

1.上述代码其实是⼀次函数调⽤,调⽤的是0地址处的⼀个函数,这个函数没有参数,没有返回值。
2. 代码中的 void ()() 是函数指针类型, (void ()())0 类型放在括号中意思是强制类型转化,是将0这个整型值,强制类型转化成这种函数指针类型,也就是说0被当做函数的地址了。
3.(void ()())0 ,前⾯加⼀个 * ,就是调⽤0地址处的这个函数,根据函数指针的类型能知道,这个函数没有参数,也没有返回值。

(2)、

void(*signal(int,void(*)(int)))(int)

1.上述代码是⼀次函数的声明。
2. 声明的函数名字叫 signal ,函数的参数有2个,第⼀个是int类型,第⼆个是函数指针类型: void()(int) 。 signal 函数的返回值类型也是函数指针类型 void()(int)

4.3.1 typedef 关键字

typedef 是⽤来类型重命名的,可以将复杂的类型,简单化。
⽐如,你觉得 unsigned int 写起来不⽅便,如果能写成 uint 就⽅便多了,那么我们可以使⽤:

typedefunsignedintuint;//将unsigned int 重命名为uint

而且指针也是可以类型重命名的,按照上面的类比,将 int* 重命名为 ptr_t ,可以这样写:

typedefint*ptr_t

但是对于数组指针和函数指针稍微有点区别:
⽐如我们有数组指针类型int(*)[5] ,需要重命名为 parr_t,要这样写:

typedefint(*parr_t)[5];//新的类型名必须在*的右边typedefint(*)[5]parr_t//这种写法是错误的

函数指针类型的重命名也是⼀样的,⽐如,将 void(*)(int) 类型重命名为 pf_t,就可以这样写:

typedefvoid(*pfun_t)(int);//新的类型名必须在*的右边

5. 函数指针数组

把函数的地址存到⼀个数组中,那这个数组就叫函数指针数组,那函数指针的数组如何定义呢?

int(*parr1[3])();int*parr2[3]();int(*)()parr3[3];int(*p[4])(intx,inty)={add,sub,mul,div};//p[4]就是我们定义的数组,后面(int x, int y)就是我们要给函数的传参值,而{ add, sub, mul, div }里面放的就是数组名

6. 转移表

计算器的⼀般实现

#include<stdio.h>intadd(inta,intb)//算加法{returna+b;}intsub(inta,intb)//算减法{returna-b;}intmul(inta,intb)//算乘法{returna*b;}intdiv(inta,intb)//算除法{returna/b;}intmain(){intx,y;intinput=1;intret=0;do{printf("*************************\n");//选择表打印printf(" 1:add 2:sub \n");printf(" 3:mul 4:div \n");printf(" 0:exit \n");printf("*************************\n");printf("请选择:");scanf_s("%d",&input);switch(input)//选择进行什么运算{case1:printf("输⼊操作数:");scanf_s("%d %d",&x,&y);//输入值ret=add(x,y);printf("ret = %d\n",ret);break;case2:printf("输⼊操作数:");scanf_s("%d %d",&x,&y);//输入值ret=sub(x,y);printf("ret = %d\n",ret);break;case3:printf("输⼊操作数:");scanf_s("%d %d",&x,&y);//输入值ret=mul(x,y);printf("ret = %d\n",ret);break;case4:printf("输⼊操作数:");scanf_s("%d %d",&x,&y);//输入值ret=div(x,y);printf("ret = %d\n",ret);break;case0:printf("退出程序\n");break;default:printf("选择错误\n");break;}}while(input);return0;}

我们发现case部分写的太麻烦了,好多内容都是相似的,可以化简

#include<stdio.h>intadd(inta,intb){returna+b;}intsub(inta,intb){returna-b;}intmul(inta,intb){returna*b;}intdiv(inta,intb){returna/b;}intmain(){intx,y;intinput=1;intret=0;int(*p[5])(intx,inty)={0,add,sub,mul,div};//转移表do{printf("*************************\n");//选择表打印printf(" 1:add 2:sub \n");printf(" 3:mul 4:div \n");printf(" 0:exit \n");//exit为退出程序printf("*************************\n");printf("请选择:");scanf("%d",&input);if((input<=4&&input>=1))//选择进行运算{printf("输⼊操作数:");scanf("%d %d",&x,&y);ret=(*p[input])(x,y);//*p[input])(x, y)通过函数地址来调用函数printf("ret = %d\n",ret);}elseif(input==0)//退出运算{printf("退出计算器\n");}else//输入错误{printf("输⼊有误\n");}}while(input);return0;}

完.

神秘力量觉醒:一键三连触发隐藏buff,博主可能突然闪现评论区对你发射爱心光波~

http://www.jsqmd.com/news/1311752/

相关文章:

  • Gemma 4推理加速:多令牌预测与推测解码技术详解
  • 桌面AI协作新范式:多智能体架构与系统集成实战
  • EvoLib:为LLM应用构建记忆与进化系统的开源框架
  • 基于 TRAE 的三相电参采样驱动模块设计(二)校准机制原理
  • 临平打井怎么选?2026年浙江本地钻井服务深度解析 - 优质品牌商家
  • 【FAST-LIO2】45°雷达坐标系对齐——输入端旋转方案的完整踩坑与修复
  • 2026 年兴宾比较好的建房用钢网定做厂家哪家可靠,别再乱选建房辅料!这玩意儿能省一半工时,师傅都偷偷用的好东西 - 领域鉴赏官
  • Arduino Nano专用Grove扩展板设计解析与实战应用指南
  • 2026 降AI率工具实测对比:不踩雷攻略,毕业季必备宝典
  • 树莓派驱动7.5英寸电子纸屏:低功耗信息站开发全攻略
  • Hadoop核心架构与集群搭建实战:从基础原理到环境部署
  • FeCoNiCrRu高熵氧化物3秒热冲击合成!AEM-LOM双机制协同、200 mV超低过电位、500小时超稳定析氧!
  • 10.5英寸HDMI AMOLED显示模组:从接口桥接到系统集成的技术解析
  • 流体机械设计核心:从叶轮、蜗壳原理到CFD仿真与工程实践
  • 从单打独斗到军团作战:QM 如何重新定义 AI Agent 的协作范式
  • Vue3 hooks 函数
  • 2026 年新发布:保山专业的高密度纤维水泥板厂商哪家权威,你以为只能用在工地?这玩意儿居然让家装师傅夸了一上午-欧拉德建材 - 实业推荐官【官方】
  • COCI字符串网格DP:BFS优化与状态转移实战解析
  • 佳能ix6780 ip8780 mg3580 mg3680 mg3620 mg3660 ts3380清零软件5B00,5B02,5B04,1700,1702,1704,P07,E08亲测完美。
  • PaperBanana:多智能体协作如何实现学术图表自动化生成与优化
  • 东莞智捷软件的后台门户网页对应的系统文档面向什么客户?
  • Token、上下文窗口、推理延迟:这三个词搞懂,你就入门了
  • AI助手Gemini在2026年预算规划中的十大应用场景与方法
  • 终极指南:如何为波斯语和阿拉伯语项目选择完美的开源字体解决方案
  • Vue3+ElementPlus前后端分离项目实战
  • 2026抖店无货源合规运营全攻略:避开90%商家踩中的风控红线,一套工具搞定铺货+自动履约 - 电商分享
  • 基于CH348L的8路USB转TTL串口扩展器设计全解析
  • vn.py 量化交易框架实践指南:从安装配置到 CTA 策略回测
  • mncli配ip踩坑日记
  • 从零理解数据维度:高维灾难、降维实战与业务应用全解析