【再识C进阶2(中)】详细介绍指针的进阶——函数指针数组、回调函数、qsort函数

2024-02-27 1483阅读

温馨提示:这篇文章已超过450天没有更新,请注意相关的内容是否还可用!

前言

💓作者简介: 加油,旭杏,目前大二,正在学习C++,数据结构等👀

💓作者主页:加油,旭杏的主页👀

⏩本文收录在:再识C进阶的专栏👀

🚚代码仓库:旭日东升 1👀

🌹欢迎大家点赞 👍 收藏 ⭐ 加关注哦!💖💖

学习目标:

       在这一篇博客中,我们要认识并理解函数指针数组的概念,再学会在特定情境下使用函数指针数组;简单认识一下指向函数指针数组的指针;认识一下回调函数,并通过qsort函数来认识一下回调函数。这就是本博客的学习目标。


学习内容:

通过上面的学习目标,我们可以列出要学习的内容:

  1. 认识并理解函数指针数组的概念
  2. 学会在特定情境下使用函数指针数组
  3. 简单认识一下指向函数指针数组的指针
  4. 认识一下回调函数
  5. 通过qsort函数来认识一下回调函数

一、函数指针数组

1.1 函数指针

       我们先来简单回顾一下函数指针,我们在初始C语言中学过指针的初阶,我们认识了整形指针、字符指针等基本数据类型的指针。整形指针是存放整形类型变量的地址字符指针是存放字符类型变量的地址……那么我们来看这个函数指针,明显是一个指针。通过小学学习的找规律进行编写句子,可以轻松地得到:函数指针是存放函数类型变量的地址(可能描述有些不正确)。看下图方便理解:

整形指针:存放整形类型变量的地址,在32位平台下是4个字节,在64位平台下是8个字节

字符指针:存放字符类型变量的地址,在32位平台下是4个字节,在64位平台下是8个字节

函数指针:存放函数类型变量的地址,在32位平台下是4个字节,在64位平台下是8个字节

1.1.1 了解什么是函数的地址? 

啊,读者可能会感觉到有点奇怪!为什么函数也有地址呢?

       因为函数是由一些运行的语句组成的,程序运行的时候就会把函数中的语句调用到内存中去,那么函数代码在内存中开始的那个内存空间的地址就是函数的地址!

接下来,让我们用代码来认识一下函数的地址:

void test()
{
    printf("Hellod,worid!");
}
int main()
{
    printf("%p\n", test);   //函数与数组类似,数组名表示数组首元素的地址,函数名表示函数的地址
    printf("%p\n", &test);  //&函数名拿到的是函数的地址
}

【再识C进阶2(中)】详细介绍指针的进阶——函数指针数组、回调函数、qsort函数

1.1.2 学习如何使用函数指针? 

       在了解完函数指针是什么,可能大家还不知道什么是函数指针?书接上文,函数的地址要想保存下来,需要怎么保存呢?下面,我们来看代码:

void test()
{
    printf("Hellod,worid!");
}
//下面pfun1和pfun2哪个有能力存放test函数的地址?
void (*pfun1)();
void *pfun2();

       首先,能够用于存储地址,就要求pfun1或者pfun2是指针,看上面,pfun1先与 * 结合,说明pfun1是指针,去掉指针就是指针所指向的类型是:void (),返回值类型为void。

那如何使用函数指针呢?下面来看代码:

void test()
{
    printf("Hellod,worid!\n");
}
int main()
{
    void (*pf)() = &test; //用pf指针存储函数的地址
    (*pf)();
    pf();
    test();
}

【再识C进阶2(中)】详细介绍指针的进阶——函数指针数组、回调函数、qsort函数

1.2 利用计算器代码来具体介绍函数指针数组

1.2.1 函数指针数组是什么?

       在学习完函数指针后,我们来认识一下函数指针数组是什么?和介绍函数指针一样,函数指针数组的主语是数组,在初始C语言中,我们学过数组的内容中介绍了一些常见的数组类型:整形数组,字符数组等基本数据类型的数组。整形数组是存放一些整形类型的变量字符数组是存放一些字符类型的变量……那么函数指针数组存放的值一些函数指针类型的变量。看下图,方便理解:

整形数组是存放一些整形类型的变量,数组存放的是想同类型的变量;

字符数组是存放一些字符类型的变量;

函数指针数组是存放一些函数指针类型的变量。

 下面用代码来认识一下函数指针数组:

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) = { pf1, pf2 };
}

【再识C进阶2(中)】详细介绍指针的进阶——函数指针数组、回调函数、qsort函数

1.2.2 利用画图对函数指针数组的写法详解:

【再识C进阶2(中)】详细介绍指针的进阶——函数指针数组、回调函数、qsort函数

