【C语言】指针深入讲解(下)

news2024/11/23 2:53:26

目录

  • 前言
  • 回调函数
    • 回调函数的概念
    • 回调函数的使用
  • qsort函数的使用和模拟实现
    • qsort函数的介绍
    • qsort函数的使用
    • qsort函数模拟实现

前言

今天我们来学习指针最后一个知识点回调函数,这个知识点也很重要,希望大家能坚持学习下去。
没学习之前指针知识内容的,可以点击这里进行学习。

回调函数

回调函数的概念

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

回调函数的使用

在指针深入讲解(中)篇中我们写的计算机的实现的代码中。
我们设计了实现功能的函数。

#include <stdio.h>
int add(int a, int b)
{
 return a + b;
}
int sub(int a, int b)
{
 return a - b;
}
int mul(int a, int b)
{
 return a * b;
}
int div(int a, int b)
{
 return a / b;
}

在没学习回调函数之前,主函数中有很多冗余的地方。
下面代码,scanf和printf函数大量重复出现。

int main()
{
	int x, y;
	int input = 1;
	int ret = 0;
do
{
	printf("*************************\n");
	printf(" 1:add 2:sub \n");
	printf(" 3:mul 4:div \n");
	printf(" 0:exit \n");
	printf("*************************\n");
	printf("请选择:");
	scanf("%d", &input);
switch (input)
{
	case 1:
	printf("输⼊操作数:");
	scanf("%d %d", &x, &y);
	ret = add(x, y);
	printf("ret = %d\n", ret);
	break;
	case 2:
	printf("输⼊操作数:");
	scanf("%d %d", &x, &y);
	ret = sub(x, y);
	printf("ret = %d\n", ret);
	break;
	case 3:
	printf("输⼊操作数:");
	scanf("%d %d", &x, &y);
	ret = mul(x, y);
	printf("ret = %d\n", ret);
	break;
	case 4:
	printf("输⼊操作数:");
	scanf("%d %d", &x, &y);
	ret = div(x, y);
	printf("ret = %d\n", ret);
	break;
	case 0:
	printf("退出程序\n");
	break;
	default:
	printf("选择错误\n");
	break;
}
} while (input);
return 0;
}

我们观察功能实现的函数可以发现,函数的结构都是 int 函数名 (int,int) 的形式,这样就可以把函数的地址作为参数传给这样类型的函数指针,指针指向那个函数就调用那个函数,这就是使用的回调函数

用回调函数的形式,简化计算器实现的主函数代码。

void calc(int(*pf)(int, int))
{
 int ret = 0;
 int x, y;
 printf("输⼊操作数:");
 scanf("%d %d", &x, &y);
 ret = pf(x, y);
 printf("ret = %d\n", ret);
}
int main()
{
	int x, y;
	int input = 1;
	int ret = 0;
do
{
	printf("*************************\n");
	printf(" 1:add 2:sub \n");
	printf(" 3:mul 4:div \n");
	printf(" 0:exit \n");
	printf("*************************\n");
	printf("请选择:");
	scanf("%d", &input);
switch (input)
{
	case 1:
	calc(add);
	break;
	case 2:
	calc(sub);
	break;
	case 3:
	calc(mul);
	break;
	case 4:
	calc(div);
	break;
	case 0:
	printf("退出程序\n");
	break;
	default:
	printf("选择错误\n");
	break;
}
} while (input);
return 0;
}

这样代码就简化了很多,pf是指向函数的指针,当我们选择哪个功能时,会把实现该功能的函数地址传给pf指针,当条件满足,就会通过pf调用该函数。

qsort函数的使用和模拟实现

qsort函数的介绍

在这里插入图片描述

参数介绍

参数名含义
base指向要排序的数组的第一个对象的指针
num指向的数组中的元素数。是无符号整型,size_t
size数组中每个元素的大小(以字节为单位)。是无符号整型。size_t
compar指向比较两个元素的函数的指针。此函数被重复调用以比较两个元素。

最后一个参数很重要单独说明一下
在这里插入图片描述
compar是指向比较函数的指针,返回值为int型。
返回 <0说明p1指向的元素小于p2指向的元素。
返回 =0说明p1指向的元素等于p2指向的元素。
返回 >0说明p1指向的元素大于p2指向的元素。

qsort函数的使用

使⽤qsort函数排序整型数据

