news 2026/5/11 12:27:41

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

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
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,适合多分支场景
      • 扩展功能:只需要在数组里追加函数地址,不用改判断
      • 调用格式:转移表名[下标] (实参);
      版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
      网站建设 2026/5/11 12:26:02

      如何快速解决FanControl ADLX初始化失败:AMD显卡用户完整指南

      如何快速解决FanControl ADLX初始化失败&#xff1a;AMD显卡用户完整指南 【免费下载链接】FanControl.Releases This is the release repository for Fan Control, a highly customizable fan controlling software for Windows. 项目地址: https://gitcode.com/GitHub_Tren…

      作者头像 李华
      网站建设 2026/5/11 12:23:18

      ARM调试与数据缓存维护指令详解

      1. ARM调试与数据缓存维护指令深度解析在嵌入式系统开发领域&#xff0c;ARM架构处理器的调试和缓存维护机制是每个底层开发者必须掌握的核心技术。调试寄存器(DBGWVR)和数据缓存维护指令(DCCIMVAC等)构成了系统调试和性能优化的基石&#xff0c;它们直接影响着开发效率、系统稳…

      作者头像 李华
      网站建设 2026/5/11 12:19:37

      用Logisim搞定华科计组实验:从零搭建单总线CPU(含定长/变长时序对比)

      用Logisim从零构建单总线CPU&#xff1a;定长与变长时序的实战对比 在计算机组成原理的实验课程中&#xff0c;CPU设计往往是让学生既兴奋又头疼的核心环节。华科的这门实验要求学生用Logisim搭建一个完整的单总线CPU&#xff0c;其中时序控制单元的设计尤为关键。很多同学在完…

      作者头像 李华
      网站建设 2026/5/11 12:13:33

      3步掌握清华PPT模板:终极方案解决学术演示设计难题

      3步掌握清华PPT模板&#xff1a;终极方案解决学术演示设计难题 【免费下载链接】THU-PPT-Theme 清华主题PPT模板 项目地址: https://gitcode.com/gh_mirrors/th/THU-PPT-Theme 还在为学术汇报PPT设计而苦恼吗&#xff1f;每次准备答辩、会议或教学演示&#xff0c;你都要…

      作者头像 李华