函数指针
首先来看一段代码:
#include <stdio.h> void test() { printf("hehe\n"); } int main() { printf("%p\n", test); printf("%p\n", &test); return 0; }
让我们来看一下执行结果吧:
从上述结果得出,无论是函数名还是&函数名都是函数的地址,所以我们可以通过这两个获得函数的地址。
那么我们就会想到,既然这玩意跟函数一样都有地址,那是不是也可以存在一种指针来存放函数的地址呢?这就用到了我们今天要学习的函数指针。
下面我们看代码:
void test() { printf("hehe\n"); } int main() { //下面pfun1和pfun2哪个有能力存放test函数的地址? void (*pfun1)(); void* pfun2(); return 0; }
首先,能给存储地址,就要求pfun1或者pfun2是指针,那哪个是指针呢?
显然是pfun1,类似于数组指针。pfun1先和*结合,说明pfun1是指针,指针指向的是一个参数,指向的函数无参数,返回值类型是void。
我们下面举个例子来展示一下函数指针的使用。
#include <stdio.h> int Add(int x, int y) { return x + y; } int main() { int (*pf)(int, int) = &Add; //1 常规的调用函数方法 int a = Add(3, 5); //2 利用函数的地址调用函数 int b = (*pf)(4, 5); //3 也是利用函数的地址调用函数 int c = pf(5, 5); printf("%d\n%d\n%d\n", a, b, c); return 0; }
下面我们再阅读两段有趣(用陕西话应该说是木乱)的代码:
//代码一:
(*(void (*)( ) ) 0 ))( );
我们采用由里向外的方法来看,我们不难看出void(*) ( )是函数指针类型,后面跟个“0”那就显然是将0强制转换成void(*)( )类型的函数指针,然后显然就是调用0地址处的这个函数。
//代码二:
void (*signal(int , void(*)(int)))(int)
逐步分析:1.signal是个函数类型
2.signal的函数分别是int和void(*)(int)的这个函数指针类型,该函数指针有一个是int型参数,返回值为void
3.signal函数返回类型也是void(*)(int)
但是代码二又显得过于复杂了,不方便阅读,这时我们可以回首掏出我们
之前在结构体中学到的typedef(重命名)关键字,先看下面的内容:
typedef int(* parr_t)(int);//将int(*)(int)类型重名为parr_t.
我们照猫画虎使用一下上面的方法:
typedef void(* pfun_t)(int);//将void(*)(int)类型转换为pfun_t.
pfun_t signal(int, pfun_t);//将signal函数内的参数void(*)(int)转换为pfun_t,然后signal函数的类型也是void(*)(int)
函数指针数组
倘若君觉得函数指针的难度是:就这?那么我要从兜里掏出函数指针数组,阁下该如何应对呢?
函数指针数组,顾名思义,就是每个元素类型为函数指针的数组
那要把函数的地址存放到一个地址中,那这个数组就叫做函数指针数组,那函数指针数组应该如何定义呢?我们来看一下三种情况:
int (*parr1[10])(); int* parr2[10](); int (*)() parr3[10];
答案是:parr1 parr1先和[ ]结合,说明parr1是数组,那么数组的每个类型是int(*)( )类型的函数指针。
为了详细了解函数指针数组,我们将引入它的一个重要用途:转移表。
例子:计算器。
在以前,我们可能会这样写计算器:
#include<stdio.h> void menu() { printf("****************************\n"); printf("****** 1. add 2.sub ******\n"); printf("****** 3. mul 4.div ******\n"); printf("****** 0.exit ******\n"); printf("****************************\n"); } int add(int x, int y) { return x + y; } int sub(int x, int y) { return x - y; } int mul(int x, int y) { return x * y; } int div(int x, int y) { return x / y; } int main() { int x, y; int input = 0; int ret = 0; do { menu(); printf("请选择:>"); scanf("%d", &input); switch (input) { case 1: printf("请输入操作数:"); scanf("%d %d", &x, &y); ret = add(x, y); printf("ret = %d\n", ret); break; case 2: printf("请输入操作数:"); scanf("%d %d", &x, &y); ret = sub(x, y); printf("ret = %d\n", ret); break; case 3: printf("请输入操作数:"); scanf("%d %d", &x, &y); ret = mul(x, y); printf("ret = %d\n", ret); break; case 4: printf("请输入操作数:"); scanf("%d %d", &x, &y); ret = div(x, y); printf("ret = %d\n", ret); break; case 0: printf("单击以推出程序:\n"); break; default: printf("输入错误,请重新输入\n"); break; } }while(input); return 0; }
这样写不仅重复代码量多,而且非常木乱。
因此我们来使用函数指针数组来对这个计算器进行改造。
int main() { int x, y; int input = 0; //将四个函数以及零的地址放入函数指针数组当中 int (*parr[5])(int, int) = { 0,add,sub,mul,div }; do { menu(); printf("请输入您的选择:>\n"); scanf("%d", &input); //根据input值判断行为 if (input >= 1 && input <= 4) { //正常进入计算器操作程序 printf("请输入两个操作数:\n"); scanf("%d %d", &x, &y); //通过函数指针数组的元素索引找到对应使用函数元素的地址,并在后面传参 int ret = (*parr[input])(x, y); printf("ret = %d\n", ret); } else if (input == 0) { printf("退出计算器\n"); } else { printf("选择错误,请重新选择:>\n"); } } while (input); return 0; }
这样做无疑大大减少了代码量,而且程序的可阅读性非常高,这便是函数指针数组的重要使用。
指向函数指针的数组
倘若观众老爷还是觉得简单,那我要是拿出指向函数指针的数组,阁下该如何应对?
定义:指向函数指针数组的指针是一个指针。
指针指向一个数组,数组的元素都是函数指针。
如何定义?
#include <stdio.h> void test(const char* str) { printf("%s", str); } int main() { //函数指针pfun void (*pfun)(const char*) = test; //函数指针数组pfunArr void (*pfunArr[5])(const char*); pfunArr[0] = test; //指向函数指针数组pfunArr的指针ppfunArr void (*(*ppfunArr)[10])(const char*) = &pfunArr; return 0; }
好了这期就介绍到这里,下一期我们来详细讲一下回调函数和qsort函数,指针的知识就圆满结束啦!谢谢各位未来的大厂员工收看,谢谢!!!