#include <stdio.h>
//qosrt函数的使⽤者得实现⼀个⽐较函数
int int_cmp(const void * p1, const void * p2)
{
 return (*( int *)p1 - *(int *) p2);
}
int main()
{
 int arr[] = { 1, 3, 5, 7, 9, 2, 4, 6, 8, 0 };
 int i = 0;
 
 qsort(arr, sizeof(arr) / sizeof(arr[0]), sizeof (int), int_cmp);
 for (i = 0; i< sizeof(arr) / sizeof(arr[0]); i++)

 {
 printf( "%d ", arr[i]);
 }
 printf("\n");
 return 0;
}

使⽤qsort排序结构数据

struct Stu //学⽣
{
 char name[20];//名字
 int age;//年龄
};
//假设按照年龄来⽐较
int cmp_stu_by_age(const void* e1, const void* e2)
{
 return ((struct Stu*)e1)->age - ((struct Stu*)e2)->age;
}
//strcmp - 是库函数,是专⻔⽤来⽐较两个字符串的⼤⼩的
//假设按照名字来⽐较
int cmp_stu_by_name(const void* e1, const void* e2)
{
 return strcmp(((struct Stu*)e1)->name, ((struct Stu*)e2)->name);
}
//按照年龄来排序
void test2()
{
 struct Stu s[] = { {"zhangsan", 20}, {"lisi", 30}, {"wangwu", 15} };
 int sz = sizeof(s) / sizeof(s[0]);
 qsort(s, sz, sizeof(s[0]), cmp_stu_by_age);
}
//按照名字来排序
void test3()
{
 struct Stu s[] = { {"zhangsan", 20}, {"lisi", 30}, {"wangwu", 15} };
 int sz = sizeof(s) / sizeof(s[0]);
 qsort(s, sz, sizeof(s[0]), cmp_stu_by_name);
}
int main()
{
 test2();
 test3();
 return 0;
}

qsort函数模拟实现

用回调函数,模拟实现qsort函数。
qsost底层采用的是快速排序的方法,在这里我们使用更简单的冒泡排序的排序算法来模拟实现qsort函数,快排会在学习数据结构时讲解。
我们要实现的qsort是可以针对任何数据进行排序,那想一下我们知道用户使用这个函数的时候是拿来排序什么数据吗?显然是不知道的,所以在内部实现时,我们需要更改什么呢?分析如下:

  1. 比较的方法
    由于不知道用户排序的数据类型,传过来的数组首元素地址我们必须使用void*指针接收,是不能进行解引用的,且数据类型是不能传参的,那我们该怎么找到相邻元素比较呢?
    于是我们在参数中添加了数组元素的大小(即宽度,一个元素占几个字节,这是用户可以传参的),这样就能找到相邻元素了
    通过首元素地址加减上一个数组元素占几个字节就可以找到相邻元素。
    公式为:元素地址=首元素地址+数组元素的宽度 * 数组元素下标
    在这里插入图片描述

注意:要把base强制类型转化为char型指针

接下来就是如何比较,由于我们不知道用户排序什么数据,所以没办法实现两个数据的比较,例如整数可以直接使用关系操作符,而字符串需要strcmp函数等等,于是我们把比较两个数据大小的函数交给用户去实现,所以在参数中使用了一个函数指针。

if (cmp ((char *) base + j*size , (char *)base + (j + 1)*size) > 0)

这里我们默认还是qsort的比较规则,用户实现compare函数时遵守:当第一个元素大于第二个元素时,就返回大于0的数字,此时我们交换,按这个规则排序出来为升序,反之为降序。

2.交换数据的方式
同样的是,我们不知道数据类型,但我们知道数据的大小,所以我们可以一个一个字节的交换

void _swap(void *p1, void * p2, int size)
{
 int i = 0;
 for (i = 0; i< size; i++)
 {
 char tmp = *((char *)p1 + i);
 *(( char *)p1 + i) = *((char *) p2 + i);
 *(( char *)p2 + i) = tmp;
 }
}

这样我们就完成了qsort函数的模拟实现。
源代码如下(排序整数):

#include<stdio.h>
void my_squrt(void* base, size_t num, size_t size, int (*compar)(void* p1, void* p2))
{
	int i = 0;
	int j = 1;
	for (i = 0; i < num - 1; i++)
	{
		for (j = 0; j < num - i-1; j++)
		{
		//判断大小
			if (compar((char*)base + j * size, (char*)base + (j + 1) * size) > 0)
			{
			//交换数据
				for (int k = 0; k < size; k++)
				{
					char tmp = *((char*)base + j * size + k);
					*((char*)base + j * size + k) = *((char*)base + (j + 1) * size + k);
					*((char*)base + (j + 1) * size + k) = tmp;
				}
			}
		}
	}
}
//整数的比较函数
int int_cmp(void* p1, void* p2)
{
	return *((int*)p2) - *((int*)p1);
}

