目录
五、函数指针数组
数组是一个存放相同类型数据的存储空间,函数指针数组顾名思义存储的是函数指针。
int (*parr1[10])();
parr1先和[10]结合说明它是个数组有10个元素,元素类型是int(*)()函数指针。
用途:转移表。用经典的计算器来举例:
首先要有最基础的功能+,-,*,/
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;
}
我们该如何使用它们?可以使用switch来实现
int main()
{
int x, y;
int input = 1;
int ret = 0;
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);
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;
}
虽然功能实现了,但在使用四种功能时好像重复了几段代码,看起来有些冗余
printf("输入操作数:");
scanf("%d %d", &x, &y);
ret = Add(x, y);
printf("ret = %d\n", ret);
break;
它们只是调用了不同的函数给了ret,而这些函数的类型又是相同的,就可以放到一个数组内来使用。
int main()
{
int x, y;
int input = 1;
int ret = 0;
int(*p[5])(int x, int y) = { NULL, Add, Sub, Mul, Div }; //转移表
while (input)
{
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 ((input <= 4 && input >= 1))
{
printf("输入操作数:");
scanf("%d %d", &x, &y);
ret = (*p[input])(x, y);
}
else
printf("输入错误\n");
printf("ret = %d\n", ret);
}
return 0;
}
像刚刚一样我们输入了同样的值,得到的数据一样,使用转移表代码量是减少了很多。
六、指向函数指针数组的指针
写函数指针数组就是在函数指针的基础上先使它与[]结合。
函数指针数组的指针也是在函数指针数组的基础上先与*结合。
int (*(*parr1)[10])();
定义:
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;
}
七、回调函数
回调函数就是一个通过函数指针调用的函数。如果你把函数的指针(地址)作为参数传递给另一个函数,当这个指针被用来调用其所指向的函数时,我们就说这是回调函数。回调函数不是由该函数的实现方直接调用,而是在特定的事件或条件发生时由另外的一方调用的,用于对该事件或条件进行响应。
7.1 qsort
先来看一个函数:
void qsort(void *base, size_t nitems, size_t size, int (*compar)(const void *, const void*))
qsort可以对任何数组进行排序,它是使用快速排序来实现的。那又是如何对任何数组进行排序的?我们先来看它的类型,base:指向要排序的数组的第一个元素的指针;nitems: base 指向的数组中元素的个数;size:数组中每个元素的大小,以字节为单位;compar:用来比较两个元素的函数。
其中base和compar的参数都是void*类型的(const只是表示不可*修改),void*是很特殊的。
void*可以指向任何类型的地址,但是带类型的指针不能指向void*的地址。
void*指针只有强制类型转换以后才可以正常取值和加减整数。
在实现qsort时,那个程序员并不知道后面人使用时会排序什么类型数组,而每个类型的比较又是不同的,所以需要我们自己实现compar(比较函数)。
现在来看看各个类型如何实现compar:
整形:
int compar(const void* x, const void* y)
{
return *(int*)x - *(int*)y;
}
字符:
int compar(const void* x, const void* y)
{
return *(char*)x - *(char*)y;
}
字符串:
int compar(const void* x, const void* y)
{
return strcmp((char*)x , (char*)y);
}
浮点型:
int compar(const void* x, const void* y)
{
return *(double*)x - *(double*)y;
}
结构体:
struct Stu
{
char name[20];
int age;
};
int compar1(const void* x, const void* y)
{
return ((struct Stu*)x)->age-((struct Stu*)y)->age;
}
int compar1(const void* x, const void* y)
{
return strcmp(((struct Stu*)x)->name, ((struct Stu*)y)->name);
}
7.2 实现一个可以排序任何类型的冒泡函数
qsort是使用快速排序来实现的,我们可以先用冒泡排序进行实现排序函数:
先写一个整形冒泡,在这个基础上来修改:
void My_sort(int* arr, int sz)
{
int i = 0;
int j = 0;
for (i = 0; i < sz - 1; i++)
{
for (j = 0; j < sz - 1 - i; j++)
{
if (*(arr + j) > *(arr + j + 1))
{
int tmp = *(arr + j);
*(arr + j) = *(arr + j + 1);
*(arr + j + 1) = tmp;
}
}
}
}
这是的排序函数只能排整形,我们只需要让它也可以排序其他类型的数组就可以。
参数参考qsort来写:
void My_sort(void* base, size_t nitems, size_t size, int (*compar)(const void*, const void*))
参数是比我们原先的冒泡多了两个参数。
size是排序数组的元素大小,我们在排序是肯定是会对base进行访问和加减的,void*是不能进行的,它就是可以加减整数也不能确定跳过几个字节。
所以我们要对base强制类型转换,自然不能转换为int,如果base指向的是double类型该如何跳过一个元素,每次多+2?那char呢?它是无法通用的。
其实我们可以将它转成char*,我们已经知道元素的字节大小了(size),那每次访问跳过size个字节就可以,而char*每次加几也就是跳过几个字节,这样一来我们访问第几个元素就加几个size。
compar先以最熟悉的int*类型来实现。
int compar(const void* x, const void* y)
{
return *(int*)x - *(int*)y;
}
void My_sort(void* base, size_t nitems, size_t size, int (*compar)(const void*, const void*))
{
int i = 0;
int j = 0;
for (i = 0; i < nitems - 1; i++)
{
for (j = 0; j < nitems - 1 - i; j++)
{
if (compar((char*)base+j*size,(char*)base+(j+1)*size)>0)
{
swap();//交换函数
}
}
}
}
x-y,x大与y就交换它们两个,这时它是升序函数;y-x,y大于x就交换它们两个,这时它是降序函数。
再来看看swap:
在交换时也是不知道数组的类型,但每个类型它都是以字节为单位的,可以再次使用char*,每次只交换1个字节交换size次,也就刚好是这个数组的元素大小。
int compar(const void* x, const void* y)
{
return *(int*)x - *(int*)y;
}
void swap(char* x, char* y, int size)
{
int i = 0;
for (i = 0; i < size; i++)
{
char tmp = *x;
*x++ = *y;
*y++ = tmp;
}
}
void My_sort(void* base, size_t nitems, size_t size, int (*compar)(const void*, const void*))
{
int i = 0;
int j = 0;
for (i = 0; i < nitems - 1; i++)
{
for (j = 0; j < nitems - 1 - i; j++)
{
if (compar((char*)base + j * size, (char*)base + (j + 1) * size) > 0)
{
swap((char*)base + j * size, (char*)base + (j + 1) * size,size);//交换函数
}
}
}
}
来测试一下:
int main()
{
int arr[10] = { 9,8,7,0,4,5,6,3,2,1 };
My_sort(arr, 10,4,compar);
int i = 0;
for (i = 0; i < 10; i++)
{
printf("%d ", arr[i]);
}
return 0;
}
改为y-x
int compar(const void* x, const void* y)
{
return *(int*)y - *(int*)x;
}
再来试试结构体类型:
struct stu {
char name[20];
int age;
};
int compar(const void* x, const void* y)
{
return *(int*)y - *(int*)x;
}
int compar1(const void* x, const void* y)
{
return strcmp(((struct stu*)x)->name, ((struct stu*)y)->name);
}
int main()
{
/*int arr[10] = { 9,8,7,0,4,5,6,3,2,1 };
My_sort(arr, 10,4,compar);
int i = 0;
for (i = 0; i < 10; i++)
{
printf("%d ", arr[i]);
}*/
struct stu p[3] = { {"zhangsan",17}, {"lisi",20},{"wangwu",19} };
My_sort(p, 3, sizeof(p[0]), compar1);
int i = 0;
for (i = 0; i < 3; i++)
{
printf("%s,%d\n", (p + i)->name, (p+i)->age);
}
return 0;
}
这里结构体定义要放到compar前,否则会报错。