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

C语言指针:从零掌握指针(4)

文章目录

  • C语言指针:从零掌握指针(4)
    • 前言
    • 一、字符指针变量
      • 总结:
    • 二、数组指针变量
      • 2.1 是什么?
      • 2.2 怎么初始化?
    • 三,二维数组传参的本质
      • 总结:
    • 四, 函数指针变量
      • 4.1 函数指针变量的创建
      • 4.2 函数指针变量的使用
      • 4.3 两个有趣的代码
      • 4.3.1 typedef关键字
        • 总结:
    • 五,函数指针数组
    • 六,转移表
      • 总结:

C语言指针:从零掌握指针(4)

前言

本篇我们学习:字符指针变量,数组指针变量,二维数组传参的本质,函数指针变量,函数指针数组和转移表

一、字符指针变量

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

intmain(){charch='w';char*p=&ch;printf("%c\n",*p);return0;}

还有一种使用方法如下:

intmain(){/*int arr[] = { 1,2,3,4,5,6 }; int * p = arr;*///p指向了arr数组,实际上是p指向了arr数组的首元素constchar*p="abcdef";//"abcdef"是一个常量字符串printf("%s\n",p);return0;}

代码const char * p = “abcdef”;不是把整个字符串放到了字符指针p里。本质上是把字符串的首字符的地址放到了p里

练习:

intmain(){charstr1[]="hello bit.";charstr2[]="hello bit.";constchar*str3="hello bit.";constchar*str4="hello bit.";if(str1==str2)printf("str1 and str2 are same\n");//1elseprintf("str1 and str2 are not same\n");//2if(str3==str4)printf("str3 and str4 are same\n");//3elseprintf("str3 and str4 are not same\n");//4return0;}

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

总结:

  • 字符串在表达式里,表达式的值是首字符的地址
  • 字符指针变量 = 存字符(或字符串首)地址的变量
  • 字符数组arr 是数组,内容可以改
    字符指针p 是指针,指向常量字符串,不能改内容

二、数组指针变量

2.1 是什么?

之前我们学习了指针数组,指针数组是⼀种数组,数组中存放的是地址(指针)。数组指针变量是指针变量?还是数组
答案是:指针变量-----存放的是数组的地址,能够指向数组的指针变量

2.2 怎么初始化?

数组指针变量是⽤来存放数组地址的,那怎么获得数组的地址呢?就是我们之前学习的 &数组名 。

intarr[10]={0};&arr;//得到的就是数组的地址

如果要存放个数组的地址,就得存放在数组指针变量中,如下:

int(*p)[10]=&arr;

三,二维数组传参的本质

有了数组指针的理解,我们就能够讲⼀下⼆维数组传参的本质了。过去我们有⼀个⼆维数组的需要传参给⼀个函数的时候,我们是这样写的:

voidPrint(intarr[3][5],intr,intc){inti=0;//控制的是行for(i=0;i<r;i++){intj=0;for(j=0;j<c;j++){printf("%d ",arr[i][j]);}printf("\n");}}intmain(){intarr[3][5]={1,2,3,4,5,2,3,4,5,6,3,4,5,6,7};Print(arr,3,5);return0;}

这⾥实参是⼆维数组,形参也写成⼆维数组的形式,那还有什么其他的写法吗?
⾸先我们再次理解⼀下⼆维数组:

  • ⼆维数组其实可以看做是每个元素是⼀维数组的数组,也就是⼆维数组的每个元素是⼀个⼀维数组。
  • 那么⼆维数组的⾸元素就是第⼀⾏,是个⼀维数组

⼆维数组传参本质上也是传递了地址,传递的是第⼀
⾏这个⼀维数组的地址

指针形式:

voidPrint(int(*arr)[5],intr,intc){inti=0;//控制的是行for(i=0;i<r;i++){intj=0;//arr[i]for(j=0;j<c;j++){//*(arr + i) == arr[i]//printf("%d ", arr[i][j]);//arr[i][j] == *(*(arr+i) +j )printf("%d ",*(*(arr+i)+j));}printf("\n");}}intmain(){intarr[3][5]={1,2,3,4,5,2,3,4,5,6,3,4,5,6,7};Print(arr,3,5);//arr 是数组名,也表示首元素的地址return0;}


二维数组传参,形参的部分可以写成数组,也可以写成指针形式

总结:

二维数组传参 = 传递指向一维数组的指针 写法等价: void f(int arr[][N]) void f(int (arr)[N])
不能写成 int
*,类型不匹配 传递的是地址,函数内修改会影响原数组 二维数组在内存中是连续存储的

四, 函数指针变量

4.1 函数指针变量的创建

什么是函数指针变量呢?
根据前⾯学习整型指针,数组指针的时候,我们的类⽐关系,我们不难得出结论:
函数指针变量应该是⽤来存放函数地址的,未来通过地址能够调⽤函数的。
那么函数是否有地址呢?

  • 函数也有地址,函数指针就是用来存函数入口地址的变量
  • 函数名 = 函数的入口地址**

  • 函数名就是函数的地址
  • &函数名也是拿到函数的地址
intmain(){/*int a = 10; &a; printf("%p\n", &Add); printf("%p\n", Add);*/intarr[6]={0};int(*pa)[6]=&arr;//pa就是一个数组指针int(*pf)(int,int)=&Add;//pf就是函数指针变量char*(*pt)(int*,char)=&test;return0;}

4.2 函数指针变量的使用

// 定义一个加法函数intAdd(intx,inty){returnx+y;}intmain(){// 定义函数指针 pf,指向 Add 函数int(*pf)(int,int)=Add;// 用三种等价方式调用 Add 函数intr1=(*pf)(10,20);// 写法1:显式解引用intr2=Add(10,20);// 写法2:直接调用原函数intr3=pf(10,20);// 写法3:直接用指针调用(C语言允许)printf("%d\n",r1);// 30printf("%d\n",r2);// 30printf("%d\n",r3);// 30return0;}

为什么三种调用方式结果一样?

  • Add(10, 20):直接调用函数,走函数的入口地址。
  • pf(10, 20):C 语言的语法糖,会自动把 pf 里存的地址当成函数入口调用。
  • (*pf)(10, 20):显式解引用 pf,拿到它指向的函数,再调用,是最 “原始” 的写法。
  • 这三种写法最终都会跳转到 Add 函数的同一段代码执行,所以结果都是 30。

Add 是函数本身,pf 是存了 Add 地址的指针变量,二者类型不同,但调用效果可以一样

4.3 两个有趣的代码

    intmain(){//void (*p)(); p是函数指针变量//void (*)() 函数指针类型//0 -int//( void (*)() )0, 发生了强制类型转换,将0当做一个地址,这个0地址处放的函数是:无参,返回类型是void的函数//(*(void(*)())0)();//上面的代码是一次函数调用//调用的是0这个地址处的函数//return0;}
      //下面的代码是一个函数声明//声明的函数名字叫:signal//signal函数有2个参数,第一个参数的类型是int,第二个参数的类型是函数指针 void(*)(int),这个函数指针能够指向的函数参数是int,返回类型是void//signal函数的返回值类型是函数指针 void(*)(int),这个函数指针能够指向的函数参数是int,返回类型是voidvoid(*signal(int,void(*)(int)))(int);

      4.3.1 typedef关键字

      是 C/C++ 里给数据类型起别名的关键字,作用就是:把复杂的类型名变简单、让代码更易读、更规范,可以将复杂的类型,简单化。

      把 typedef 去掉,剩下的就是变量定义;加上 typedef,变量名就变成类型名

      总结:
      • typedef = 给数据类型起别名
      • 用法:typedef 原类型 新名字
      • 最常用场景:结构体、指针、数组、函数指针
      • 比 #define更安全、更智能

      五,函数指针数组

      把函数的地址存到⼀个数组中,数组里每一个元素都是一个函数指针,那这个数组就叫函数指针数组


      格式:
      返回值 (*数组名[数组大小])(参数列表);
      int (*arr[5])(int, char);

      • arr:数组名
      • arr[5]:数组有 5 个元素
      • (*arr[5]):每个元素是指针
      • (int, char):指针指向的函数参数
      • nt:函数返回值

      //函数指针数组intAdd(intx,inty){returnx+y;}intSub(intx,inty){returnx-y;}intmain(){//int (*pf1)(int, int) = Add;//int (*pf2)(int, int) = Sub;//函数指针数组int(*pf[4])(int,int)={Add,Sub};return0;}

      调用时不用解引用 *,直接 数组[下标]()。

      六,转移表

      什么是转移表?

      • 转移表本质 = 函数指针数组
      • 专门用来替代 if-else 多层嵌套、switch 超长分支
      • 函数指针数组的用途:转移表

      原理:

      用下标索引直接找到数组里对应的函数指针,直接调用函数, 不用层层判断,执行效率更高、代码更整洁、扩展性极强


      举例:计算器的一般实现

      voidmenu(){printf("--------- 计算器 ---------\n");printf("----- 1. add 2. sub ----\n");printf("----- 3. mul 4. div ----\n");printf("----- 0. exit ----\n");printf("---------------------------\n");}intAdd(intx,inty){returnx+y;}intSub(intx,inty){returnx-y;}intMul(intx,inty){returnx*y;}intDiv(intx,inty){returnx/y;}intmain(){intinput=0;intx=0,y=0;intr=0;do{menu();printf("选择:");scanf("%d",&input);switch(input){case1:printf("请输入两个操作数:");scanf("%d %d",&x,&y);r=Add(x,y);printf("%d\n",r);break;case2:printf("请输入两个操作数:");scanf("%d %d",&x,&y);r=Sub(x,y);printf("%d\n",r);break;case3:printf("请输入两个操作数:");scanf("%d %d",&x,&y);r=Mul(x,y);printf("%d\n",r);break;case4:printf("请输入两个操作数:");scanf("%d %d",&x,&y);r=Div(x,y);printf("%d\n",r);break;case0:printf("退出计算器\n");break;default:printf("选择错误\n");break;}}while(input);return0;}

      使用函数指针数组实现:

      //使用函数指针数组的实现-----------------------------------voidmenu(){printf("--------- 计算器 ---------\n");printf("----- 1. add 2. sub ----\n");printf("----- 3. mul 4. div ----\n");printf("----- 0. exit ----\n");printf("---------------------------\n");}intAdd(intx,inty){returnx+y;}intSub(intx,inty){returnx-y;}intMul(intx,inty){returnx*y;}intDiv(intx,inty){returnx/y;}intmain(){intinput=0;intx=0,y=0;intr=0;//函数指针的数组//转移表int(*pfArr[])(int,int)={NULL,Add,Sub,Mul,Div};//0 1 2 3 4do{menu();//打印菜单printf("选择:");//读取用户输入选项scanf("%d",&input);//0 1 2 3 4 5 6 7if(input>=1&&input<=4){printf("请输入两个操作数:");scanf("%d %d",&x,&y);//通过转移表下移,调用对应运算函数r=pfArr[input](x,y);printf("%d\n",r);}elseif(input==0){printf("退出计算器\n");}else{printf("选择错误\n");}}while(input);//输入0时跳出循环return0;}

      转移表优势

      • 加功能只需要往数组里加一个函数地址,不用改判断逻辑
      • 分支再多,查找都是 O (1) 直接下标访问
      • 代码极简、可读性强、维护方便
      • 嵌入式、状态机、协议解析必用

      模板结构

      1. 写各个业务功能函数
      2. typedef 函数指针类型
      3. 定义函数指针数组 = 转移表,按序号依次放函数
      4. 输入命令 / 下标,做边界校验
      5. 转移表下标; 直接调用

      通用代码模板:

      #include<stdio.h>// 1. 功能函数voidfun0(void){printf("功能0\n");}voidfun1(void){printf("功能1\n");}voidfun2(void){printf("功能2\n");}voiddefFun(void){printf("无效指令\n");}// 2. 函数指针别名typedefvoid(*CmdFunc)(void);// 3. 转移表CmdFunc cmdTable[]={fun0,fun1,fun2};#defineCMD_TBL_LENsizeof(cmdTable)/sizeof(cmdTable[0])intmain(void){intidx;scanf("%d",&idx);if(idx>=0&&idx<CMD_TBL_LEN)cmdTable[idx]();elsedefFun();return0;}

      总结:

      • 转移表 = 函数指针数组
      • 本质:用整数下标映射到对应函数
      • 替代冗长 if-else /switch,适合多分支场景
      • 扩展功能:只需要在数组里追加函数地址,不用改判断
      • 调用格式:转移表名[下标] (实参);
      http://www.jsqmd.com/news/795770/

      相关文章:

    1. 千问 LeetCode 2227. 加密解密字符串 Python3实现
    2. Unitree GO2 ROS2 SDK完整指南:5步实现四足机器人智能控制与自主导航
    3. 2026年中石化加油卡回收靠谱平台最新深度测评 - 京顺回收
    4. [具身智能-622]:高速图像传感器接口(视觉 / 摄像头)与数据格式
    5. 别再只加contentDescription了!Android无障碍适配TalkBack的7个实战避坑点(含完整代码)
    6. 根据用户主动关注用户和用户朋友圈以及其他关系层面平台注入的用户 系统推荐程序返回用户推荐列表
    7. 第四章 数字孪生制作完整流程
    8. 无人机通信安全渗透测试:从信号拦截到GPS欺骗的完整攻防框架
    9. 茅台自动预约系统:告别手动抢购,实现智能预约的完整解决方案
    10. 从零到精通:手把手教你用BusHound分析SCSI Sense错误码(附完整排查流程)
    11. 终极指南:如何通过Typora插件实现高效文件管理与快速切换
    12. 洛谷比赛分级
    13. 如何用FanControl在5分钟内解决Windows风扇噪音问题?
    14. mkcert进阶玩法:一键生成局域网HTTPS证书,让内网测试告别“不安全”警告(含Windows/Linux/Mac多平台指南)
    15. WebGLM:基于检索增强生成(RAG)的实时联网智能问答系统实战解析
    16. 金仓数据库 V9R4C19 安全加固实战:禁用 root 部署 + hashbytes 单向哈希
    17. 大模型中转哪个技术机构靠谱
    18. 2026年论文AI率爆表?掌握这2招快速去AI痕迹,导师挑不出毛病! - 降AI实验室
    19. 如何彻底卸载Windows Defender:2025完整移除工具使用指南
    20. PDPI Spec:规格驱动开发如何提升AI时代软件工程效率
    21. 不只是Target选错:深挖Metasploit中‘Exploit completed, but no session’的3个隐蔽原因与对策
    22. 基于Claude的智能代码质量监控工具设计与实践
    23. 别再死记硬背三段式状态机了!用HDLbits的Simple FSM题,带你搞懂Verilog状态机设计的核心差异
    24. 12万Star的Karpathy skills:四原则修正 LLM 编码行为
    25. Simulink给STM32做自动代码生成?我实测了F4和H7系列,这些坑你得提前知道
    26. 2026遥感、地球科学与人工智能国际学术会议(RSGAI 2026)
    27. FFXIV TexTools终极指南:打造《最终幻想14》专属视觉体验的三大核心模块
    28. 闲鱼自动化脚本开发实战:基于uiautomator2的UI自动化与风控对抗
    29. Go语言技能树构建:从知识体系到评估引擎的工程实践
    30. Teamcenter 13 部署实战:从零到一构建企业级PLM环境