int main()
{
	int arr[10] = { 1,2,3,4,5,6,7,8,9,10 }; \
		int sz = sizeof(arr) / sizeof(arr[0]);
	my_squrt(arr,sz,sizeof(arr[0]), int_cmp);
	int i = 0;
	//打印
	for (i = 0; i < sz; i++)
	{
		printf("%d ", arr[i]);
	}
	return 0;
}

总结:
qsout函数是典型的回调函数的例子,排序时我们不知道用户传进来的数据是什么类型,所以使用void *,把比较函数的实现方法交给用户实现,把实现函数通过函数指针传给qsout函数,在qsout函数内部比较时调用该函数。这就是回调函数。


感谢大家的观看, 大家可以在评论区留言,你们的支持就是我最大的动力。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2118750.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

k8s的配置管理

一、配置管理分为两种&#xff1a; 1. 加密配置&#xff1a;用来保存密码和token密钥对以及其它敏感的k8s资源。 2.应用配置&#xff1a;我们需要定制化的给应用进行配置&#xff0c;我们需要把定制好的配置文件同步到pod当中的容器。 二、加密配置 1.secret三种类型&#xf…

WPS取消首字母自动大写

WPS Office&#xff08;12.1.0.17827&#xff09; ① 点击文件&#xff0c;在文件中找到选项 ② 选择编辑&#xff0c;取消勾选

三国地理揭秘:为何北伐之路如此艰难,为何诸葛亮无法攻克陇右小城?

俗话说:天时不如地利&#xff0c;不是随便说说&#xff0c;诸葛亮六出祁山&#xff0c;连关中陇右的几座小城都攻不下来&#xff0c;行军山高路险&#xff0c;无法携带和建造攻城器械&#xff0c;是最难的&#xff0c;所以在汉中&#xff0c;无论从哪一方进攻&#xff0c;防守方…

计算机为啥选中二进制?

坊间传闻&#xff0c;当年&#xff0c;彷徨少年computer有幸读到东方奇书《道德经》中一段&#xff1a;“道生一&#xff0c;一生二&#xff0c;二生三&#xff0c;三生万物。”忽然灵光乍现&#xff0c;做五体投地状。“啊门、主啊&#xff0c;我get到了&#xff0c;狗屁二生三…

「滚雪球学MyBatis」教程导航帖(已完结)

写在前面 我是bug菌&#xff0c;CSDN | 掘金 | InfoQ | 51CTO | 华为云 | 阿里云 | 腾讯云 等社区博客专家&#xff0c;C站博客之星Top30&#xff0c;华为云2023年度十佳博主&#xff0c;掘金多年度人气作者Top40&#xff0c;掘金等各大社区平台签约作者&#xff0c;51CTO年度博…

python爬虫爬取淘宝商品比价||淘宝商品详情API接口

最近在学习北京理工大学的爬虫课程&#xff0c;其中一个实例是讲如何爬取淘宝商品信息&#xff0c;现整理如下&#xff1a; 功能描述&#xff1a;获取淘宝搜索页面的信息&#xff0c;提取其中的商品名称和价格 探讨&#xff1a;淘宝的搜索接口 翻页的处理 技术路线:requests‐…

随身WiFi大揭秘!9毛3000G?坑你没商量!博主亲测,教你如何避坑!

随身WiFi老坑人&#xff1f;流量收费坑&#xff1f;网速坑&#xff1f;今天本博主重金自费入购7款随身WiFi测评&#xff0c;这份避坑指南请您收好了&#xff01; 随身WiFi多吓人啊&#xff0c;一块9毛钱3000个G&#xff0c;还有的是5块9毛钱3000个G&#xff0c;确实有3000个G&…

同样数据源走RTMP播放延迟低还是RTSP低?

背景 在比较同一个数据源&#xff0c;是RTMP播放延迟低还是RTSP延迟低之前&#xff0c;我们先看看RTMP和RTSP的区别&#xff0c;我们知道&#xff0c;RTMP&#xff08;Real-Time Messaging Protocol&#xff09;和RTSP&#xff08;Real Time Streaming Protocol&#xff09;是…

京东鸿蒙上线前瞻——使用 Taro 打造高性能原生应用

背景 2024 年 1 月&#xff0c;京东正式启动鸿蒙原生应用开发&#xff0c;基于 HarmonyOS NEXT 的全场景、原生智能、原生安全等优势特性&#xff0c;为消费者打造更流畅、更智能、更安全的购物体验。同年 6 月&#xff0c;京东鸿蒙原生应用尝鲜版上架华为应用市场&#xff0c…

C++ 多态学习笔记(下)

