目录
3.2 回调函数的典型应用 —— qsort( )快速排序函数
一. 函数指针
函数指针是用于存放函数的地址的指针变量
1.1 函数指针的定义和初始化
定义:int (*pf)(int, int) //定义了一个函数指针pf,它指向一个具有两个int型输入参数、返回值类型为int型的函数。
初始化:int (*pf)(int, int) = &函数名
函数指针定义和初始化的通用格式:
函数返回值类型 (*指针变量名)(形参1类型, 形参2类型, ...... ) = &函数名
注意:&数组名 数组名 但是 &函数名 = 函数名
因此,在初始化函数指针时,函数名前面的取地址符号&可以省略。
函数指针定义和初始化的代码演示:
#include<stdio.h>
int Add(int x, int y)
{
return x + y;
}
int main()
{
//写为int (*pf)(int, int) = Add 也可
int (*pf)(int, int) = &Add; //定义函数指针,并初始化
//三个printf函数打印的结果相同
printf("%p\n", Add);
printf("%p\n", &Add);
printf("%p\n", pf);
return 0;
}
1.2 通过函数指针调用函数
代码演示:
#define _CRT_SECURE_NO_WARNINGS 1
#include<stdio.h>
int Add(int x, int y)
{
return x + y;
}
int main()
{
int (*pf)(int, int) = &Add;
int x = 0;
int y = 0;
scanf("%d %d", &x, &y); //假设输入4 5
int ret = (*pf)(x, y); //通过函数指针调用函数
printf("ret = %d\n", ret); //打印 ret = 9
return 0;
}
由于 函数名 = &函数名,因此,通过函数指针调用函数的代码还可以写为:int ret = pf(x, y);
注意:通过函数指针调用函数时,指针变量名前面的*只是为了便于理解,提高代码的可读性,其实并无实际作用。但是,如果要写上*,就必须将函数指针变量的变量名和*用括号括起来,即写为(*pf)(x,y)的格式,因为如果不加括号,pf会首先和后面的括号进行结合,再结合前面的*。
1.3 几段特殊代码的解读
<1> (*(void(*)( ))0)( )
- void(*)( ) —— 是一个函数指针类型(无参数,返回void)
- (void(*)())0 —— 将0强制类型转换为函数指针
- (*(void(*)( ))0)( ) —— 调用0地址处的函数
<2> void (* signal(int, void(*)(int))) (int)
- signal(int, void(*)(int)) —— siganl为函数,传入参数有两个,一个为整型,另一个为函数指针类型(指向一个输出参数为int型,返回值为void类型的函数)。
- void(* )(int) —— 去除signal(int, void(*)(int))进行分析,void(* )(int)为signal函数的返回值类型,signal的返回值类型为函数指针(指向一个输出参数为int型,返回值为void类型的函数)。
- 整段代码的含义是对signal函数进行声明,该函数有两个输入参数,一个为int型,另一个为void(*)()类型,该函数的返回值为void(*)()类型。
注意:代码<2>不可以写为 void(*)(int) (signal(int, void(*)(int)))的格式。如果一个函数的返回值类型为函数指针,则*必须与函数名靠在一起。
代码<2>的简化写法:(运用typedef对类型进行重定义)
typedef void(* pfun_t)(int); //将void(*)(int)类型重定义为pfun_t
pfun_t signal(int, pfun_t); //将代码2进行简化
二. 函数指针数组
函数指针数组就是存放函数指针变量的数组。
2.1 函数指针数组的定义和初始化
#include<stdio.h>
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; //定义并初始化函数指针变量pf1,使其指向函数Add的地址
int (*pf2)(int, int) = Sub; //定义并初始化函数指针变量pf2,使其指向函数Sub的地址
//定义并初始化函数指针数组pfarr,其包含两个函数指针变量pf1和pf2
int (*pfarr[2])(int, int) = { pf1,pf2 };
return 0;
}
函数指针数组定义和初始化的通用格式:
函数返回值类型 (*函数指针数组变量名[包含元素个数])(函数参数类型) = { 函数指针变量 }
2.2 函数指针数组的应用 —— 转移表
本文在1.2章节讲到了通过函数指针调用函数的方法,这里大家可能会有疑惑,直接调用函数不好吗,通过函数指针调用函数不是多此一举吗?对于1.2中的演示代码,通过函数指针调用函数确实没有必要,但是,在某些特定的场景下,通过函数指针调用则可以大大减少代码量,提升代码的运行效率和可读性。这里就涉及到函数指针数组的一个重要功能 —— 转移表。
通过对比两种不同的函数调用方法,模拟实现一个可以进行整数加减乘除的简易计算器,来说明使用函数指针数组的优越性。
- 方法1:主函数通过switch选择语句来判断调用哪个函数
#define _CRT_SECURE_NO_WARNINGS 1
#include<stdio.h>
void menu()
{
printf("******* 1.Add *******\n");
printf("******* 2.Sub *******\n");
printf("******* 3.Mul *******\n");
printf("******* 4.Div *******\n");
printf("******* 0.end *******\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; //两个操作数x和y
do
{
menu();
printf("请选择你要进行的操作: > ");
int ret = 0; // 函数返回值
scanf("%d", &input);
switch (input)
{
case 0:
printf("程序结束!\n");
break;
case 1: //输入1选择加法操作
printf("请输入两个操作数: > ");
scanf("%d %d", &x, &y);
ret = Add(x, y); //调用加法函数
printf("ret = %d\n", ret);
break;
case 2: //输入2选择减法操作
printf("请输入两个操作数: > ");
scanf("%d %d", &x, &y);
ret = Sub(x, y); //调用减法函数
printf("ret = %d\n", ret);
break;
case 3: //输入3选择乘法操作
printf("请输入两个操作数: > ");
scanf("%d %d", &x, &y);
ret = Mul(x, y); //调用乘法函数
printf("ret = %d\n", ret);
break;
case 4: //输入4选择除法操作
printf("请输入两个操作数: > ");
scanf("%d %d", &x, &y);
ret = Div(x, y); //调用除法函数
printf("ret = %d\n", ret);
break;
default:
printf("输入错误!\n");
break;
}
} while (input);
return 0;
}
这段程序有近80行代码,且每一个case后边,都跟有重复或相似的代码语句,整段程序的代码冗余度高,可读性查,看起来十分别扭。
那这就需要我们思考,如何使这段代码变得简洁?这里就可以用到函数指针数组做转移表来简化代码。需要定义一个函数指针数组,包含加、减、乘、除四个自定义函数的地址,通过函数指针调用函数,来实现代码的简化。图2.1为转移表图解。
- 方法2:主函数利用函数指针数组(转移表)调用函数
#define _CRT_SECURE_NO_WARNINGS 1
#include<stdio.h>
void menu()
{
printf("******* 1.Add *******\n");
printf("******* 2.Sub *******\n");
printf("******* 3.Mul *******\n");
printf("******* 4.Div *******\n");
printf("******* 0.end *******\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 (*pfarr[5])(int, int) = { NULL,Add,Sub,Mul,Div }; //定义并初始化函数指针数组pfarr
do
{
menu();
int x = 0;
int y = 0;
printf("请选择你要进行的操作: > ");
scanf("%d", &input);
if (input >= 1 && input <= 4) //如果input为1-4之间的整型,执行对应操作
{
printf("请输入两个操作数: > ");
scanf("%d %d", &x, &y);
int ret = (pfarr[input])(x, y);
printf("ret = %d\n", ret);
}
else if(input == 0)
{
printf("程序结束!\n");
}
else
{
printf("输入错误!\n");
}
} while (input);
return 0;
}
相对于上段代码,这段代码在简洁性和可读性方面明显更强,代码的运行效率也更高,由此体现出函数指针调用函数(函数指针数组)在程序设计中的巨大作用。
2.3 指向函数指针数组的指针
这里以案例的形式来说明:
int (*p1)(int, int); //定义函数指针p1
int (*p2[4])(int, int); //定义函数指针数组p2,p2包含4个函数指针
int (*(*p3)[4])(int, int) // 定义指向函数指针数组的指针
对int (*(*p3)[4])(int, int)进行拆分解读:
- (*p3)[4]:指向含有四个元素的数组的数组指针
- int(* )(int, int):去除(*p3)[4]进行分析,数组指针p3指向的数组的元素类型为函数指针,这里的函数指针指向有两个int型输入参数、返回值类型为int的函数
- int (*(*p3)[4])(int, int) 定义了指向函数指针数组的指针,该指针变量指向的数组含有4个元素,每个元素均是函数指针类型。
三. 回调函数
3.1 回调函数的特性
- 是一个通过函数指针调用的函数。
- 把函数的指针(地址)作为函数的参数传递给另一个函数,当这个指针被用来调用其所指向的函数时,我们就说这是回调函数。
- 回调函数不是由该函数的实现方式直接调用,而是在特定的事件或条件发生时由另外一方调用,用于对该事件或条件的相应。
3.2 回调函数的典型应用 —— qsort( )快速排序函数
3.2.1 qsort函数可以实现的功能
qsort函数为快速排序函数,可以实现对不同数据类型的升序或降序排序。具体包括:对整型数据的排序、对字符型数据的排序、对结构体类型数据的排序。
3.2.2 qsort函数的原型及参数
查阅MSDN,的C语言标准给出的qsort( )函数可以实现的功能、函数原型、及使用qsort( )函数要引用的头文件为:
qsort( )函数的四个参数:
- base:待排序元素的首元素个数
- num:待排序元素的个数
- width:待排序元素的大小(字节为单位)
- compare:函数指针,用于比较待排序数据中两个元素的函数
3.2.3 对compare函数的特别解释
compare函数就是前文中提到的回调函数,程序员在使用qsort函数时,需要自己书写compare函数,具体的书写格式见本文3.3章。
compare函数的标准格式:
int compare(const void* e1, const void* e2)
{
return *(待排序数组元素类型*)e1 - *(待排序数组元素类型*)e2;
}
3.3 qsort函数的应用
3.3.1 用qsort函数对整型数据进行排序
代码演示:
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
int cmp_int(void* e1, void* e2)
{
return *(int*)e1 - *(int*)e2; //实现升序排序
//若希望实现降序排序,则应写为return *(int*)e2 - *(int*)e21;
}
int main()
{
int arr1[] = { 1,4,2,5,2,5,9,0 };
int sz = sizeof(arr1) / sizeof(arr1[0]);
qsort(arr1, sz, sizeof(int), cmp_int); //qsort实现升序排序
for (int i = 0; i < sz; i++)
{
printf("%d ", arr1[i]); //打印0 1 2 2 4 5 5 9
}
printf("\n");
}
3.3.2 用qsort函数对字符型数据进行排序
代码演示:
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
int cmp_char(void* e1, void* e2)
{
return *(char*)e1 - *(char*)e2;
}
int main()
{
char ch[] = { 'a','d','u','a','s','h' };
int sz = sizeof(ch) / sizeof(ch[0]);
//按照字符数据对应的ASCII码值,对字符进行升序排序
qsort(ch, sz, sizeof(char), cmp_char);
int i = 0;
for (i = 0; i < sz; i++)
{
printf("%c ", ch[i]); //打印a a d h s u
}
printf("\n");
}
3.3.3 用qsort函数对结构体类型数据进行排序
这里以很多C语言教程在进行结构体讲解时使用的学生信息结构体为例,分别按照学生姓名、学生年龄对结构体数据进行排序。
- 按照学生姓名排序
代码演示:
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
struct stu
{
char name[20];
int age;
};
int sort_by_name(void* e1, void* e2)
{
return strcmp((*(struct stu*)e1).name,(*(struct stu*)e2).name);
}
int main()
{
struct stu s[3] = { {"zhangsan",25},{"lisi",20},{"wangwu",30} };
int sz = sizeof(s) / sizeof(s[0]);
qsort(s, sz, sizeof(s[0]), sort_by_name);
return 0;
}
调试运行结果:
- 按照学生年龄排序
代码演示:
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
struct stu
{
char name[20];
int age;
};
int sort_by_age(void* e1, void* e2)
{
return ((struct stu*)e1)->age - ((struct stu*)e2)->age;
}
int main()
{
struct stu s[3] = { {"zhangsan",25},{"lisi",20},{"wangwu",30} };
int sz = sizeof(s) / sizeof(s[0]);
qsort(s, sz, sizeof(s[0]), sort_by_age);
return 0;
}
调试运行结果:
全文结束,感谢大家的阅读,敬请批评指正。