尧图网站建设 尧图网络
  • 首页
  • 关于我们
  • 服务项目
  • 案例展示
  • 建站流程
  • 资讯中心
  • 联系我们
首页/资讯中心/详情

了解指针4

了解指针4
📅 发布时间:2026/7/14 19:47:36

深入理解指针(4) — 内容总结


1. 字符指针变量(char*)

1.1 指向单个字符

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

1.2 指向字符串字面量

#include<stdio.h>intmain(){//加cosnt是为了不让该字符串里的内容,若是修改代码会崩溃constchar*pstr="hello bit.";// pstr 存的是字符串首字符 'h' 的地址printf("%c\n",*pstr);// 输出首字符 'h'printf("%s\n",pstr);// 输出 hello bit.return0;}

注意:字符串字面量存储在只读数据区,所以pstr最好用const char*修饰,防止通过pstr修改时程序崩溃。

1.3 经典题:字符数组 vs 字符指针

#include<stdio.h>intmain(){charstr1[]="hello bit.";charstr2[]="hello bit.";constchar*str3="hello bit.";constchar*str4="hello bit.";if(str1==str2)// str1和str2的地址不一样{printf("str1 and str2 are same\n");}else{printf("str1 and str2 are not same\n");// ✅ 输出这行}if(str3==str4){printf("str3 and str4 are same\n");// ✅ 输出这行}else{printf("str3 and str4 are not same\n");}return0;}

结果:
str1 and str2 are not same
str3 and str4 are same

原因:

str1 和 str2 是两个独立的数组,分别占据内存中不同的位置。

str1 == str2 比较的是两个数组的起始地址(也就是数组名被隐式转换为指针)。

虽然它们的内容完全相同,但地址不同,所以比较结果为假(不相等)。

“hello bit.” 是一个字符串常量,存储在常量区(只读数据段)。

编译器为了节省内存,会将内容相同的字符串常量优化为只存一份。

str3 和 str4 都指向同一块内存地址。

str3 == str4 比较的是两个指针存储的地址值,它们相同,所以比较结果为

真(相等)。

结论:

变量存储位置比较结果
str1、str2各自在栈上开辟独立的字符数组不相同(地址不同)
str3、str4都指向只读数据区的同一字符串字面量相同(编译器优化,指向同一地址)

2. 数组指针变量

2.1 数组指针 vs 指针数组(分辨技巧)

int*p1[10];// 指针数组:p1 先与 [] 结合 → 10 个 int* 的数组//指向首元素int(*p2)[10];// 数组指针:p2 先与 * 结合 → 指向含 10 个 int 的数组的指针(指向数组的指针变量 )

p1是数组,类型是指针,所有是指针数组
p2是指针,指向数组,所有是数组指针
口诀:[]优先级高于*。不加括号 → 数组;加括号 → 指针。

2.2 数组指针的初始化

intarr[10]={0};int(*p)[10]=&arr;// p 指向整个数组(int (*)[10] 类型)

关键区别:arr表示数组首元素的地址(类型为int*),而&arr表示整个数组的地址(类型为int (*)[10])。虽然两者的数值可能相同,但指针类型和步长不同。

图解:

int (*p)[10] = &arr; │ │ │ └── p 指向一个包含 10 个 int 的数组 └────── p 是一个指针

关键:&arr的类型是int (*)[10](数组指针),与普通指针int*不同。


3. 二维数组传参的本质

二维数组作为函数参数时,会退化为指向第一行(一维数组)的指针。

3.1 传统写法(数组形式)

#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;}

3.2 本质写法(数组指针形式)

#include<stdio.h>// int a[3][5] 等价于 int (*p)[5]voidtest(int(*p)[5],intr,intc)//int (*p)[5]是指向第一个数组的地址{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;}

核心理解:

表达式含义
p指向第 0 行(一个int[5])
p + i指向第 i 行,跳过一个int[5](即 20 字节)
*(p + i)得到第 i 行的数组名(int[5])也就是该数组首元素的地址
*(p + i) + j第 i 行中第 j 个元素的地址
*(*(p + i) + j)第 i 行第 j 个元素的值