开始新的学习之前&#xff0c;我们先通过一段涉及继承、多态的 代码来回忆、加深理解。 Animal作为基类&#xff0c;我们要给每种动物实例化出sound()的模块&#xff0c;因为Animal在实际意义上没什么好实例化的&#xff0c;所以设计成抽象类。 class Animal { public:virtua…

又考了两个Oracle认证:RAC和DataGuard,文末送资料

号主姚远目前已经拥有Oracle的认证超过20个了&#xff0c;最近又考了两个Oracle 19c的认证&#xff0c;是RAC和DataGuard。其实内容和12c没有太大的区别&#xff0c;但题目依然很难&#xff0c;很多选项模拟两可&#xff0c;需要对相关概念非常清楚才能通过考试。姚远的运气不错…

Linux网络:应用层协议http/https

认识URL URL是我们平时说的网址 eg&#xff1a;http常见的URL http://user:passwww.example.jp:80/dir/index.htm?uid1#ch1 注意&#xff1a; 服务器地址就是域名&#xff0c;相当于服务器ip地址 像http服务绑定80端口号&#xff0c;https服务绑定443端口。ssh服务端口绑定…

EasyCVR无法启动并报错“error while loading shared libraries”,如何解决?

安防监控/视频汇聚平台EasyCVR视频管理系统以其强大的拓展性、灵活的部署方式、高性能的视频能力和智能化的分析能力&#xff0c;为各行各业的视频监控需求提供了优秀的解决方案。通过简单的配置和操作&#xff0c;用户可以轻松地进行远程视频监控、存储和查看&#xff0c;满足…

Python操作ES集群API(增删改查等)

前言&#xff1a;本博客仅作记录学习使用&#xff0c;部分图片出自网络&#xff0c;如有侵犯您的权益&#xff0c;请联系删除 学习B站博主教程笔记&#xff1a; 最新版适合自学的ElasticStack全套视频&#xff08;Elk零基础入门到精通教程&#xff09;Linux运维必备—Elastic…

Vue组件:插槽的使用

在实际开发中&#xff0c;子组件往往只提供基本的交互功能&#xff0c;而内容是有父组件来提供的。为此&#xff0c;Vue.js 提供了一种混合父组件内容和子组件模板的方式&#xff0c;这种方式称为内容分发。 1、基本用法 Vue.js 参照当前 Web Components 规范草案实现了一套内…

和 InternLM 解锁“谁是卧底”新玩法

本文来自社区投稿&#xff0c;作者LangGPT联合发起人、东北大学在读博士生王明 在大模型技术日益普及的今天&#xff0c;AI 的应用已经渗透到各个领域&#xff0c;带来了无数创新和乐趣。今天&#xff0c;我们将一起探索如何搭建一个 AI 版的“谁是卧底”游戏。通过 InternStud…

【Unity基础】Input中GetAxis和GetAxisRaw的区别

一句话描述&#xff1a;GetAxis使用了平滑过渡&#xff0c;而GetAxisRaw是直接改变。 在Unity中&#xff0c;Input.GetAxisRaw 和 Input.GetAxis 都用于获取输入设备的轴向输入&#xff08;例如键盘、鼠标或手柄的摇杆&#xff09;&#xff0c;但它们的工作方式和返回值有细微…

GPU 服务器性能评估:多维度深度探索

在深度学习的浩瀚宇宙中&#xff0c;GPU 服务器犹如一颗璀璨的星辰&#xff0c;以其无与伦比的计算能力和效率引领着技术进步的浪潮。为了充分挖掘这一强大工具的潜力&#xff0c;我们需深入探寻其性能评估的奥秘&#xff0c;这不仅仅是对单一指标的简单堆砌&#xff0c;而是从…

从虚拟现实到元宇宙:Facebook引领未来社交的下一步

随着科技的迅猛发展&#xff0c;社交媒体正在经历一场深刻的变革。从最初的文本和图片交流&#xff0c;到如今的沉浸式虚拟现实&#xff08;VR&#xff09;和即将到来的元宇宙&#xff0c;社交互动的方式正在发生根本性的变化。作为行业领军者&#xff0c;Facebook&#xff08;…

3GPP协议入门——物理层基础(一)

1. 频段/带宽 NR指定了两个频率范围&#xff0c;FR1&#xff1a;通常称Sub 6GHz&#xff0c;也称低频5G&#xff1b;FR2&#xff1a;通常称毫米波&#xff08;Millimeter Wave&#xff09;&#xff0c;也称高频5G。 2. 子载波间隔 NR中有15kHz&#xff0c;30kHz&#xff0c;6…