文章目录深入指针4 - 学习笔记整理一、数组指针和指针数组回顾验证 * 和 的抵消关系二、二维数组传参的本质二维数组内存布局二维数组名含义传参打印实现三、函数指针变量基本概念四、两段有趣的代码提高阅读能力1. 有趣的函数调用2. 有趣的函数声明函数声明、定义、调用的区别五、typedef关键字基本用法复杂声明简化六、函数指针数组运算符优先级* []函数指针数组实现七、计算器实现1. 普通计算器switch语句2. 转移表实现函数指针数组深入指针4 - 学习笔记整理一、数组指针和指针数组回顾验证*和的抵消关系#includestdio.hintmain(){inta10;int*pa;printf(%d\n,*p);// *p *(a) aintarr[10]{1,2,3,4,5,6,7,8,9,10};int(*parr)[10]arr;// 数组指针存放整个数组的地址for(inti0;i10;i){printf(%d ,(*parr)[i]);// (*parr)[i] *(arr)[i] arr[i] *(arri)}return0;}关键点[i]通过下标访问数组元素arr[i][j]访问二维数组第i行第j列元素*和互为逆运算*(a) a二、二维数组传参的本质二维数组内存布局二维数组在内存中是连续存储的按行优先排列。二维数组名含义二维数组的数组名是首元素地址而首元素是第一行的一维数组所以二维数组名就是第一行一维数组的地址。传参打印实现// 方法1使用数组接收voidprint1(intarr[3][5],intr,intc){for(inti0;ir;i){for(intj0;jc;j){printf(%d ,*(*(arri)j));// 三种写法均可// printf(%d , arr[i][j]);// printf(%d , (*(arr i))[j]);}printf(\n);}}// 方法2使用数组指针接收本质voidprint2(int(*p)[5],intr,intc)// p指向含有5个int元素的数组{for(inti0;ir;i){for(intj0;jc;j){printf(%d ,(*(pi))[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}};print1(arr,3,5);print2(arr,3,5);return0;}核心理解二维数组传参传递的是第一行一维数组的地址形参要用数组指针接收int(*p)[5]三、函数指针变量基本概念函数也有地址函数名函数名都是函数的地址通过函数地址调用函数即使不知道函数名只要知道地址就能调用查看变量类型去掉变量名剩余部分就是类型intAdd(intx,inty){returnxy;}intmain(){inta10;int*paa;// 整型指针intarr[5]{0};int(*parr)[5]arr;// 数组指针int(*pf)(int,int)Add;// 函数指针存放Add函数的地址// int (*pf)(int, int) Add; // 等价// 三种调用方式结果相同intret1(*pf)(4,5);// 解引用函数指针intret2pf(4,5);// 直接使用函数指针推荐intret3Add(4,5);// 直接使用函数名printf(ret1 %d\n,ret1);printf(ret2 %d\n,ret2);printf(ret3 %d\n,ret3);return0;}四、两段有趣的代码提高阅读能力1. 有趣的函数调用(*(void(*)())0)();解析(void (*)())函数指针类型指向返回void、无参数的函数(void (*)())0将0强制转换为函数指针类型即0成为函数地址*(void (*)())0解引用找到0地址处的函数(*(void (*)())0)()调用0地址处的函数含义调用地址为0处的函数实际不可调用仅供理解2. 有趣的函数声明void(*signal(int,void(*)(int)))(int);解析方法从内向外逐步剖析signal是一个函数名它有两个参数int和void(*)(int)函数指针signal的返回值类型是void(*)(int)函数指针简化使用typedeftypedefvoid(*pf_t)(int);pf_tsignal(int,pf_t);函数声明、定义、调用的区别概念说明示例函数声明告诉编译器函数的存在包含返回值类型、函数名、参数类型int Add(int, int);函数定义完整实现函数功能包含函数体int Add(int x, int y) { return x y; }函数调用使用函数完成特定功能int sum Add(3, 5);五、typedef关键字作用类型重命名简化复杂类型基本用法// 基本类型重命名typedefunsignedintuint;// unsigned int → uinttypedefint*ptr_t;// int* → ptr_t// 数组指针重命名typedefint(*parr_t)[6];// int(*)[6] → parr_t// 注意新类型名必须在*右边// 函数指针重命名typedefint(*pf_t)(int,int);// int(*)(int,int) → pf_tintAdd(intx,inty){returnxy;}intmain(){uint n10;// 等价于 unsigned int n 10ptr_tpn;// 等价于 int* p nintarr[6]{0};parr_tparrarr;// 等价于 int(*parr)[6] arrpf_tpaddAdd;// 等价于 int(*padd)(int,int) Addreturn0;}复杂声明简化// 原始复杂声明void(*signal(int,void(*)(int)))(int);// 使用typedef简化typedefvoid(*pf_t)(int);pf_tsignal(int,pf_t);六、函数指针数组概念存放相同类型函数指针的数组运算符优先级*[]int*parr[5];// 指针数组parr先与[5]结合有5个int*元素int(*parr)[5];// 数组指针parr先与*结合指向有5个int元素的数组函数指针数组实现intAdd(intx,inty){returnxy;}intSub(intx,inty){returnx-y;}intMul(intx,inty){returnx*y;}intDiv(intx,inty){returnx/y;}intmain(){// 函数指针数组存放4个函数指针int(*pf[4])(int,int){Add,Sub,Mul,Div};// ↑ pf是数组有4个元素每个元素类型是int(*)(int,int)// 遍历调用for(inti0;i4;i){printf(%d ,pf[i](4,6));// 直接通过数组下标调用// printf(%d , (*pf)[i](4, 6)); // 错误*不能解引用数组}return0;}注意*只能解引用指针不能解引用数组七、计算器实现1. 普通计算器switch语句intAdd(intx,inty){returnxy;}intSub(intx,inty){returnx-y;}intMul(intx,inty){returnx*y;}intDiv(intx,inty){returnx/y;}voidmenu(){printf(*********************************\n);printf(**********1.Add 2.Sub***********\n);printf(**********3.Mul 4.Div***********\n);printf(**********0.exit ***********\n);printf(*********************************\n);}intmain(){intx,y,input,ret;do{menu();printf(请选择);scanf(%d,input);switch(input){case1:printf(请输入两个整数);scanf(%d %d,x,y);retAdd(x,y);printf(结果是%d,ret);break;case2:printf(请输入两个整数);scanf(%d %d,x,y);retSub(x,y);printf(结果是%d,ret);break;case3:printf(请输入两个整数);scanf(%d %d,x,y);retMul(x,y);printf(结果是%d,ret);break;case4:printf(请输入两个整数);scanf(%d %d,x,y);retDiv(x,y);printf(结果是%d,ret);break;case0:printf(正在退出计算器...);break;default:printf(请重新选择);break;}printf(\n);}while(input);return0;}缺点添加新功能需要增加case代码冗长2. 转移表实现函数指针数组转移表通过函数指针数组实现函数调用转移intAdd(intx,inty){returnxy;}intSub(intx,inty){returnx-y;}intMul(intx,inty){returnx*y;}intDiv(intx,inty){returnx/y;}intAnd(intx,inty){returnxy;}intOr(intx,inty){returnx|y;}intNor(intx,inty){returnx^y;}intcontray(intx){return~x;}// 单目运算voidmenu(){printf(*********************************\n);printf(**********1.Add 2.Sub***********\n);printf(**********3.Mul 4.Div***********\n);printf(**********5.And 6.Or ***********\n);printf(**********7.Nor 8.contray*******\n);printf(**********0.exit ***********\n);printf(*********************************\n);}intmain(){intinput,x,y;// 函数指针数组转移表int(*parr[8])(int,int){0,Add,Sub,Mul,Div,And,Or,Nor};// 下标: 0 1 2 3 4 5 6 7do{menu();printf(请选择);scanf(%d,input);if(input1input7)// 双目运算{printf(请输入两个数);scanf(%d %d,x,y);intretparr[input](x,y);// 通过数组下标调用对应函数printf(结果是%d\n,ret);}elseif(input8)// 单目运算特殊处理{printf(请输入一个数);scanf(%d,x);intretcontray(x);printf(结果是%d\n,ret);}elseif(input0){printf(退出计算器\n);}else{printf(请重新输入\n);}}while(input);return0;}优点添加新功能只需在数组中增加函数指针代码简洁易于扩展消除了大量重复的switch-case代码