【C语言】指针第三弹(函数指针、函数指针数组、回调函数)

目录

一. 函数指针

1.1 函数指针的定义和初始化

1.2 通过函数指针调用函数

1.3 几段特殊代码的解读

二. 函数指针数组

2.1 函数指针数组的定义和初始化

2.2 函数指针数组的应用 —— 转移表 

2.3 指向函数指针数组的指针

三. 回调函数

3.1 回调函数的特性

3.2 回调函数的典型应用 —— qsort( )快速排序函数

3.2.1 qsort函数可以实现的功能

3.2.2 qsort函数的原型及参数

3.2.3 对compare函数的特别解释

3.3 qsort函数的应用

3.3.1 用qsort函数对整型数据进行排序

3.3.2 用qsort函数对字符型数据进行排序

3.3.3 用qsort函数对结构体类型数据进行排序


一. 函数指针

函数指针是用于存放函数的地址的指针变量

1.1 函数指针的定义和初始化

定义:int (*pf)(int, int)   //定义了一个函数指针pf,它指向一个具有两个int型输入参数、返回值类型为int型的函数。

初始化:int (*pf)(int, int)  = &函数名

函数指针定义和初始化的通用格式:

函数返回值类型 (*指针变量名)(形参1类型, 形参2类型,  ...... ) = &函数名

注意:&数组名 \neq 数组名   但是 &函数名 = 函数名

因此,在初始化函数指针时,函数名前面的取地址符号&可以省略。

函数指针定义和初始化的代码演示:

#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.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 回调函数的特性

  1. 是一个通过函数指针调用的函数。
  2. 把函数的指针(地址)作为函数的参数传递给另一个函数,当这个指针被用来调用其所指向的函数时,我们就说这是回调函数。
  3. 回调函数不是由该函数的实现方式直接调用,而是在特定的事件或条件发生时由另外一方调用,用于对该事件或条件的相应。

3.2 回调函数的典型应用 —— qsort( )快速排序函数

3.2.1 qsort函数可以实现的功能

qsort函数为快速排序函数,可以实现对不同数据类型的升序或降序排序。具体包括:对整型数据的排序、对字符型数据的排序、对结构体类型数据的排序。

3.2.2 qsort函数的原型及参数

查阅MSDN,的C语言标准给出的qsort( )函数可以实现的功能、函数原型、及使用qsort( )函数要引用的头文件为:

图3.1 MSDN中给出的对快排函数qsort的定义

 qsort( )函数的四个参数:

  1. base:待排序元素的首元素个数
  2. num:待排序元素的个数
  3. width:待排序元素的大小(字节为单位)
  4. compare:函数指针,用于比较待排序数据中两个元素的函数

3.2.3 对compare函数的特别解释

compare函数就是前文中提到的回调函数,程序员在使用qsort函数时,需要自己书写compare函数,具体的书写格式见本文3.3章。

compare函数的标准格式:

int compare(const void* e1, const void* e2)

{

        return *(待排序数组元素类型*)e1 - *(待排序数组元素类型*)e2;

}

图3.2 Compare函数两个参数的存储位置示意

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;
}

调试运行结果:

图3.3 按照学生姓名对学生信息结果体数据进行排序的结果
  •  按照学生年龄排序

代码演示:

#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;
}

 调试运行结果:

图3.4 按照学生年龄对学生信息结果体数据进行排序的结果

 

全文结束,感谢大家的阅读,敬请批评指正。

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值