函数指针
先看一段代码
#include<stdio.h>
void test()
{
printf("hello world\n");
}
int main()
{
printf("%p\n", test);
printf("%p\n", &test);
return 0;
}
数组中&arr是arr的首地址,那么函数中test和&test有什么关系呢?
函数中函数名==&函数名
函数指针的形式
int Add(int x, int y)
{
return x + y;
}
int main()
{
int (*pf)(int, int) = &Add;
return 0;
}
代码中
int (*pf)(int, int) = &Add;
pf 就是函数指针
第一个int是函数返回类型
(*pf)表示指针名称
(int,int)是函数参数类型
&Add是函数地址
应用
int Add(int x, int y)
{
return x + y;
}
int main()
{
int (*pf)(int, int) = Add;
int ret = (*pf)(3, 5);
printf("%d\n", ret);
return 0;
}
这里的int ret = (*pf)(3, 5)等价于int ret = pf(3, 5),*没有实际意义。
阅读下面代码,思考代码的含义
1.(*(void(*)())0)();
2.void (* signal(int, void(*)(int) ) )(int);
(*(void(*)())0)();
1. void(*)() - 函数指针类型
2. (void(*)())0 - 对0进行强制类型转换,被解释为一个函数地址
3. *(void(*)())0 - 对0地址进行了解引用操作
4. (*(void(*)())0)() - 调用0地址处的函数
该函数为调用0地址处的函数,函数无参,返回类型是void
void (* signal(int, void(*)(int) ) )(int);
1. signal 和()先结合,说明signal是函数名
2. signal函数的第一个参数的类型是int,第二个参数的类型是函数指针,该函数指针,指向一个参数为int,返回类型是void的函数
3. signal函数的返回类型也是一个函数指针,该函数为指针,指向一个参数为int,返回类型是void的函数
注:上述代码源自《C陷阱和缺陷》
代码2过于复杂,可不可以简化呢?
这里就要提到typedef关键字:
typedef - 对类型进行重定义
typedef void(*pfun_t)(int) ;
对void(*)(int)的函数指针类型重命名为pfun_t
void (* signal(int, void(*)(int) ) )(int);
可以简化为pfun_t signal(int, pfun_t);
函数指针数组
数组是一个存放相同类型数据的存储空间,把函数的地址存到一个数组中,那这个数组就叫函数指针数组。
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) = Sub;
int (*pfArr[2])(int, int) = {Add, Sub};
return 0;
}
上述代码中pfArr就是函数指针数组
应用
用函数指针数组模拟实现一个计算器
#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 input = 0;
int(*parr[5])(int, int) = { NULL, Add, Sub, Mul, Div };
do
{
menu();
int x = 0;
int y = 0;
printf("请选择;");
scanf("%d", &input);
if (input >= 1 && input <= 4)
{
printf("请输入两个操作数:");
scanf("%d %d", &x, &y);
int ret = (parr[input])(x, y);
printf("%d\n", ret);
}
else if (input == 0)
{
printf("退出程序\n");
}
else
{
printf("选择错误,请重新选择");
}
} while (input);
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)[10])(const char*)=&pfunArr;
return 0;
}
回调函数
回调函数就是一个通过函数指针调用的函数。如果你把函数的指针(地址)作为参数传递给另一个函数,当这个指针被用来调用其所指向的函数时,我们就说这是回调函数。回调函数不是由该函数的实现方直接调用,而是在特定的事件或条件发生时由另外的方调用的,用于对该事件或条件进行响应。
上文模拟计算器可以利用传递函数实现
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 Calc(int(*pf)(int, int))
{
int x = 0;
int y = 0;
printf("请输入2个操作数>:");
scanf("%d %d", &x, &y);
return pf(x, y);
}
int main()
{
int input = 0;
do {
menu();
int ret = 0;
printf("请选择:>");
scanf("%d", &input);
switch (input)
{
case 1:
ret = Calc(Add);
printf("ret = %d\n", ret);
break;
case 2:
ret = Calc(Sub);
printf("ret = %d\n", ret);
break;
case 3:
ret = Calc(Mul);//
printf("ret = %d\n", ret);
break;
case 4:
ret = Calc(Div);//
printf("ret = %d\n", ret);
break;
case 0:
printf("退出程序\n");
break;
default:
printf("选择错误,重新选择!\n");
break;
}
} while (input);
return 0;
}
熟练运用回调函数可以省去大量重复代码。
练习
模拟qsort实现一个冒泡排序的通用算法
什么是qsort
qsort是一个库函数,对数据进行排序,需引头文件#include <stdlib.h>
void qsort(void* base, //base中存放的是待排序数据中第一个对象的地址
size_t num, //排序数据元素的个数
size_t size,//排序数据中一个元素的大小,单位是字节
int (*cmp)(const void* e1, const void* e2)//是用来比较待排序数据中的2个元素的函数
);
应用实例
1.排序整型数组
#include<stdio.h>
#include <stdlib.h>
void print_arr(int arr[], int sz)
{
int i = 0;
for (i = 0; i < sz; i++)
{
printf("%d ", arr[i]);
}
printf("\n");
}
int cmp_int(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};
int sz = sizeof(arr) / sizeof(arr[0]);
print_arr(arr, sz);
//排序
qsort(arr, sz, sizeof(arr[0]), cmp_int);
//打印
print_arr(arr, sz);
return 0;
}
2.排序结构体
#include<stdio.h>
#include <stdlib.h>
#include <string.h>
struct Stu
{
char name[20];
int age;
};
int sort_by_age(const void* e1, const void* e2)
{
return ((struct Stu*)e1)->age - ((struct Stu*)e2)->age;
}
int sort_by_name(const void*e1, const void*e2)
{
return strcmp(((struct Stu*)e1)->name, ((struct Stu*)e2)->name);
}
int main()
{
//使用qsort函数排序结构体数据
struct Stu s[3] = { {"zhangsan", 30},{"lisi", 34},{"wangwu", 20} };
int sz = sizeof(s) / sizeof(s[0]);
//按照年龄来排序
qsort(s, sz, sizeof(s[0]), sort_by_age);
//按照名字来排序
qsort(s, sz, sizeof(s[0]), sort_by_name);
return 0;
}
排序前
按照年龄来排序
按照名字来排序
注意:这里的strcmp比较字符串比较的是对应位置的ASCII码值
模拟实现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++;
}
}
void my_qsort(void* base,
int sz,
int width,
int(*cmp)(const void*e1, const void*e2)
)
{
int i = 0;
int j = 0;
//排序的趟数
for (i = 0; i < sz-1; i++)
{
//一趟冒泡排序
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);
}
}
}
}
int cmp_int(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};
int sz = sizeof(arr) / sizeof(arr[0]);
my_qsort(arr, sz, sizeof(arr[0]), cmp_int);
int i = 0;
for (i = 0; i < sz; i++)
{
printf("%d ", arr[i]);
}
return 0;
}