C语言——深度理解指针(4)

C语言——深度理解指针(4)
1. 字符指针变量在指针的类型中我们知道有⼀种指针类型为字符指针 char*⼀般使⽤:intmain(){charchw;char*pcch;*pcw;return0;}还有⼀种使⽤⽅式如下intmain(){char*pstrhello.;//这⾥是把⼀个字符串放到pstr指针变量⾥了吗不是放的是h的地址printf(%s\n,pstr);//%s打印的是字符串当拿到首元素的地址的时候会依次往后打印直到遇到\0printf(%c\n,*pstr);//这才是打印一个字符return0;}上面代码中是把⼀个字符串放到pstr指针变量⾥了吗其实不是的他的本质是把字符串hello⾸字符h的地址放到了pstr中。下面我们看一段有趣的代码判断str1和str2、str3和str4是否相同#includestdio.hintmain(){charstr1[]hello;charstr2[]hello;constchar*str3hello;constchar*str4hello;if(str1str2)printf(str1 and str2 are same\n);elseprintf(str1 and str2 are not same\n);if(str3str4)printf(str3 and str4 are same\n);elseprintf(str3 and str4 are not same\n);return0;}答案是str1和str2不相同、str3和str4相同。原因是这⾥str3和str4指向的是⼀个同⼀个常量字符串。C/C会把常量字符串存储到单独的⼀个内存区域当⼏个指针指向同⼀个字符串的时候他们实际会指向同⼀块内存。但是⽤相同的常量字符串去初始化不同的数组的时候就会开辟出不同的内存块。所以str1和str2不同str3和str4相同2. 数组指针变量2.1 数组指针变量是什么之前我们学习了指针数组指针数组是⼀种数组数组中存放的是地址指针。我们已经熟悉• 整形指针变量 int * pint; 存放的是整形变量的地址能够指向整形数据的指针。• 浮点型指针变量 float * pf; 存放浮点型变量的地址能够指向浮点型数据的指针。• 同理数组指针变量应该是存放的应该是数组的地址能够指向数组的指针变量。数组指针变量int(*p)[10]解释 p 先和 * 结合说明 p 是⼀个指针变量然后指针指向的是⼀个⼤⼩为10个整型的数组。所以 p 是⼀个指针指向⼀个数组叫 数组指针注意不可以写成下面的这种情况int*p[10]//这表示一个指针数组**intp[10] 与 int (p)[10] 的区别int *p[10]指针数组10 个指针元素。int (*p)[10]数组指针一个指针指向包含 10 个 int 的数组这⾥要注意 [] 的优先级要⾼于 * 号的所以必须加上 () 来保证 p 先和 * 结合。2.2 数组指针变量怎么初始化数组指针变量是⽤来存放数组地址的那数组地址是什么呢就是上一篇我们说的**数组名**intarr[10]{0};arr;//得到的就是数组的地址int(*p)[10]arr;//这就是数组指针变量的初始化数组指针类型解析理解指针类型int* p的指针类型int*intarr[10]指针类型:int[10]同理数组指针变量int(p)[10]的指针类型就是int()[10]总结对于简单的指针声明来说去掉指针变量的函数名剩下的就是指针类型3. ⼆维数组传参的本质⼆维数组的需要传参给⼀个函数的时候我们之前这样写的#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;}这⾥实参是⼆维数组形参也写成⼆维数组的形式那还有什么其他的写法吗⾸先我们再次理解⼀下⼆维数组⼆维数组其实可以看做是每个元素是⼀维数组的数组也就是⼆维数组的每个元素是⼀个⼀维数组。那么⼆维数组的⾸元素就是第⼀⾏是个⼀维数组。如下图所以根据数组名是数组⾸元素的地址这个规则⼆维数组的数组名表⽰的就是第⼀⾏的地址是⼀维数组的地址。根据上⾯的例⼦第⼀⾏的⼀维数组的类型就是 int [5] 所以第⼀⾏的地址的类型就是数组指针类型int(*)[5]。那就意味着⼆维数组传参本质上也是传递了地址传递的是第⼀⾏这个⼀维数组的地址那么形参也是可以写成指针形式的。如下#includestdio.hvoidtest(int(*p)[5],intr,intc)//这里的形参是指针类型{inti0;intj0;for(i0;ir;i){for(j0;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}};test(arr,3,5);return0;}总结⼆维数组传参形参的部分可以写成数组也可以写成指针形式。4. 函数指针变量4.1 函数指针变量的创建什么是函数指针变量呢用前面指针变量的类比来说函数指针变量就是⽤来存放函数地址的未来通过地址能够调⽤函数的。且和数组一样函数名就是函数的地址当然也可以通过 函数名 的⽅式获得函数的地址。如果我们要将函数的地址存放起来就得创建函数指针变量咯函数指针变量的写法其实和数组指针⾮常类似。如下voidtest(){printf(hehe\n);}void(*pf1)()test;void(*pf2)()test;intAdd(intx,inty){returnxy;}int(*pf3)(int,int)Add;int(*pf3)(intx,inty)Add;/函数指针类型解析4.2 函数指针变量的使⽤通过函数指针调⽤指针指向的函数。#includestdio.hintAdd(intx,inty){returnxy;}intmain(){int(*pf3)(int,int)Add;printf(%d\n,(*pf3)(2,3));printf(%d\n,pf3(3,5));//pf3和(*pf3)等价return0;}当使用(*pf3)时解引用操作得到的仍然是函数地址即pf3本身因此两种写法最终都指向同一个函数入口4.3 两段有趣的代码1、(*(void(*)())0)();1.上述代码其实是⼀次函数调⽤调⽤的是0地址处的⼀个函数这个函数没有参数没有返回值。2. 代码中的 void ()() 是函数指针类型 (void ()())0 类型放在括号中意思是强制类型转化是将0这个整型值强制类型转化成这种函数指针类型也就是说0被当做函数的地址了。3.(void ()())0 前⾯加⼀个 * 就是调⽤0地址处的这个函数根据函数指针的类型能知道这个函数没有参数也没有返回值。2、void(*signal(int,void(*)(int)))(int)1.上述代码是⼀次函数的声明。2. 声明的函数名字叫 signal 函数的参数有2个第⼀个是int类型第⼆个是函数指针类型: void()(int) 。 signal 函数的返回值类型也是函数指针类型 void()(int)4.3.1 typedef 关键字typedef 是⽤来类型重命名的可以将复杂的类型简单化。⽐如你觉得 unsigned int 写起来不⽅便如果能写成 uint 就⽅便多了那么我们可以使⽤typedefunsignedintuint;//将unsigned int 重命名为uint而且指针也是可以类型重命名的按照上面的类比将 int* 重命名为 ptr_t ,可以这样写typedefint*ptr_t但是对于数组指针和函数指针稍微有点区别⽐如我们有数组指针类型int(*)[5] ,需要重命名为 parr_t要这样写typedefint(*parr_t)[5];//新的类型名必须在*的右边typedefint(*)[5]parr_t//这种写法是错误的函数指针类型的重命名也是⼀样的⽐如将 void(*)(int) 类型重命名为 pf_t,就可以这样写typedefvoid(*pfun_t)(int);//新的类型名必须在*的右边5. 函数指针数组把函数的地址存到⼀个数组中那这个数组就叫函数指针数组那函数指针的数组如何定义呢int(*parr1[3])();int*parr2[3]();int(*)()parr3[3];int(*p[4])(intx,inty){add,sub,mul,div};//p[4]就是我们定义的数组后面(int x, int y)就是我们要给函数的传参值而{ add, sub, mul, div }里面放的就是数组名6. 转移表计算器的⼀般实现#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_s(%d,input);switch(input)//选择进行什么运算{case1:printf(输⼊操作数);scanf_s(%d %d,x,y);//输入值retadd(x,y);printf(ret %d\n,ret);break;case2:printf(输⼊操作数);scanf_s(%d %d,x,y);//输入值retsub(x,y);printf(ret %d\n,ret);break;case3:printf(输⼊操作数);scanf_s(%d %d,x,y);//输入值retmul(x,y);printf(ret %d\n,ret);break;case4:printf(输⼊操作数);scanf_s(%d %d,x,y);//输入值retdiv(x,y);printf(ret %d\n,ret);break;case0:printf(退出程序\n);break;default:printf(选择错误\n);break;}}while(input);return0;}我们发现case部分写的太麻烦了好多内容都是相似的可以化简#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);//exit为退出程序printf(*************************\n);printf(请选择);scanf(%d,input);if((input4input1))//选择进行运算{printf(输⼊操作数);scanf(%d %d,x,y);ret(*p[input])(x,y);//*p[input])(x, y)通过函数地址来调用函数printf(ret %d\n,ret);}elseif(input0)//退出运算{printf(退出计算器\n);}else//输入错误{printf(输⼊有误\n);}}while(input);return0;}完.神秘力量觉醒一键三连触发隐藏buff博主可能突然闪现评论区对你发射爱心光波~