函数指针
函数指针创建
int Add(int x,int y)
{
return x+y;
}
int main()
{
//如果我们想打印出函数地址可以这么写
printf("%p\n",&Add);
//那么我们如何通过创建函数指针来存放函数的地址呢?
//我们可以想到上面介绍过的数组指针
//int arr[10];
//int (*pa)[10]=&arr;
//是通过数组类型加上指针形式int(*)[10]建立
//同样函数也可以通过函数类型+指针实现int(*)(int,int)
int (*pf)(int,int)=&Add;
//pf是一个存放函数地址的指针变量--函数指针
//也可以写成int (*pf)(int,int)=Add;
//&函数名和函数名都可以表示函数地址
return 0;
}
函数调用
int Add(int x, int y)
{
return x + y;
}
int main()
{
//int (*p)(int, int) = Add;
int (*p)(int, int) = &Add;
//通过之前的知识可知,这两种写法都是可行的
//那么我们如何调用函数呢?
int ret = (*p)(2, 4);
//int ret = p(2, 4);
//这两种写法也都是可行的,因为将Add赋给了p
//因此不解引用也是可行的,此时的(*p)中的*只是一个摆设
printf("%d\n", ret);
来看几个例子
(*(void(*)())0)();
//是一次函数调用,是将int型的0强制类型转换成函数指针类型,再解引用操作调用0地址处的函数
void (*signal(int,void(*)(int)))(int);
//void (*)(int)signal(int,void(*)(int));
//这样看更好理解,是一次函数声明,函数名为signal函数的第一个参数是int型第二个参数是void(*)(int)型的函数指针,函数返回值是void
//代码简化
typedef void(*pf)(int);//当使用typedef定义时,若含有*则新定义类型应放在*后面
pf signal(int,pf);
函数指针数组
int (*pf[10])(const char*)={&strlen};
//函数指针数组就是用来存放函数指针的数组
用法:
来实现一个计算器
void menu()
{
printf("------1.Add 2.Sub------\n");
printf("------3.Mul 4.Div------\n");
printf("------0.exit ------\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 = 0, y = 0,ret=0;
int input = 0;
do
{
menu();
scanf("%d", &input);
switch (input)
{
case 1:
printf("请输入两个数字->");
scanf("%d %d", &x, &y);
ret = Add(x, y);
printf("%d\n", ret);
break;
case 2:
printf("请输入两个数字->");
scanf("%d %d", &x, &y);
ret = Sub(x, y);
printf("%d\n", ret);
break;
case 3:
printf("请输入两个数字->");
scanf("%d %d", &x, &y);
ret = Mul(x, y);
printf("%d\n", ret);
break;
case 4:
printf("请输入两个数字->");
scanf("%d %d", &x, &y);
ret = Div(x, y);
printf("%d\n", ret);
break;
case 0:
printf("退出计算器\n");
break;
default:
printf("选择错误\n");
}
} while (input);
return 0;
}
可以通过使用函数指针数组来简化代码:
int(*pf[5])(int, int) = { 0,Add,Sub,Mul,Div };
int main()
{
int x = 0, y = 0, ret = 0;
int input = 0;
do
{
menu();
scanf("%d", &input);
if (input >= 1 && input <= 4)
{
printf("请输入两个数字->");
ret = pf[input](x, y);
printf("%d\n", ret);
}
else if (input == 0)
{
printf("退出计算器\n");
exit(0);
}
else
{
printf("选择错误\n");
}
} while (input);
return 0;
}
指向函数指针数组的指针
![](https://i-blog.csdnimg.cn/blog_migrate/44e8609bf12e80d8294ed96e047faadb.png)
回调函数
回调函数就是一个通过函数指针调用的函数。如果你把函数的指针(地址)作为参数传递给另一个函数,当这个指针被用来调用其所指向的函数时,我们就说这是回调函数。回调函数不是由该函数的实现方直接调用,而是在特定的事件或条件发生时由另外的一方调用的,用于对该事件或条件进行响应。
看一下我们刚才写的计算器代码,会发现很多重复的部分,因此我们可以另外创建一个函数,通过调用函数来实现计算器功能。
![](https://i-blog.csdnimg.cn/blog_migrate/19add44bb413c85f3db0e72daf889136.png)
void calc(int(*pf)(int,int))
{
int x=0,y=0;
printf("请输入两个数字->");
scanf("%d %d",&x,&y);
ret=pf(x,y);
}
calc函数相当于一个中介,再通过函数指针调用其所指向的函数,其中被调用的函数称为回调函数。
快速排序:
void qsort(void* base,size_t num,size_twidth,int(__cdecl*compare)(const void*elem1,const void*elem2));
头文件<stdlib.h> and <search.h> 函数参数: void* base--待排序数组的起始位置 size_t num--数组大小(元素个数) size_t width--数组一个元素宽度 int(__cdecl*compare)(const void*elem1,const void*elem2)--为一个比较两个元素的函数指针 |
其中最难理解的是函数指针:其中elem1和elem2是要比较的两个元素的地址,返回值根据elem1和elem2的大小不同而有所差异
![](https://i-blog.csdnimg.cn/blog_migrate/8da92d27b461ecd3960553ffd83242c1.png)
我们要注意的是elem1和elem2是void*型的(坏处是:没有明确的指针类型,不能直接解引用,也不可以++,好处是:可以接收不同类型的地址)
//void* 类型使用
int main()
{
int a = 0;
char b = 'h';
int* p = &a;
void* pf = p;
pf = &b;
//void*类型指针就像垃圾桶一样,可以接收任意类型指针
//void*没有明确的指向,因此不可以解引用操作,也不可以+-整数
printf("%p", pf);
return 0;
}
在使用时要进行强制类型转换,根据比较类型的不同写出不同的函数,如下所示
eg:
#include<stdio.h>
#include<string.h>
#include<stdlib.h>
#include<assert.h>
struct Stu
{
char name[20];
int age;
float grade;
};
//整形的比较
int compare1(const void* e1, const void *e2)
{
return *((int*)e1) - *((int *)e2);
}
//结构体中整形的比较
int compare_age(const void* e1, const void *e2)
{
return ((struct Stu *)e1)->age - ((struct Stu *)e2)->age;
}
//结构体中字符串的比较
int compare_name(const void* e1, const void *e2)
{
return strcmp((((struct Stu *)e1)->name), (((struct Stu *)e2)->name));
}
//字符串的比较
int compare_char(const void * e1, const void * e2)
{
return *(char *)e1 - *(char *)e2;
}
void test1()
{
int arr[5] = { 4,3,2,9,1 };
int num = sizeof(arr) / sizeof(arr[0]);
qsort(arr, num, sizeof(arr[0]), compare1);
//为一个库函数,可以直接调用,比较函数不用传参,在qsort的内部会自动传参
}
void test2()
{
struct Stu arr2[3] = { {"zhangsan",17,98.5},{"lisi",16,28.76},{"wangwu",25,93} };
int num = sizeof(arr2) / sizeof(arr2[0]);
qsort(arr2, num, sizeof(arr2[0]), compare_age);
qsort(arr2, num, sizeof(arr2[0]), compare_name);
}
void test3()
{
char a[20] = "dcba";
int sz = strlen(a);
qsort(a, sz, sizeof(a[0]), compare_char);
printf("%s", a);
}
int main()
{
test1();//整形数组的排序
test2();//结构体数组的排序
test3();//字符数组的排序
return 0;
}
//查看是否真正改变了排序可以通过调试来查看
//此时创建的不同类型的比较函数就是回调函数
冒泡排序:
冒泡排序思想:两两相邻元素进行比较
实现冒泡排序的普遍性bubble_sort()函数实现
int compare_int(const void* e1, const void* e2)
{
return *(int*)e1 - *(int*)e2;
//指针类型决定解引用操作时访问权限大小,根据数组的类型确定不同的compare函数
//int*型的指针可通过解引用得到元素并比较大小
}
void Swap(char* buf1,char* buf2,size_t width)
{
int i = 0;
for (i = 0; i < width; i++)
{
char tmp = *buf1;
*buf1 = *buf2;
*buf2 = tmp;
buf1++;//指向下一个元素
buf2++;//也指向下一个元素
}
}
void bubble_sort(void* base,size_t sz,size_t width,int(*compare)(const void* elem1,const void* elem2))
{
int i = 0, j = 0;
for (i = 0; i < sz - 1; i++)
{
for (j = 0; j < sz - 1 - i; j++)
{
if (compare((char*)base+width*j,(char*)base+width*(j+1))>0)
{
Swap((char*)base + width * j, (char*)base + width * (j + 1), width);
//不确定数组的类型,因此传递两个元素的起始地址以及数组一个元素的大小(字节)
}
}
}
}
int main()
{
int arr[10] = { 1,4,5,7,0,8,9,3,2,6 };
size_t sz = sizeof(arr) / sizeof(arr[0]);//sizeof是单目运算符
size_t wid = sizeof(arr[0]);
bubble_sort(arr, sz, wid, compare_int);
for (int i = 0; i < sz; i++)
{
printf("%d ", arr[i]);
}
return 0;
}
最终运行结果:
![](https://i-blog.csdnimg.cn/blog_migrate/7018037f13b300718baf7ffb4a75a840.png)
代码分析:
![](https://i-blog.csdnimg.cn/blog_migrate/35e75ef1aa4839f8b242f0f5796ff4ad.png)