1.2.3 计算器代码的实现

       在详细介绍分支与循环语句中,我们讲过像这种代码的设计结构,先有一个函数的主题、再有一个菜单、之后根据菜单的内容进行功能的设计,最后进行结束游戏。大致思路就是这,请读者继续跟着我的思路进行设计计算器:

1.2.3.1 计算器代码的主体
int main()
{
	int input = 0;
	do {
		menu();
		printf("请选择:>");
		scanf("%d", &input);
		switch (input)
		{
		case 1:
			break;
		//……
		default:
			break;
	    }
	} while (input);
	return 0;
}
 1.2.3.2 计算器的菜单
void menu()
{
	printf("***************************\n");
	printf("***** 1. Add   2. Sub *****\n");
	printf("***** 3. Mul   4. Div *****\n");
	printf("*****      0. exit    *****\n");
	printf("***************************\n");
}
1.2.3.3 计算器的自定义函数部分 

这个简单的计算器将实现四种计算的功能,分别是:加、减、乘、除。

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;
}
1.2.3.4 计算器函数主体的选择部分
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");
			break;
	    }
1.2.3.5 这种计算器代码的不足之处
  1. 当计算器的功能逐渐增加的时候,菜单会越来越长,所需要写出的函数也会越来越多,最重要的是switch()语句会越来越长。
  2. case语句中的代码存在冗余。

1.2.4 对计算器代码进行改进

       这种改进使得在以后对计算器的功能进行升级的时候,会非常方便!因为你只需要将函数指针数组进行修改,以及input的范围进行修改即可。

do {
		menu();
		printf("请选择:>");
		scanf("%d", &input);
		int (*pfarr[])(int, int) = { NULL, &Add, &Sub, &Mul, &Div };
		if (0 == input)
		{
			printf("退出计算器!\n");
		}
		else if (input >= 1 && input 比较,而两个结构体的数据可能不能直接用>比较。 

       在这个qsort函数中,最难的是第四个形参,这个形参所指向的函数的作用如下图:让两个数进行相减,根据结果与0进行比较判断谁在前,谁在后。

【再识C进阶2(中)】详细介绍指针的进阶——函数指针数组、回调函数、qsort函数

介绍一下void* 的指针:

  1. void* 是无类型指针,可以接收任意类型的地址;
  2. 不能进行解引用操作;
  3. 不能进行加、减整数的操作。

3.3 qsort函数的使用

//排序整形数组
int int_cmp(const void* e1, const void* e2)
{
	return (*(int*)e1 - *(int*)e2);
}
int main()
{
	int arr[10] = { 3,4,5,6,7,8,9,2,1,0 };
	int sz = sizeof(arr) / sizeof(arr[0]);
	qsort(arr, sz, sizeof(arr[0]), int_cmp);
	for (int i = 0; i  
//排序结构体中名字的顺序
struct student {
	char name[40];
	int age;
};
int name_cmp(const void* p5, const void* p6)
{
	return strcmp(((struct student*)p5)->name, ((struct student*)p6)->name);
}
int main()
{
    struct student s[3] = { {"zhangsan", 23}, {"lisi", 45}, {"wangwu", 78} };
	int sz2 = 3;
	qsort(s, sz2, sizeof(s[0]), name_cmp);
	for (int i = 0; i  
//排序结构体中年龄的大小
struct student {
	char name[40];
	int age;
};
int age_cmp(const void* p3, const void* p4)
{
	return ((struct student*)p3)->age - ((struct student*)p4)->age;
}
int main()
{
    qsort(s, sz2, sizeof(s[0]), age_cmp);
	for (int i = 0; i  

3.4 回调函数的作用

  1. 恰当时间发送通知;
  2. 让代码更加灵活;
  3. 提高运行效率。

学习产出:

  1. 认识并理解函数指针数组的概念
  2. 学会在特定情境下使用函数指针数组
  3. 简单认识一下指向函数指针数组的指针
  4. 认识一下回调函数
  5. 通过qsort函数来认识一下回调函数
VPS购买请点击我

免责声明:我们致力于保护作者版权,注重分享,被刊用文章因无法核实真实出处,未能及时与作者取得联系,或有版权异议的,请联系管理员,我们会立即处理! 部分文章是来自自研大数据AI进行生成,内容摘自(百度百科,百度知道,头条百科,中国民法典,刑法,牛津词典,新华词典,汉语词典,国家院校,科普平台)等数据,内容仅供学习参考,不准确地方联系删除处理! 图片声明:本站部分配图来自人工智能系统AI生成,觅知网授权图片,PxHere摄影无版权图库和百度,360,搜狗等多加搜索引擎自动关键词搜索配图,如有侵权的图片,请第一时间联系我们,邮箱:ciyunidc@ciyunshuju.com。本站只作为美观性配图使用,无任何非法侵犯第三方意图,一切解释权归图片著作权方,本站不承担任何责任。如有恶意碰瓷者,必当奉陪到底严惩不贷!

目录[+]