结论:int a[3][5]作为参数时退化为int (*)[5](指向含 5 个 int 的数组的指针),而非二级指针int**。


4. 函数指针变量

4.1 函数地址

函数名和&函数名都是函数的地址:

#include<stdio.h>voidtest(){printf("hehe\n");}intmain(){printf("test: %p\n",test);// 输出 test: 005913CAprintf("&test: %p\n",&test);// 输出 &test: 005913CAreturn0;}

结论:test和&test数值相同,都是函数的入口地址。

4.2 函数指针的定义与使用

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;// x、y 可写可不写

&Add和Add都是函数地址

函数指针拆解:

int (*pf3) (int x, int y) ↑ ↑ ↑ | | └── 参数列表 | └──────────── pf3 是指针 └───────────────── 返回值类型为 int

调用方式(两种等价):

#include<stdio.h>intAdd(intx,inty){returnx+y;}intmain(){int(*pf3)(int,int)=Add;//(*pf3)==Add==&Add==pf3printf("%d\n",(*pf3)(2,3));// 输出 5(注意一定要把*pf3括起来)printf("%d\n",pf3(3,5));// 输出 8(等价写法)return0;}

4.3 两个经典考验

例1:
(*(void(*)())0)();

拆解步骤:

步骤解释
0整数 0
(void (*)())0将 0 强制转换为函数指针类型(无参,返回 void)
*(void (*)())0解引用,得到该函数
(*(void (*)())0)()调用地址为 0 的那个函数

本质:调用一个地址为 0 的函数(危险操作,会导致段错误)。

例2:
void(*signal(int,void(*)(int)))(int);

拆解步骤:

步骤解释
signal函数名
signal(int, void(*)(int))signal 函数有两个参数:一个int,一个函数指针(参数为 int、返回 void)
void (*...)(int)signal 的返回类型是函数指针(参数为 int、返回 void)

4.4 用typedef简化函数指针

typedefunsignedintuint;// 给类型起别名//unsigned int == unittypedefint*pint;// 将 int* 起别名为 pint//int*=pinttypedefint(*parr_t)[5];// 数组指针类型起别名//int(*)[5] == parr_ttypedefvoid(*pfun_t)(int);// 函数指针类型起别名//void (*)(int) == pfun_t//pfun_t p2 = test;//可定义为 pfun_t(类型) p2(函数指针变量的名字) = test(函数)// 简化例2:typedefvoid(*pfun_t)(int);pfun_tsignal(int,pfun_t);// 简洁明了!

和#define的区别:

// 使用 typedeftypedefchar*STRING;STRING p1,p2;// p1 和 p2 都是 char* 类型(两个都是指针)✅// 使用 #define#defineSTRINGchar*STRING p1,p2;// 展开后:char* p1, p2; 只有 p1 是指针,p2 是 char!❌

5. 函数指针数组

int*arr[10];// 整型指针数组int(*parr1[3])();// 函数指针数组:3 个函数指针,返回 int、无参int*parr2[3]();// ❌ 错误:函数不能返回数组int(*)()parr3[3];// ❌ 语法错误

int (*parr1[3])();拆解:

int (*parr1[3])(); ↑ └── parr1 先与 [3] 结合 → 数组 再与 * 结合 → 数组中存放的是指针 再与 () 结合 → 指针指向函数(返回 int、无参)

结论:parr1是一个包含 3 个元素的数组,每个元素是int (*)()类型的函数指针。


6. 转移表 — 函数指针数组的应用

函数指针数组可用于替换冗长的switch-case,实现转移表。

