六、函数指针数组
数组是一个存放相同类型数据的存储空间,我们已经学习了指针数组
eg:
int *arr[10] //整形指针数组-数组-存放的是整形指针
char *arr[5] //字符指针数组-数组-存放的是字符指针
那么把函数的地址存到一个数组中,那这个数组就叫函数指针数组,那函数指针的数组如何定义呢
int (*parr【10】)( )
parr先和【】结合,说明parr是数组,那么数组的内容是什么呢?
是int(*)()类型的函数指针(从函数指针+数组名【】)
总结:
看pa和【】还是和*结合,
如果是和【】结合,那么pa就是数组
如果是和*结合,那么pa就是指针
eg:函数指针数组可以将类型为函数指针的元素放在一起
cpp
int Add(int x, int y)
{
return x + y;
}
int Sub(int x, int y)
{
return x - y;
}
int main()
{
int (*pf1)(int, int) = &Add;
int (*pf2)(int, int) = ⋐
//数组中存放相同类型的多个元素
int (*pfArr[2])(int, int) = { &Add,&Sub };
//pfArr是函数指针数组-存放函数指针的数组
return 0;
}
函数指针数组的用途:转移表
eg2:(计算器)
基础版:但是这个代码不好,如果想实现||,&&,&,|,>>,<<更多的功能还得重复写一些步骤,就很冗余
cpp
#include<stdio.h>
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;
}
void menu()
{
printf("***********************************\n");
printf("***1.add 2.sub***************\n");
printf("***3.mul 4.div***************\n");
printf("***0.exit *************************\n");
printf("***********************************\n");
}
int main()
{
int x, y = 0;
int input = 0;
int ret = 0;
do
{
menu();
printf("请选择:\n");
scanf("%d", &input);
switch (input)
{
case 1:
printf("请输入2个操作数:\n");
scanf("%d %d", &x, &y);
ret = add(x, y);
printf("ret=%d\n", ret);
break;
case 2:
printf("请输入2个操作数:\n");
scanf("%d %d", &x, &y);
ret = sub(x, y);
printf("ret=%d\n", ret);
break;
case 3:
printf("请输入2个操作数:\n");
scanf("%d %d", &x, &y);
ret = mul(x, y);
printf("ret=%d\n", ret);
break;
case 4:
printf("请输入2个操作数:\n");
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;
}
使用函数指针数组实现:
这样就统一起来了,更加简洁,方便,如果要加功能,只需要改菜单,数组定义,input的范围
cpp
#include<stdio.h>
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;
}
void menu()
{
printf("***********************************\n");
printf("***1.add 2.sub***************\n");
printf("***3.mul 4.div***************\n");
printf("***0.exit *************************\n");
printf("***********************************\n");
}
int main()
{
int x, y = 0;
int input = 1;
int ret = 0;
int (*p[5])(int, int) = { NULL,&add,&sub,&mul,&div };//转移表(加个NULL是为了和数组下标统一)
while (input)
{
menu();
printf("请选择:\n");
scanf("%d", &input);
if (input == 0)
{
printf("退出程序\n");
}
else if (input <= 4 && input >= 1)
{
printf("请输入2个操作数:\n");
scanf("%d %d", &x, &y);
ret = (*p[input])(x, y);//*可不写,和函数指针一样
printf("ret=%d\n", ret);
}
else
{
printf("输入错误,请重新输入\n");
}
}
return 0;
}
注意 :这样改的条件是:函数的返回类型和参数类型一样
七、指向函数指针数组的指针
(这部分内容不重要,但是可以拓展视野)
类比:指向指针数组的指针 int*(*p)【3】=&arr
指向函数指针数组的指针是一个指针,指针指向一个数组,数组元素都是函数指针
eg:
int(*(*p)****【5】)(int,int)=&pfArr p是指向函数指针数组的指针
int(* 【5】)(int,int)=&pfArr 函数指针数组类型
int(* )(int,int)=&pfArr 函数指针类型
八、回调函数
回调函数就是一个通过函数指针调用的函数,如果你把函数指针(地址)作为参数传递给另一个函数,当这个指针被用来调用函数其指向的函数时,我们就说这是回调函数,回调函数不是由该函数的实现方直接调用,而是在特定的事件或条件发生时由另外的一方调用的,用于对该事件或条件进行响应
简单来说就是有两个函数,通过函数指针得到一个函数A(回调函数)的地址, 另一个函数
B(&A)实现间接调用A函数
注意:依赖函数指针才有回调函数
eg1:
计算器基础版简化:
这个部分每个case都在重复,那就用一个函数进行代替:
cpp
case 1:
printf("请输入2个操作数:\n");
scanf("%d %d", &x, &y);
ret = add(x, y);//sub/mul/div
printf("ret=%d\n", ret);
break;
运用回调函数进行改进:
cpp
#include<stdio.h>
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;
}
void menu()
{
printf("***********************************\n");
printf("***1.add 2.sub***************\n");
printf("***3.mul 4.div***************\n");
printf("***0.exit *************************\n");
printf("***********************************\n");
}
void calc(int (*pf)(int,int))
{
int x, y = 0;
printf("请输入2个操作数:\n");
scanf("%d %d", &x, &y);
int ret = pf(x, y);
printf("ret=%d\n", ret);
}
int main()
{
int input = 0;
do
{
menu();
printf("请选择:\n");
scanf("%d", &input);
switch (input)
{
case 1:
calc(add);
break;
case 2:
calc(sub);
break;
case 3:
calc(mul);
break;
case 4:
calc(div);
break;
case 0:
printf("退出程序\n");
break;
default:
printf("选择错误,请重新选择\n");
break;
}
} while (input);
return 0;
}
图解:
借助calc函数回调add,sub,mul,div等函数,是通过函数地址回调函数,逻辑就是进入case语句calc函数开始执行,当执行到ret=pf(x,y),又回调add等函数,回调结束又回到calc函数中
注意:add,sub,mul,div这些函数才是回调函数,而不是calc函数
eg2:qsort快排:
对数据的排序方法有很多:冒泡排序,选择排序,插入排序,快速排序
为了对比qsort函数进行排序我们这里再来复习一下冒泡排序:
cpp
#include<stdio.h>
void print_arr(int* arr, int sz)
{
for (int i = 0; i < sz; i++)
{
printf("%d ", arr[i]);
}
printf("\n");
}
void bubble_arr(int* arr, int sz)
{
for (int i = 0; i < sz-1; i++)//趟数
{
for (int j = 0; j < sz-i-1; j++)//每一趟冒泡排序
{
if (arr[j] > arr[j + 1])
{
int tmp = arr[j];
arr[j] = arr[j + 1];
arr[j + 1] = tmp;
}
}
}
}
int main()
{
int arr[] = { 9,8,7,6,5,4,3,2,1,0 };
int sz = sizeof(arr) / sizeof(arr[0]);
print_arr(arr, sz);
bubble_arr(arr, sz);
print_arr(arr, sz);
return 0;
}
但是这个冒泡排序只能排序int类型的数据,下面就来介绍qsort快排:
qsort函数是一个库函数,底层使用的快速排序的方式,对数据进行排序,这个函数可以直接使用,可以用来排序任意类型的数据
头文件:#include<stdlib.h>
void qsort( void *base, size_t num, size_t width, int (__cdecl *compare )(const void *elem1, const void *elem2 ) );
qsort(被排序数组的初始位置,要排序的数组的元素个数,一个元素所占字节,比较函数)
重点是最后一个参数,比较函数:
如果要升序(elem1<elem2),则return<0,就要return elem1-elem2
如果要降序(elem1>elem2),则return>0,就要return elem2-elem1
cpp
#include<stdio.h>
#include<stdlib.h>
int int_cmp(const void* e1, const void* e2)
{
return (*(int*)e1 - *(int*)e2);
}
int main()
{
int arr[] = { 1,3,5,7,9,2,4,6,8,0 };
qsort(arr, sizeof(arr) / sizeof(arr[0]), sizeof(arr[0]), int_cmp);
for (int i = 0; i < sizeof(arr) / sizeof(arr[0]); i++)
{
printf("%d ", arr[i]);
}
printf("\n");
return 0;
}
比较不同类型的数据,方法是有差异的:
(1)排序整形数据,两个整形可以直接直接使用><比较
(2)比较结构体数据,两个结构体的数据可能不能使用>比较
(eg:字符串的比较得用strcmp)
注意: 比较函数(__cdecl *compare )的返回值一定为int类型比较函数就是回调函数,比较时没有直接用比较函数(__cdecl *compare ),而是通过函数指针传给qsort函数
为什么用void*类型的指针 ?
void*指针:无具体类型的指针
不能进行解引用操作,也不能进行+-整数的操作
它是用来存放任意类型数据的地址的
结构体类型数据快排:
cpp
//结构体类型
struct Stu
{
char name[20];
int age;
};
//int cmp_stu_by_age(const void* e1, const void* e2)
//{
// return ((struct Stu*)e1)->age - ((struct Stu*)e2)->age;
//}
//int cmp_stu_by_name(const void* e1, const void* e2)
//{
// return *(struct *)e1 - *(int*)e2;
//}
int cmp_stu_by_name(const void* e1, const void* e2)
{
return strcmp(((struct Stu*)e1)->name , ((struct Stu*)e2)->name);
}
int main()
{
struct Stu arr[] = { {"zhangsan", 20}, {"lisi", 30}, {"wangwu", 12} };
int sz = sizeof(arr) / sizeof(arr[0]);
//qsort(arr, sz, sizeof(arr[0]), cmp_stu_by_age);
qsort(arr, sz, sizeof(arr[0]), cmp_stu_by_name);
return 0;
}
double类型的数据快排:这两种都可以
cpp
//浮点型数据
int cmp_double(const void* e1, const void* e2)
{
return (int)(*(double*)e1 > *(double*)e2);
}
//int cmp_double(const void* e1, const void* e2)
//{
// return *(double*)e1 < *(double*)e2?-1:1 ;
//}
本次内容就到此啦,欢迎评论区或者私信交流,觉得笔者写的还可以,或者自己有些许收获的,麻烦铁汁们动动小手,给俺来个一键三连,万分感谢 !