一、指针概念
1.1 内存和地址
在开始学习指针前,我们先来讲一个例子,假如你身处一栋楼中,你点了一份外卖,那么,外卖员如何能找到你?有两种方法。法一:直接一间一间找,这样做不仅消耗时间长,而且效率低。法二:把房间编号,然后告诉外卖员房间号即可。⽣活中,每个房间有了房间号,就能提⾼效率,能快速的找到房间。
生活中如此,计算机中亦如此。计算机为了实现对内存的高效管理,是把内存划分为⼀个个的内存单元,每个内存单元的⼤⼩取1个字节。其中,每个内存单元,相当于⼀个房间,⼀ 个⼈字节空间⾥⾯能放8个⽐特位,就好⽐一个房间住八个人,每个⼈是⼀个⽐特位。 每个内存单元也都有⼀个编号(这个编号就相当 于宿舍房间的⻔牌号),有了这个内存单元的编 号,CPU就可以快速找到⼀个内存空间。
⽣活中我们把⻔牌号也叫地址,在计算机中我们 把内存单元的编号也称为地址。C语⾔中给地址起 了新的名字叫:指针。
所以我们可以理解为: 内存单元的编号 == 地址 == 指针
1.2 指针变量和地址
理解了以上概念,我们再回到C语⾔,在C语⾔中创建变量其实就是向内存申请空间。
那我们怎么拿到地址呢?那就不得不拿出一个操作符了——&(取地址操作符),它的作用便是拿出地址。
1. int a = 4; 2. printf("%p", &a);
这样便可打印出a的地址。
1.3 指针的类型
那么,指针都有哪些类型呢?
int *ptr; // 声明一个指向整数的指针
char *ptr; // 声明一个指向字符的指针
double *ptr; // 声明一个指向双精度浮点数的指针
总之,和我们以前学习变量类型时差不多,那我们该如何判断指针类型呢?
从语法的角度看,你只要把指针声明语句里的指针名字去掉,剩下的部分就是这个指针的类型。这是指针本身所具有的类型。让我们看看各个指针的类型:
int*ptr;//指针的类型是int*
char*ptr;//指针的类型是char*
int**ptr;//指针的类型是int**
int(*ptr)[3];//指针的类型是int(*)[3]
int*(*ptr)[4];//指针的类型是int*(*)[4]
怎么样?找出指针的类型的方法是不是很简单?
1.4 指针变量和解引⽤操作符(*)
那我们通过取地址操作符(&)拿到的地址是⼀个数值,⽐如:0x006FFD70,这个数值有时候也是需要 存储起来,⽅便后期再使⽤的,那我们把这样的地址值存放在哪⾥呢?答案是:指针变量中。
1. int a = 4; 2. int* p = &a;
指针变量也是⼀种变量,这种变量就是⽤来存放地址的,存放在指针变量中的值都会理解为地址。
我们把地址保存好以后,我们该如何使用呢?大家想象一下:我们如何把锁住门打开,答案很简单,使用钥匙即可。同理,我们如何使用地址,就要找出与之匹配的“钥匙”那就是叫解引⽤操作符(*)。
1. int a = 4; 2. int* p = &a; 3. *p = 0;
以上代码,使用了*操作符,什么意思呢?即*pa 的意思就是通过pa中存放的地址,找到指向的空间, *pa其实就是a变量了;所以*pa = 0,这个操作符是把a改成了0。
1.5 指针的大小
初步了解了使用后,我们肯定要了解一下指针的大小,那指针的大小和什么有关呢?大家可以先猜测一下,我们随后解密。
1. #include<stdio.h> 2. int main() 3. { 4. printf("%zd\n", sizeof(char*)); 5. printf("%zd\n", sizeof(short*)); 6. printf("%zd\n", sizeof(int*)); 7. printf("%zd\n", sizeof(double*)); 8. return 0; 9. }
答案如下:
x64环境下输出结果
x86环境下输出结果
总结:
32位平台下地址是32个bit位,指针变量⼤⼩是4个字节
64位平台下地址是64个bit位,指针变量⼤⼩是8个字节
注意指针变量的⼤⼩和类型是⽆关的,只要指针类型的变量,在相同的平台下,⼤⼩都是相同的。
1.6 指针变量类型的意义
调试以下代码,观察内存变化。
1. #include <stdio.h> 2. int main() 3. { 4. int n = 0x11223344; 5. int *pi = &n; 6. *pi = 0; 7. return 0; 8. }//代码一
1. #include <stdio.h> 2. int main() 3. { 4. int n = 0x11223344; 5. char *pi = &n; 6. *pi = 0; 7. return 0; 8. }//代码二
调试我们可以看到,代码1会将n的4个字节全部改为0,但是代码2只是将n的第⼀个字节改为0。
结论:
指针的类型决定了,对指针解引⽤的时候有多⼤的权限(⼀次能操作⼏个字节)。
1.7 野指针
概念: 野指针就是指针指向的位置是不可知的(随机的、不正确的、没有明确限制的)
形成原因:
1. 指针未初始化
1. #include <stdio.h> 2. int main() 3. { 4. int *p;//局部变量指针未初始化,默认为随机值 5. *p = 20; 6. return 0; 7. }//此代码在VS上会报错
2.指针越界访问
1. #include<stdio.h> 2. int main() 3. { 4. int arr[10] = { 0 }; 5. int* p = arr; 6. for (int i = 0; i < 11; i++) 7. { 8. //当指针指向的范围超出数组arr的范围时,p就是野指针 9. *p = arr[i]; 10. p++; 11. } 12. return 0; 13. }
3.指针指向的空间释放
1. #include<stdio.h> 2. int* test() 3. { 4. int n = 10; 5. return &n; 6. } 7. int main() 8. { 9. int* p = test(); 10. return 0; 11. }
那该如何避免指针变成野指针呢?
1.指针初始化,明确指针指向哪里。如若不知道可以赋值为NULL(NULL 是C语⾔中定义的⼀个标识符常量,值是0,0也是地址,这个地址是⽆法使⽤的,读写该地址 会报错。)
2.⼩⼼指针越界,不要越界访问。
3.指针变量不再使⽤时,及时置NULL,指针使⽤之前检查有效性。
4.避免返回局部变量的地址。
1.8 const修饰
变量是可以修改的,如果把变量的地址交给⼀个指针变量,通过指针变量的也可以修改这个变量。 但是如果我们希望⼀个变量加上⼀些限制,不能被修改,怎么做呢?这就是const的作用。变量中可以这样使用,指针亦是如此。
1. #include<stdio.h> 2. void test1() 3. { 4. int n = 10; 5. int m = 20; 6. int* p = &n; 7. *p = 20;//ok? 8. p = &m; //ok? 9. } 10. void test2() 11. { 12. int n = 10; 13. int m = 20; 14. const int* p = &n; 15. *p = 20;//ok? 16. p = &m; //ok? 17. } 18. void test3() 19. { 20. int n = 10; 21. int m = 20; 22. int* const p = &n; 23. *p = 20;//ok? 24. p = &m; //ok? 25. } 26. void test4() 27. { 28. int n = 10; 29. int m = 20; 30. const int*const p = &n; 31. *p = 20;//ok? 32. p = &m; //ok? 33. } 34. int main() 35. { 36. test1();//无const修饰 37. test2();//const在*的左边 38. test3();//const在*的右边 39. test4();//const在*的两边 40. return 0; 41. }
大家可自行在VS上验证,观察是否会报错。
这里直接说结论:
const如果放在*的左边,修饰的是指针指向的内容,保证指针指向的内容不能通过指针来改变。 但是指针变量本⾝的内容可变。
const如果放在*的右边,修饰的是指针变量本⾝,保证了指针变量的内容不能修改,但是指针指 向的内容,可以通过指针改变。
大家可以这样理解:假如你带你女朋友去逛街,突然想吃凉皮,你要是想守住你的钱袋子,就在左边加const,这样你的钱袋子就保住了,但是你的女朋友可能会想,连个凉皮都不给我,我要换男朋友,然后你就没女朋友了。所幸刚刚是做梦,但是不幸的是,你女朋友现在就是要吃凉皮,为了避免上一次情况的出现,把*放入了const右边,给她了一碗凉皮,这样她就不会换男朋友了。就在你为余额清零心疼不已时,你又醒了,原来又是一个梦,你女朋友又要吃凉皮,你重生归来,顿时恼怒,结合前两世方法,在const两边都加*,这样你既不用花钱,有能保住女朋友,这才是重生文男主。(仅当帮助记忆,无其他不良引导)
1.9 二级指针
指针变量也是变量,是变量就有地址,那指针变量的地址存放在哪⾥?
答案是:二级指针。
1. #include<stdio.h> 2. int main() 3. { 4. int a = 10; 5. int* p = &a; 6. int** pa = p; 7. return 0; 8. }
对于⼆级指针的运算有:
*pa 通过对pa中的地址进⾏解引⽤,这样找到的是 p , *pa 其实访问的就是 p 。
**pa 先通过 *pa 找到 p ,然后对 p 进⾏解引⽤操作: *pa ,那找到的是 a 。
我觉得可以把多级指针想象成多级导数或者剥洋葱,逐渐抽丝剥茧,最后寻到本质。
二、指针运算
指针的基本运算有三种,分别是:
- 指针+- 整数
- 指针-指针
- 指针的关系运算
2.1 指针+- 整数
因为数组在内存中是连续存放的,只要知道第⼀个元素的地址,顺藤摸⽠就能找到后⾯的所有元素。
1. int main() 2. { 3. int arr[10] = { 0 }; 4. int* p = &arr[0]; 5. int sz = sizeof(arr) / sizeof(arr[0]); 6. for (int i = 0; i < sz; i++) 7. { 8. printf("%d ", *(p + i)); 9. } 10. return 0; 11. }
2.2 指针-指针
1. int my_strlen(char* s) 2. { 3. char* p = s; 4. while (*p != '\0') 5. p++; 6. return p - s; 7. } 8. int main() 9. { 10. printf("%d\n", my_strlen("abcd")); 11. return 0; 12. }
2.3 指针的关系运算
1. int main() 2. { 3. int arr[10] = { 1,2,3,4,5,6,7,8,9,10 }; 4. int* p = &arr; 5. int i = 0; 6. int sz = sizeof(arr) / sizeof(arr[0]); 7. while (p < arr + sz) 8. { 9. printf("%d ", *p); 10. p++; 11. } 12. return 0; 13. }
三、指针与数组
3.1 数组名理解
在学习函数时,我们要传数组时,总是写的是数组名。当你看到这时,或许会好奇数组名是什么?用以下代码来演示:
1. int main() 2. { 3. int arr[10] = { 1,2,3,4,5,6,7,8,9,10 }; 4. printf("&arr[0] = %p\n", &arr[0]); 5. printf("arr = %p\n", arr); 6. return 0; 7. return 0; 8. }
运行结果如下:
我们发现它们的地址一样,所以我们得出如下结论:
数组名是地址并且是数组首元素的地址。
这里强调两个例外:
• sizeof(数组名),sizeof中单独放数组名,这⾥的数组名表⽰整个数组,计算的是整个数组的⼤⼩, 单位是字节
• &数组名,这⾥的数组名表⽰整个数组,取出的是整个数组的地址(整个数组的地址和数组⾸元素 的地址是有区别的)
那么,arr和&arr有什么区别呢?那可以试试如下代码:
1. #include <stdio.h> 2. int main() 3. { 4. int arr[10] = { 1,2,3,4,5,6,7,8,9,10 }; 5. printf("arr = %p\n", arr); 6. printf("arr+1 = %p\n", arr + 1); 7. printf("&arr = %p\n", &arr); 8. printf("&arr+1 = %p\n", &arr + 1); 9. return 0;
这⾥我们发现&arr[0]和&arr[0]+1相差4个字节,arr和arr+1 相差4个字节,是因为&arr[0] 和 arr 都是 ⾸元素的地址,+1就是跳过⼀个元素。 但是&arr 和 &arr+1相差40个字节,这就是因为&arr是数组的地址,+1 操作是跳过整个数组的。
3.2使⽤指针访问数组
有了前⾯知识的⽀持,再结合数组的特点,我们就可以很⽅便的使⽤指针访问数组了。
1. #include <stdio.h> 2. int main() 3. { 4. int arr[10] = { 1,2,3,4,5,6,7,8,9,10 }; 5. int* p = arr; 6. int sz = sizeof(arr) / sizeof(arr[0]); 7. for (int i = 0; i < sz; i++) 8. { 9. printf("%d ", (*p + i)); 10. } 11. return 0; 12. }
这个代码搞明⽩后,我们再试⼀下,如果我们再分析⼀下,数组名arr是数组⾸元素的地址,可以赋值 给p,其实数组名arr和p在这⾥是等价的。那我们可以使⽤arr[i]可以访问数组的元素,那p[i]是否也可 以访问数组呢?
1. #include <stdio.h> 2. int main() 3. { 4. int arr[10] = { 1,2,3,4,5,6,7,8,9,10 }; 5. int* p = arr; 6. int sz = sizeof(arr) / sizeof(arr[0]); 7. for (int i = 0; i < sz; i++) 8. { 9. printf("%d ", p[i]); 10. } 11. return 0; 12. }
我们可以得出:arr[i]等价于*(arr+i)。
3.3 一维数组传参本质
我们在学习函数时,也传递过数组。那么,我们到底传过去的是什么呢?
1. #include <stdio.h> 2. void test(int arr[]) 3. { 4. int sz = sizeof(arr) / sizeof(arr[0]); 5. printf("%d", sz); 6. } 7. int main() 8. { 9. int arr[10] = { 1,2,3,4,5,6,7,8,9,10 }; 10. test(arr); 11. return 0; 12. }
结果如下:
这就要学习数组传参的本质了,说本质上数组传参本质上传递的是数组⾸元素的地址。
所以函数形参的部分理论上应该使⽤指针变量来接收⾸元素的地址。那么在函数内部我们写 sizeof(arr) 计算的是⼀个地址的⼤⼩(单位字节)⽽不是数组的⼤⼩(单位字节)。正是因为函 数的参数部分是本质是指针,所以在函数内部是没办法求的数组元素个数的。
总结:⼀维数组传参,形参的部分可以写成数组的形式,也可以写成指针的形式。
3.4 指针数组
指针数组是指针还是数组? 我们类⽐⼀下,整型数组,是存放整型的数组,字符数组是存放字符的数组。 那指针数组呢?是存放指针的数组。
指针数组的每个元素都是⽤来存放地址(指针)的。
指针数组的每个元素是地址,⼜可以指向⼀块区域。
3.5 指针数组模拟⼆维数组
我们知道 ,二维数组在内存里是连续存放的。
pa[i]是访问pa数组的元素,pa[i]找到的数组元素指向了整型⼀维数组,pa[i][j]就是整型⼀维数 组中的元素。
1. #include <stdio.h> 2. int main() 3. { 4. int arr1[] = { 1,2,3,4,5 }; 5. int arr2[] = { 6,7,8,9,10 }; 6. int arr3[] = { 11,12,13,14,15 }; 7. //数组名是数组⾸元素的地址,类型是int*的,就可以存放在pa数组中 8. int* pa[3] = { arr1, arr2, arr3 }; 9. int i = 0; 10. int j = 0; 11. for (i = 0; i < 3; i++) 12. { 13. for (j = 0; j < 5; j++) 14. { 15. printf("%d ", pa[i][j]); 16. } 17. printf("\n"); 18. } 19. return 0; 20. }
3.6 数组指针变量
前面我们已经学习过了指针数组,指针数组是把指针放到一个数组中,那么,数组指针变量是指针变量?还是数组?
答案是:指针变量。
我们已经熟悉:
• 整形指针变量: int * pint; 存放的是整形变量的地址,能够指向整形数据的指针。
• 浮点型指针变量: float * pf; 存放浮点型变量的地址,能够指向浮点型数据的指针。
那数组指针变量应该是:存放的应该是数组的地址,能够指向数组的指针变量。
1. int* p1[10]; 2. int (*p2)[10];
大家可以先猜测一下p1和p2分别是什么?
数组指针变量
int (*p)[10];
解释:p先和*结合,说明p是⼀个指针变量变量,然后指着指向的是⼀个⼤⼩为10个整型的数组。所以 p是⼀个指针,指向⼀个数组,叫 数组指针。 这⾥要注意:[]的优先级要⾼于*号的,所以必须加上()来保证p先和*结合。
那问题来了,那该这么初始化?数组指针本质是什么?答案是指针。指针是用来干什么的?答案是存放地址?那数组指针是不是可以理解为:存放数组的地址。所以:如果要存放个数组的地址,就得存放在数组指针变量中。
1. int arr[10] = {0}; 2. int (*p)[10] = &arr;
通过调试,我们发现&arr与p的类型一致,证明我们的结论是正确的。
数组指针类型解析:
1. int(*p)[10] = &arr; 2. | | | 3. | | | 4. | | p指向数组的元素个数 5. | p是数组指针变量名 6. p指向的数组的元素类型
3.7 二维数组传参本质
学习到这里我们就可以解释二维数组传参本质。当时我们代码如下:
1. 2. #include <stdio.h> 3. int main() 4. { 5. int arr1[] = { 1,2,3,4,5 }; 6. int arr2[] = { 6,7,8,9,10 }; 7. int arr3[] = { 11,12,13,14,15 }; 8. //数组名是数组⾸元素的地址,类型是int*的,就可以存放在pa数组中 9. int* pa[3] = { arr1, arr2, arr3 }; 10. int i = 0; 11. int j = 0; 12. for (i = 0; i < 3; i++) 13. { 14. for (j = 0; j < 5; j++) 15. { 16. printf("%d ", pa[i][j]); 17. } 18. printf("\n"); 19. } 20. return 0; 21. } 22. 23.
这⾥实参是⼆维数组,形参也写成⼆维数组的形式,那还有什么其他的写法吗?
⾸先我们再次理解⼀下⼆维数组,⼆维数组起始可以看做是每个元素是⼀维数组的数组,也就是⼆维 数组的每个元素是⼀个⼀维数组。那么⼆维数组的⾸元素就是第⼀⾏,是个⼀维数组。
所以,根据数组名是数组⾸元素的地址这个规则,⼆维数组的数组名表⽰的就是第⼀⾏的地址,是⼀ 维数组的地址。根据上⾯的例⼦,第⼀⾏的⼀维数组的类型就是 int [5] ,所以第⼀⾏的地址的类 型就是数组指针类型 int(*)[5] 。那就意味着⼆维数组传参本质上也是传递了地址,传递的是第⼀ ⾏这个⼀维数组的地址,那么形参也是可以写成指针形式的。如下:
1. #include<stdio.h> 2. void test(int *p[5],int a,int b) 3. { 4. for (int i = 0; i < a; i++) 5. { 6. for (int j = 0; j < b; j++) 7. { 8. printf("%d", *((*p+i)+j)); 9. } 10. } 11. } 12. int main() 13. { 14. int arr[3][5] = { {1,2,3,4,5}, {2,3,4,5,6},{3,4,5,6,7} }; 15. test(arr, 3, 5); 16. return 0; 17. }
总结:⼆维数组传参,形参的部分可以写成数组,也可以写成指针形式。
四、指针与函数
4.1 函数指针变量的创建
什么是函数指针呢?通过前面的学习我们不难知道。函数指针变量应该是⽤来存放函数地址的,未来通过地址能够调⽤函数的。
那我们不禁要问?函数真的有地址吗?我们来代码验证一下:
1. #include<stdio.h> 2. void test() 3. { 4. ; 5. } 6. int main() 7. { 8. test(); 9. printf("%p\n", test); 10. printf("%p\n", &test); 11. return 0; 12. }
确实打印出来了地址,所以函数是有地址的,函数名就是函数的地址,当然也可以通过 &函数名 的⽅ 式获得函数的地址。
如果我们要将函数的地址存放起来,就得创建函数指针变量咯,函数指针变量的写法其实和数组指针 ⾮常类似。
1. #include<stdio.h> 2. void test() 3. { 4. ; 5. } 6. int add(int x, int y) 7. { 8. return x + y; 9. } 10. int main() 11. { 12. int x = 3; 13. int y = 4; 14. test(); 15. int ret = add(x, y); 16. void(*p)() = test; 17. int(*p1)(int, int) = add;//里面有无x,y都一样 18. return 0; 19. }
函数指针类型解析:
1. int (*pf3) (int x, int y) 2. | | ------------ 3. | | | 4. | | pf3指向函数的参数类型和个数的交代 5. | 函数指针变量名 6. pf3指向函数的返回类型 7. int (*) (int x, int y) //pf3函数指针变量的类型
4.2 函数指针变量的使⽤
通过函数指针调⽤指针指向的函数。
1. #include<stdio.h> 2. int add(int x, int y) 3. { 4. return x + y; 5. } 6. int main() 7. { 8. int x = 2; 9. int y = 3; 10. int ret = add(x, y); 11. int(*p1)(int, int) = add; 12. printf("%d\n", p1(2,3)); 13. printf("%d\n", (*p1)(2, 3)); 14. printf("%d\n",ret); 15. return 0; 16. }
4.3 typedef关键字
typedef 是⽤来类型重命名的,可以将复杂的类型,简单化。
像unsigned int 你觉得写得麻烦,你就可以把它定义成unit。
typedef unsigned int unit;
如果是指针类型,能否重命名呢?其实也是可以的。
typedef int* pt_t;
但是对于数组指针和函数指针稍微有点区别: ⽐如我们有数组指针类型 int(*)[5] ,需要重命名为 parr_t ,那可以这样写:
typedef int(*parr_t)[5];//parr_t必须放入*右边
函数指针类型的重命名也是⼀样的,⽐如,将 void(*)(int) 类型重命名为 pf_t ,就可以这样写:
1. 2. typedef void(*pf_t)(int);//parr_t必须放入*右边
4.4 函数指针数组
数组是⼀个存放相同类型数据的存储空间,那要把函数的地址存到⼀个数组中,那这个数组就叫函数指针数组,那函数指针的数组如何定义呢?如下:
int (*p[10])();
p 先和 [] 结合,说明 parr1是数组,数组的内容是什么呢? 是 int (*)() 类型的函数指针。
五、转移表
函数指针数组的⽤途:转移表。
举例:计算器的⼀般实现:
在没学习函数指针数组之前:
1. #include<stdio.h> 2. void menu() 3. { 4. printf("*************************\n"); 5. printf(" *****1:add 2:sub *******\n"); 6. printf(" *****3:mul 4:div *******\n"); 7. printf(" *******0:exit **********\n"); 8. printf("*************************\n"); 9. printf("请选择:"); 10. } 11. int add(int x, int y) 12. { 13. return x + y; 14. } 15. int sub(int x, int y) 16. { 17. return x - y; 18. } 19. int mul(int x, int y) 20. { 21. return x * y; 22. } 23. int div(int x, int y) 24. { 25. return x / y; 26. } 27. int main() 28. { 29. int x = 0; 30. int y = 0; 31. int input = 0; 32. int ret = 0; 33. do 34. { 35. menu(); 36. scanf("%d", &input); 37. switch (input) 38. { 39. case 1: 40. printf("请输入要计算的两个数:"); 41. scanf("%d%d", &x, &y); 42. ret =add(x,y); 43. printf("ret = %d", ret); 44. break; 45. case 2: 46. printf("请输入要计算的两个数:"); 47. scanf("%d%d", &x, &y); 48. ret = sub(x, y); 49. printf("ret = %d", ret); 50. break; 51. case 3: 52. printf("请输入要计算的两个数:"); 53. scanf("%d%d", &x, &y); 54. ret = mul(x, y); 55. printf("ret =%d", ret); 56. break; 57. case 4: 58. printf("请输入要计算的两个数:"); 59. scanf("%d%d", &x, &y); 60. ret = div(x, y); 61. printf("ret = %d", ret); 62. break; 63. default: 64. printf("选择错误,重新选择"); 65. break; 66. } 67. } while (input); 68. return 0; 69. }
我们可以看出,不仅非常繁琐,还涉及许多不必要的重复,这是我们就可以来优化一下:
1. #include<stdio.h> 2. void menu() 3. { 4. printf("*************************\n"); 5. printf(" *****1:add 2:sub *******\n"); 6. printf(" *****3:mul 4:div *******\n"); 7. printf(" *******0:exit **********\n"); 8. printf("*************************\n"); 9. printf("请选择:"); 10. } 11. int add(int x, int y) 12. { 13. return x + y; 14. } 15. int sub(int x, int y) 16. { 17. return x - y; 18. } 19. int mul(int x, int y) 20. { 21. return x * y; 22. } 23. int div(int x, int y) 24. { 25. return x / y; 26. } 27. int main() 28. { 29. int x = 0; 30. int y = 0; 31. int input = 0; 32. int ret = 0; 33. int (*p[5])(x, y) = { 0,add,sub,mul,div }; 34. do 35. { 36. menu(); 37. scanf("%d", &input); 38. scanf("%d%d", &x, &y); 39. if (input > 0 && input < 5) 40. { 41. printf("%d", (p[input])(x, y)); 42. } 43. } while (input); 44. return 0; 45. }
我们可以看到更加的简洁,这就是函数指针数组的好处。
完!