6.1 传统写法(switch-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;do{printf("*************************\n");printf(" 1:add 2:sub \n");printf(" 3:mul 4:div \n");printf(" 0:exit \n");printf("*************************\n");printf("请选择:");scanf("%d",&input);switch(input){case1:printf("请输入两个操作数:");scanf("%d %d",&x,&y);ret=add(x,y);printf("ret = %d\n",ret);break;case2:printf("请输入两个操作数:");scanf("%d %d",&x,&y);ret=sub(x,y);printf("ret = %d\n",ret);break;case3:printf("请输入两个操作数:");scanf("%d %d",&x,&y);ret=mul(x,y);printf("ret = %d\n",ret);break;case4:printf("请输入两个操作数:");scanf("%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;}

6.2 转移表写法(函数指针数组,简洁优雅)

#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");printf("*************************\n");printf("请选择:");scanf("%d",&input);if(input<=4&&input>=1){printf("请输入两个操作数:");scanf("%d %d",&x,&y);ret=(*p[input])(x,y);// 通过函数指针调用printf("ret = %d\n",ret);}elseif(input==0){printf("退出程序\n");}else{printf("输入错误\n");}}while(input);return0;}

对比总结:

方式代码量可扩展性
switch-case~60 行,每加一个运算多 6 行繁琐
转移表~40 行,只需在数组中加一个函数指针简洁优雅

核心思路:用函数指针数组的下标替代switch分支,通过(*p[input])(x, y)一行调用对应的函数。


总结

本讲是函数指针的深入内容,重点掌握:

  • 字符指针char*:区分数组与字面量,str1 != str2但str3 == str4
  • 数组指针int (*p)[10]:[]优先级高于*,加括号才是指针
  • 二维数组传参退化:int a[3][5]→int (*p)[5],不是二级指针
  • 函数指针:int (*pf)(int, int),函数名就是地址,typedef可简化复杂声明
  • 函数指针数组:int (*arr[3])(),存放多个函数指针
  • 转移表:用函数指针数组替代switch-case,代码更简洁

相关新闻

  • 【优选算法】滑动窗口专项:1.长度最小的子数组 2.无重复最长子串
  • 2026 北京二手包包变现避坑指南,7 月实时行情零折旧计价 - 生活时报
  • C++组合模式实战:统一处理树形对象结构的设计艺术

最新新闻

  • Python重试机制设计:Tenacity策略编排与分布式容错实践
  • 深度学习基石:从线性回归到梯度下降的优化之旅
  • Rocky/AlmaLinux安装FirewallD图形界面指南
  • 第十九届全国大学生智能汽车竞赛报名数据全景透视
  • 计算机毕业设计之jsp文具销售平台的设计与实现
  • 大萨达大大阿达

日新闻

  • AWS SSM安全运维实践:零公网暴露的合规远程开发方案
  • Tableau 2024.1 图表选择指南:5种业务场景与最佳图表类型匹配
  • dsPIC33FJ与CMT-8540S-SMT在嵌入式音频处理中的高效应用

周新闻

  • IX9104 PCIe5.0 高速交换芯片@ACP#完整规格 + 应用场景总结
  • Unity游戏集成Coze智能体:实现NPC智能对话与知识库联动
  • SAP EPIC 建行回单查询:从标准类CL_EPIC_EXAMPLE_CN_CCB_GHTD到Z类的5处关键修改

月新闻

  • 2026年6月公司网站搭建最新热门渠道测评:四大低成本/零代码平台对比+避坑
  • 【Linux】Linux arm 编译QT程序,出现expected “}“报错
  • 【MATLAB例程】四基站二维AOA定位与距离辅助增强对比仿真。基于角度观测和测距修正的固定目标平面定位精度分析

关于尧图

  • 公司简介
  • 团队介绍
  • 企业文化
  • 荣誉资质

服务项目

  • 定制开发
  • 电商建站
  • UI 设计
  • 运维服务

快速链接

  • 案例展示
  • 建站流程
  • 常见问题
  • 资讯中心

联系方式

  • 📍北京市朝阳区互联网产业园 A 座 10 层
  • 📞400-888-8888
  • ✉️contact@rkmt.cn
  • 🕐周一至周日 9:00-21:00

© 2024 北京尧图网络科技有限公司 版权所有 | 京 ICP 备 XXXXXXXX 号