文章目录字符指针变量数组指针变量数组指针变量是什么数组指针变量怎么初始化二维数组与指针二维数组传参的本质二维数组中的暗含的退化函数指针变量函数指针变量的创建Add和Add函数指针变量的使用函数指针是否要\*才能调用函数两段代码帮你更好理解函数指针typedef 关键字函数指针数组函数指针数组的使用场景转移表这里是think的博客希望可以一起交流知识一起think今天我们来学习指针(4)吧一起来think吧今天我们将迎来指针模块里难度偏高的知识点放平心态一起稳步开启学习之旅。字符指针变量指针中有一种指针是字符指针即char*一般这样使用intmain(){charchw;char*pcch;*pca;return0;}但是还有一种使用方法intmain(){constchar*pstrhello world.;printf(%s\n,pstr);很多人下意识认为这里是把一个字符串放到pstr指针变量实际上不是的因为指针中只能存放一个地址那么这个hello world.存放在哪里呢实际上它存放在内存中的常量区中这个字符串是常量字符串pstr拿到的是这个常量字符串的首元素地址。接下来又会有一个问题字符指针和字符数组有什么差别呢接下来我们通过一个例子来明晰它们之间的差别。#includestdio.hintmain(){charstr1[]hello bit.;charstr2[]hello bit.;constchar*str3hello bit.;constchar*str4hello bit.;if(str1str2)printf(str1 and str2 are same\n);//1elseprintf(str1 and str2 are not same\n);///2if(str3str4)printf(str3 and str4 are same\n);//3elseprintf(str3 and str4 are not same\n);//4return0;}答案就是24。我们来分析一下首先str1和str2是两个不同的字符数组其中在编译期间编译器就用常量字符串hello bit.来初始化字符数组了而这两个字符数组在内存中栈区的所开辟的空间是不同的那么它们首元素的地址也不一样。其中常量字符串如果内容是一样的之后只会在常量区中存一份因为常量区里的数据是不能修改的故而str3和str4存的hello bit.的首元素地址是一样的。数组指针变量数组指针变量是什么我们讲过一个词的本质是最后一个那个名词同样类比可知字符指针和整型指针都是指针字符指针存放的是字符的地址指向的内容是字符整型指针存放的整型的地址指向的内容是整型那么数组指针也是指针存放的是数组的地址指向的内容是数组。怎么定义数组指针变量的类型int (*p2)[10];分析一下结构*说明的是p2是指针变量后面的[10]说明这个指针变量指向的是一个有10个元素的数组其中int说明这10个元素都是整型我们对比一下和指针数组对比一下int * p1[10];分析一下[ ]的优先级是比*高的所以p1先与[ ]结合说明这是一个有10个元素的数组然后再到前面的int*,说明了数组元素的类型是整型指针。所以我们明白了为什么数组指针要加就是因为[ ]优先级高为保证*先与p2结合先确定它的本质是指针确定了本质后其他的都是它的属性了。数组指针变量怎么初始化arr的时候arr不会退化它的意义依旧是整个数组那么后就得到了整个数组的地址用arr初始化即可int arr[10] {0};int(*p)[10] arr;VS中这个的调试中类型是int[10]*是优化过的实际上的数组指针的类型是去变量名后剩下的部分即int(*)[10]。二维数组与指针二维数组传参的本质#includestdio.hvoidtest(inta[3][5],intr,intc){inti0;intj0;for(i0;ir;i){for(j0;jc;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;}arr我们知道是首元素的地址而在二维数组又可以看作每个元素是一维数组的数组所以二维数组的首元素就是第一行的一维数组所以首元素的地址就是一维数组的地址。而数组指针就是指向一维数组的指针像上面的那个例子arr的类型就是int(*)[5]。#includestdio.hvoidtest(int(*p)[5],intr,intc){inti0;intj0;for(i0;ir;i){for(j0;jc;j){printf(%d ,*(*(pi)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;}这就是二维数组传参的本质传的是一个数组指针。二维数组中的暗含的退化二维数组中化为指针的本质的时候其实是有退化现象存在的如果理解了这个现象也就可以很轻松理解二维数组的索引了。我们知道arr[i][j]就是*(*(arri)j),那么其中arr就是第一行下标为0一维数组的地址那么加i就指向了下标为i的那一行数组那么解引用就得到了对应的那一行数组此时就退化为了那一行数组的首元素地址那么接下来的操作就是一维数组中的操作了。其中退不退化看表达式的逻辑是需要整个数组还是首元素地址明显这里需要首元素地址继续去找元素的值。函数指针变量函数指针变量的创建#includestdio.hvoidtest(){printf(hehe\n);}intmain(){printf(test: %p\n,test);printf(test: %p\n,test);return0;}我们发现test和test都是地址说明函数是有地址的并且两个地址是一样的说明取函数地址可取也可以不取。test: 005913CAtest: 005913CAvoidtest(){printf(hehe\n);}void(*pf1)()test;void(*pf2)()test;intAdd(intx,inty){returnxy;}int(*pf3)(int,int)Add;int(*pf3)(intx,inty)Add;//x和y写上或者省略都是可以的我们知道函数指针就是指向函数的指针其中类比数组指针我们可以知道函数指针的写法中间有个*来说明pf1是指针后面写函数形参类型变量可以不写前面写返回值。函数指针变量的类型就是去掉变量名如int (*) (int x, int y)函数类型是int (int x,int y)。Add和Add我们知道Add和Add都可以得到函数的地址有的时候Add是整个函数的意思后就得到了整个函数的地址而为什么单独的Add都可以得到地址呢可以类比到数组中退化但是在函数中我们一般会说这是隐式转换我们口头也说是退化但是不是很准确我们会说函数隐式转换为函数指针也是可以类比数组知道它的例外情况即不隐式转换的其中不能sizeofAdd因为默认函数是没有体积有些编译器为了防止报错会设置函数体积默认为1。那么编译器是怎么实现这个隐式转换的虽然写的是 Add但在编译阶段的表达式语义分析中Add 在大多数情况下会自动转换为函数指针其效果等价于 Add。函数指针变量的使用#includestdio.hintAdd(intx,inty){returnxy;}intmain(){int(*pf3)(int,int)Add;printf(%d\n,(*pf3)(2,3));printf(%d\n,pf3(3,5));return0;}答案是58。函数指针是否要*才能调用函数很明显上面一个例子中函数指针是否解引用都是可以调用函数的那么它们的逻辑是一样的吗并不是的我们看到pf3是函数指针解引用调用了函数看起来非常符合我们的惯性因为整型字符指针解引用可以找到对应的值并且函数指针解引用就是整个函数整个函数去调用这个函数好像合理。实际上并不是如果看过我写的函数栈帧的创建和销毁 的话,你会知道函数的调用实际上就是call 函数的地址就可以跳过去执行指令了所以这里我们调用函数只需要函数指针地址即可那么pf3直接调用是正确的。那么对pf3*之后为什么还能调用不是应该*之后就没有函数地址了吗实际上编译器会进行隐式转换把函数转换为函数指针所以这样的代码也是可以的(***pf3)(2, 3)语法上我们认为是这样的*之后编译器将函数转换为函数指针然后又*在转换一直循环转换但是实际执行中编译器很聪明有*的时候它会直接会忽略一直保持函数指针这个意义。两段代码帮你更好理解函数指针(*(void (*)())0)();我们会发现0前面有个括号里面有个函数指针变量的类型很显然这里是强制类型转换这里的意思就是将0强制类型转换为这个函数指针类型那么0这个地址指向一个函数表示call 0这个地址处可以调用类型为void 的函数。我们看到*这个地址表层意义上我们拿到了函数然后去调用了这个函数实际上去看我上面写的那个函数指针是否要*才能调用函数就知道了void ( *signal(int , void(*)(int)) )(int);我们先来看里面那一部分显然signal(int , void(*)(int))中void(*)(int)是一个函数栈帧其中显然正在声明一个函数那么声明还要有返回值那么其中剩下的void(*)(int)就是返回值由此又可以引出一个知识点在函数声明或者定义的时候如果返回值是函数指针类型那么变量名和参数要写在*旁边并且要用括起不能写出这样这是语法规定的void(*)(int) signal(int , void(*)(int))可以类比函数指针来理解记忆。typedef 关键字但是这样不太好看并且使用的时候也不太好理解有什么解决办法吗typedef unsigned int uint;//将unsigned int 重命名为uinttypedef int(*parr_t)[5];//新的类型名必须在*的右边typedef void(*pfun_t)(int);//新的类型名必须在*的右边typedef就成功的补救了理解困难的问题typedef void(*pfun_t)(int);pfun_t signal(int, pfun_t);函数指针数组同样的函数指针数组就是数组里面存的元素是函数指针。定义形式int (*parr1[3])();经过了多轮输入我们知道只要有…*…这种形式的*旁边一定是有变量名的其他附带在变量名周围的都要写在内部哪怕返回值是函数指针也要把变量名和附带的参数写在括号的的*旁边。函数指针数组的使用场景转移表计算器的一般实现#includestdio.hintadd(inta,intb){returnab;}intsub(inta,intb){returna-b;}intmul(inta,intb){returna*b;}intdiv(inta,intb){returna/b;}intmain(){intx,y;intinput1;intret0;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);printf(输入操作数);scanf(%d %d,x,y);switch(input){case1:retadd(x,y);break;case2:retsub(x,y);break;case3:retmul(x,y);break;case4:retdiv(x,y);break;case0:printf(退出程序\n);break;default:printf(选择错误\n);break;}printf(ret %d\n,ret);}while(input);return0;}我们发现这个代码其中一连串的ret…xy和case12都是相似重复代码是可以省略不要的。还有一点是最重要的就是这个代码可维护性太差了如果以后要加|等等运算符呢那么还要再往下写越写越长这样是不行的。#includestdio.hintadd(inta,intb){returnab;}intsub(inta,intb){returna-b;}intmul(inta,intb){returna*b;}intdiv(inta,intb){returna/b;}intmain(){intx,y;intinput1;intret0;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((input4input1)){printf(输⼊操作数);scanf(%d %d,x,y);ret(*p[input])(x,y);printf(ret %d\n,ret);}elseif(input0){printf(退出计算器\n);}else{printf(输⼊有误\n);}}while(input);return0;}这里有一个好处就是直接通过下标去调用对应的函数而不是说重复写固定的函数方便后期维护。转移表中的表就是函数指针数组数组将函数指针统一管理成表转移就是jump所以可以理解为跳转从mian函数通过下标索引转移跳转到addsub函数。
深入了解指针(4)
发布时间:2026/5/27 8:01:03
文章目录字符指针变量数组指针变量数组指针变量是什么数组指针变量怎么初始化二维数组与指针二维数组传参的本质二维数组中的暗含的退化函数指针变量函数指针变量的创建Add和Add函数指针变量的使用函数指针是否要\*才能调用函数两段代码帮你更好理解函数指针typedef 关键字函数指针数组函数指针数组的使用场景转移表这里是think的博客希望可以一起交流知识一起think今天我们来学习指针(4)吧一起来think吧今天我们将迎来指针模块里难度偏高的知识点放平心态一起稳步开启学习之旅。字符指针变量指针中有一种指针是字符指针即char*一般这样使用intmain(){charchw;char*pcch;*pca;return0;}但是还有一种使用方法intmain(){constchar*pstrhello world.;printf(%s\n,pstr);很多人下意识认为这里是把一个字符串放到pstr指针变量实际上不是的因为指针中只能存放一个地址那么这个hello world.存放在哪里呢实际上它存放在内存中的常量区中这个字符串是常量字符串pstr拿到的是这个常量字符串的首元素地址。接下来又会有一个问题字符指针和字符数组有什么差别呢接下来我们通过一个例子来明晰它们之间的差别。#includestdio.hintmain(){charstr1[]hello bit.;charstr2[]hello bit.;constchar*str3hello bit.;constchar*str4hello bit.;if(str1str2)printf(str1 and str2 are same\n);//1elseprintf(str1 and str2 are not same\n);///2if(str3str4)printf(str3 and str4 are same\n);//3elseprintf(str3 and str4 are not same\n);//4return0;}答案就是24。我们来分析一下首先str1和str2是两个不同的字符数组其中在编译期间编译器就用常量字符串hello bit.来初始化字符数组了而这两个字符数组在内存中栈区的所开辟的空间是不同的那么它们首元素的地址也不一样。其中常量字符串如果内容是一样的之后只会在常量区中存一份因为常量区里的数据是不能修改的故而str3和str4存的hello bit.的首元素地址是一样的。数组指针变量数组指针变量是什么我们讲过一个词的本质是最后一个那个名词同样类比可知字符指针和整型指针都是指针字符指针存放的是字符的地址指向的内容是字符整型指针存放的整型的地址指向的内容是整型那么数组指针也是指针存放的是数组的地址指向的内容是数组。怎么定义数组指针变量的类型int (*p2)[10];分析一下结构*说明的是p2是指针变量后面的[10]说明这个指针变量指向的是一个有10个元素的数组其中int说明这10个元素都是整型我们对比一下和指针数组对比一下int * p1[10];分析一下[ ]的优先级是比*高的所以p1先与[ ]结合说明这是一个有10个元素的数组然后再到前面的int*,说明了数组元素的类型是整型指针。所以我们明白了为什么数组指针要加就是因为[ ]优先级高为保证*先与p2结合先确定它的本质是指针确定了本质后其他的都是它的属性了。数组指针变量怎么初始化arr的时候arr不会退化它的意义依旧是整个数组那么后就得到了整个数组的地址用arr初始化即可int arr[10] {0};int(*p)[10] arr;VS中这个的调试中类型是int[10]*是优化过的实际上的数组指针的类型是去变量名后剩下的部分即int(*)[10]。二维数组与指针二维数组传参的本质#includestdio.hvoidtest(inta[3][5],intr,intc){inti0;intj0;for(i0;ir;i){for(j0;jc;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;}arr我们知道是首元素的地址而在二维数组又可以看作每个元素是一维数组的数组所以二维数组的首元素就是第一行的一维数组所以首元素的地址就是一维数组的地址。而数组指针就是指向一维数组的指针像上面的那个例子arr的类型就是int(*)[5]。#includestdio.hvoidtest(int(*p)[5],intr,intc){inti0;intj0;for(i0;ir;i){for(j0;jc;j){printf(%d ,*(*(pi)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;}这就是二维数组传参的本质传的是一个数组指针。二维数组中的暗含的退化二维数组中化为指针的本质的时候其实是有退化现象存在的如果理解了这个现象也就可以很轻松理解二维数组的索引了。我们知道arr[i][j]就是*(*(arri)j),那么其中arr就是第一行下标为0一维数组的地址那么加i就指向了下标为i的那一行数组那么解引用就得到了对应的那一行数组此时就退化为了那一行数组的首元素地址那么接下来的操作就是一维数组中的操作了。其中退不退化看表达式的逻辑是需要整个数组还是首元素地址明显这里需要首元素地址继续去找元素的值。函数指针变量函数指针变量的创建#includestdio.hvoidtest(){printf(hehe\n);}intmain(){printf(test: %p\n,test);printf(test: %p\n,test);return0;}我们发现test和test都是地址说明函数是有地址的并且两个地址是一样的说明取函数地址可取也可以不取。test: 005913CAtest: 005913CAvoidtest(){printf(hehe\n);}void(*pf1)()test;void(*pf2)()test;intAdd(intx,inty){returnxy;}int(*pf3)(int,int)Add;int(*pf3)(intx,inty)Add;//x和y写上或者省略都是可以的我们知道函数指针就是指向函数的指针其中类比数组指针我们可以知道函数指针的写法中间有个*来说明pf1是指针后面写函数形参类型变量可以不写前面写返回值。函数指针变量的类型就是去掉变量名如int (*) (int x, int y)函数类型是int (int x,int y)。Add和Add我们知道Add和Add都可以得到函数的地址有的时候Add是整个函数的意思后就得到了整个函数的地址而为什么单独的Add都可以得到地址呢可以类比到数组中退化但是在函数中我们一般会说这是隐式转换我们口头也说是退化但是不是很准确我们会说函数隐式转换为函数指针也是可以类比数组知道它的例外情况即不隐式转换的其中不能sizeofAdd因为默认函数是没有体积有些编译器为了防止报错会设置函数体积默认为1。那么编译器是怎么实现这个隐式转换的虽然写的是 Add但在编译阶段的表达式语义分析中Add 在大多数情况下会自动转换为函数指针其效果等价于 Add。函数指针变量的使用#includestdio.hintAdd(intx,inty){returnxy;}intmain(){int(*pf3)(int,int)Add;printf(%d\n,(*pf3)(2,3));printf(%d\n,pf3(3,5));return0;}答案是58。函数指针是否要*才能调用函数很明显上面一个例子中函数指针是否解引用都是可以调用函数的那么它们的逻辑是一样的吗并不是的我们看到pf3是函数指针解引用调用了函数看起来非常符合我们的惯性因为整型字符指针解引用可以找到对应的值并且函数指针解引用就是整个函数整个函数去调用这个函数好像合理。实际上并不是如果看过我写的函数栈帧的创建和销毁 的话,你会知道函数的调用实际上就是call 函数的地址就可以跳过去执行指令了所以这里我们调用函数只需要函数指针地址即可那么pf3直接调用是正确的。那么对pf3*之后为什么还能调用不是应该*之后就没有函数地址了吗实际上编译器会进行隐式转换把函数转换为函数指针所以这样的代码也是可以的(***pf3)(2, 3)语法上我们认为是这样的*之后编译器将函数转换为函数指针然后又*在转换一直循环转换但是实际执行中编译器很聪明有*的时候它会直接会忽略一直保持函数指针这个意义。两段代码帮你更好理解函数指针(*(void (*)())0)();我们会发现0前面有个括号里面有个函数指针变量的类型很显然这里是强制类型转换这里的意思就是将0强制类型转换为这个函数指针类型那么0这个地址指向一个函数表示call 0这个地址处可以调用类型为void 的函数。我们看到*这个地址表层意义上我们拿到了函数然后去调用了这个函数实际上去看我上面写的那个函数指针是否要*才能调用函数就知道了void ( *signal(int , void(*)(int)) )(int);我们先来看里面那一部分显然signal(int , void(*)(int))中void(*)(int)是一个函数栈帧其中显然正在声明一个函数那么声明还要有返回值那么其中剩下的void(*)(int)就是返回值由此又可以引出一个知识点在函数声明或者定义的时候如果返回值是函数指针类型那么变量名和参数要写在*旁边并且要用括起不能写出这样这是语法规定的void(*)(int) signal(int , void(*)(int))可以类比函数指针来理解记忆。typedef 关键字但是这样不太好看并且使用的时候也不太好理解有什么解决办法吗typedef unsigned int uint;//将unsigned int 重命名为uinttypedef int(*parr_t)[5];//新的类型名必须在*的右边typedef void(*pfun_t)(int);//新的类型名必须在*的右边typedef就成功的补救了理解困难的问题typedef void(*pfun_t)(int);pfun_t signal(int, pfun_t);函数指针数组同样的函数指针数组就是数组里面存的元素是函数指针。定义形式int (*parr1[3])();经过了多轮输入我们知道只要有…*…这种形式的*旁边一定是有变量名的其他附带在变量名周围的都要写在内部哪怕返回值是函数指针也要把变量名和附带的参数写在括号的的*旁边。函数指针数组的使用场景转移表计算器的一般实现#includestdio.hintadd(inta,intb){returnab;}intsub(inta,intb){returna-b;}intmul(inta,intb){returna*b;}intdiv(inta,intb){returna/b;}intmain(){intx,y;intinput1;intret0;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);printf(输入操作数);scanf(%d %d,x,y);switch(input){case1:retadd(x,y);break;case2:retsub(x,y);break;case3:retmul(x,y);break;case4:retdiv(x,y);break;case0:printf(退出程序\n);break;default:printf(选择错误\n);break;}printf(ret %d\n,ret);}while(input);return0;}我们发现这个代码其中一连串的ret…xy和case12都是相似重复代码是可以省略不要的。还有一点是最重要的就是这个代码可维护性太差了如果以后要加|等等运算符呢那么还要再往下写越写越长这样是不行的。#includestdio.hintadd(inta,intb){returnab;}intsub(inta,intb){returna-b;}intmul(inta,intb){returna*b;}intdiv(inta,intb){returna/b;}intmain(){intx,y;intinput1;intret0;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((input4input1)){printf(输⼊操作数);scanf(%d %d,x,y);ret(*p[input])(x,y);printf(ret %d\n,ret);}elseif(input0){printf(退出计算器\n);}else{printf(输⼊有误\n);}}while(input);return0;}这里有一个好处就是直接通过下标去调用对应的函数而不是说重复写固定的函数方便后期维护。转移表中的表就是函数指针数组数组将函数指针统一管理成表转移就是jump所以可以理解为跳转从mian函数通过下标索引转移跳转到addsub函数。