前言
书接上回,我们继续详解指针,接下来我们从函数指针说起。
一 函数指针
首先我们来看到下面这代码:
从中我们可以看出函数名就是函数的地址,但我们要将函数的地址存放起来又改如何呢?
我们都知道指针是用来存放地址的,那对于函数的地址,指针又将如何书写。
函数指针的书写
函数的反回类型 (*函数名)(函数的参数类型)
#include<stdio.h> int Add(int x, int y) { return x + y; } int main() { int (*Add)(int, int) = &Add; return 0; }
对于函数Add我们要将它的地址存起来,就要用到函数指针,上面的函数指针,函数的返回类型是int函数的参数类型是int 。
我们知道什么是函数指针下面阅读两段有趣的代码:
//代码1
(*(void (*)())0)();
//代码2
void (*signal(int , void(*)(int)))(int);
我相信大家看到这段代码不免有点后怕,一脸懵逼。
下面我就为大家细细剖析这段代码。
代码1
拿到这段代码,我们首先从自己的熟悉的地方开始,( )0,这是什么呢?这不就是就0强制转换为什么类型,好我们继续看((void (*)())里面的void (*)()对于这个我们又是比较熟悉的,这不就是函数指针,所以是我们将0强制转换为函数指针类型;(*)()这不就是一次函数调用,那么这段代码可以理解为:
代码1为一次函数调用,调用的是0作为地址处的函数。
把0强制类型转换为:无参,返回类型是void的函数的地址;
调用0地址处的函数
代码2
我们继续从熟悉的地方分析,signal函数有二个参数int 和void(*)(int)这是函数指针,那么就还剩下void(*)(int),这样我们可以很容易看出这是个函数指针
所以代码2可以理解为:
以上是一次函数声明,
声明函数的signal函数的第一个参数的类型是int,第二个参数的类型是函数指针;
返回类型是void,signal函数的返回类型也是函数指针。
其实对于代码2我们还可以经行简化。
我们可以给void(*)(int)取个名字为puf:
typedef void(*puf)(int);
puf signal(int ,puf)l
二 函数指针数组
什么我们知道了函数指针是一个函数,是用来存放函数地址的,那么函数指针数组又是什么呢?
我们可以知道函数指针数组,的本质是数组,这个数组的作用是存放,函数指针的。
函数指针数组的定义:
数据类型(*函数名[元素个数 ])()
下面我们写一个计算器来理解函数指针数组的用法。
普通方法:
//转移表 #include<stdio.h> void menu() { printf("*************************\n"); printf("***** 1.Add 2.sub *****\n"); printf("***** 3.mul 4.div *****\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 input = 0; int x = 0; int y = 0; printf("请选择:\n"); menu(); scanf("%d", &input); switch (input) { case 1: printf("请输入操作:\n"); scanf("%d%d", &x, &y); int ret = Add(x, y);//调用加法函数 printf("sum = %d\n", ret); break; case 2: printf("请输入操作:\n"); scanf("%d%d", &x, &y); ret = sub(x, y);//调用减法函数 printf("sum = %d\n", ret); break; case 3: printf("请输入操作:\n"); scanf("%d%d", &x, &y); ret = mul(x, y);//调用乘法函数 printf("sum = %d\n", ret); break; case 4: printf("请输入操作:\n"); scanf("%d%d", &x, &y); ret = div(x, y);//调用减法函数 printf("sum = %d\n", ret); break; } return 0; }
虽然我们用常规方法写出了转移表,但是在switch语句中出现了大量冗余的代码。
所以我们可以经行改进:
#include<stdio.h> void menu() { printf("*************************\n"); printf("***** 1.Add 2.sub *****\n"); printf("***** 3.mul 4.div *****\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 input = 1; int x = 0; int y = 0; int(*p[5])(int x, int y) = { 0,Add,sub,mul,div };//转移表 int ret = 0; while (input) { printf("请选择:\n"); menu(); scanf("%d", &input); if (input >= 1 && input <= 4) { printf("请输入二个操作数\n"); scanf("%d%d", &x, &y); ret = (*p[input])(x, y);//函数调用 printf("ret = %d\n", ret); } } return 0; }
这里我们巧妙借用函数指针数组,将计算器调用的函数首地址存在数组中,这样就可以有效避免出现冗余的代码。
总结
函数指针数组的用途:转移表
三 指向函数指针数组的指针
首先这是的指针,指针指向一个数组,数组的元素是函数指针。
指向函数指针数组的指针的定义:
void test(const char* str) { printf("%s\n", str); } int main() { //函数指针pfun void (*pfun)(const char*) = test; //函数指针的数组pfunArr void (*pfunArr[5])(const char* str); pfunArr[0] = test; //指向函数指针数组pfunArr的指针ppfunArr void (*(*ppfunArr)[5])(const char*) = &pfunArr; return 0; }
其中还有指向函数指针数组的指针数组,指向函数指针数组的指针数组指针,,,下面就没必要讨论下去了,理解思路都和上面的差不多。
四 回调函数
回调函数就是一个通过函数指针调用的函数。如果你把函数的指针(地址)作为参数传递给另一个 函数,当这个指针被用来调用其所指向的函数时,我们就说这是回调函数。回调函数不是由该函数 的实现方直接调用,而是在特定的事件或条件发生时由另外的一方调用的,用于对该事件或条件进行响应。
为了加深对回调函数的理解,我们将模拟实现qorst函数。
qsort函数是C语言提供的一个库函数,是用来快速排序的。
qsort有4个参数:
base: 要排序元素的起始地址
mum: 要排序的元素个数
witdth:每个元素的大小(字节)
cmp: 比较函数
qsort 函数实现了一种快速排序算法,用于对 num 元素数组进行排序,每个元素的宽度为字节。参数基是指向要排序的数组的基的指针。qsort 用排序的元素覆盖此数组。参数 compare 是指向用户提供的例程的指针,该例程比较两个数组元素并返回指定其关系的值。qsort 在排序过程中调用 compare 例程一次或多次,每次调用时将指针传递给两个数组元素
细心的小伙伴可能看出来void *是我们没有见的。
void*
是无具体类型的指针,这种指针可以接类型的地址。
注:void*不能进行*(解引用操作),也不能+-整数。
qsort用法举例:
#include<stdio.h> #include<stdlib.h> int int_cmp(const void*e1,const void*e2) { return (*(int*)e1 - *(int*)e2); } int main() { int arr[10] = { 9,8,7,6,5,4,3,2,1,0 }; int sz = sizeof(arr) / sizeof(arr[0]); qsort(arr, sz, sizeof(arr[0]), int_cmp); int i = 0; for (i = 0;i < sz;i++) { printf("%d ", arr[i]); } return 0; }
这里我们注意比较函数是要自己写的,比较函数的返回值:
数组按递增的顺序排序,如比较函数所定义。要按降序对数组进行排序,请反转比较函数中的“大于”和“小于”的含义。
模仿qsort的功能实现一个通用的冒泡排序
void swap(char* buf1, char* buf2, int width) { int i = 0; //将字符进行一对一对的交换 for (i = 0;i < width;i++) { char tmp = *buf1; *buf1 = *buf2; *buf2 = tmp; *buf1++; *buf2++; } } //模拟实现qsort void doubble_sort(void* base, int sz, int width, int (*cmp)(const void* e1, const void* e2)) { int i = 0; for (i = 0;i < sz - 1;i++) { int j = 0; int flag = 1;//假设数组已经是排好顺序 for (j = 0;j < sz - 1 - i;j++) { if (cmp((char*)base + j * width, (char*)base + (j + 1) * width) > 0) { swap((char*)base + j * width, (char*)base + (j + 1) * width, width);//交换 flag = 0; } } if (flag == 1) { break; } } }
我们通过函数指针cmp调用了函数swap,所以函数swap是回调函数。
总结
看完这篇博客,大家应该知道函数指针是指针,用来存放函数的地址,对于函数指针数组是数组,数组中的每个元素是函数指针,及指向函数指针数组的指针,指针指向数组,数组中的每个元素是函数指针,回调函数指的是通过函数指针调用的函数。