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

目录

  • 前言
  • 回调函数
    • 回调函数的概念
    • 回调函数的使用
  • 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.mfbz.cn/a/873809.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

光伏并网发电系统中电能质量监测与优化技术探讨

0引言 随着清洁能源技术的持续进步与广泛应用&#xff0c;光伏并网发电系统亦逐步崭露头角。作为一种关键的电力供应方式&#xff0c;其受到了广泛的关注。然而&#xff0c;由于天气等外部条件的影响&#xff0c;光伏发电系统面临若干挑战。电能质量问题&#xff0c;诸如电压波…

并网光伏发电系统对电网电能质量的影响

0引言 随着清洁能源技术的持续进步&#xff0c;光伏发电作为一种关键的可再生能源&#xff0c;正逐步成为电力系统不可或缺的组成部分。然而&#xff0c;将并网光伏发电系统与传统电网相连接&#xff0c;可能会对电网的电能质量造成一定影响&#xff0c;包括但不限于电压波动、…

ROS2 Nav2 - 模型预测路径积分控制器(MPPI)

系列文章目录 前言 这是一个预测控制器&#xff08;局部轨迹规划器&#xff09;&#xff0c;用于实现模型预测路径积分&#xff08;MPPI&#xff09;算法&#xff0c;以跟踪具有自适应避障功能的路径。它包含基于插件的 critic 函数&#xff0c;可影响算法的行为。它由 Aleksei…

大模型RAG实战|构建知识库:文档和网页的加载、转换、索引与存储

我们要开发一个生产级的系统&#xff0c;还需要对LlamaIndex的各个组件和技术进行深度的理解、运用和调优。本系列将会聚焦在如何让系统实用上&#xff0c;包括&#xff1a;知识库的管理&#xff0c;检索和查询效果的提升&#xff0c;使用本地化部署的模型等主题。我将会讲解相…

【OpenCV】灰度化和二值化处理图像

文章目录 1. 图像灰度化处理对比2. 代码示例3. 二值化处理 1. 图像灰度化处理对比 2. 代码示例 #include <opencv2/opencv.hpp> using namespace cv;int main() {Mat currentImage imread("path_to_image.jpg"); // 读取彩色图像Mat grayImage;// 将彩色图像…

STL-List常用接口

List常用接口 insert list<int>::iterator pos find(lt.begin(), lt.end(), 3); if (pos ! lt.end())lt.insert(pos, 30); for (auto e : lt)cout << e << " "; cout << endl; list的不会失效&#xff0c;而vector会失效。 erase后均会失…

asynMotorController控制器类

电机控制器的基类&#xff0c;实际的电机控制器从这个类派生 asynMotorController.h头文件 /* asynMotorController.h* 这个文件为asynMotorController定义了基类。* 真实电机控制器从这个类派生。它派生字PortDriver.*/ #ifndef asynMotorController_H #define asynMotorCont…

《Attention Is All You Need》论文导读

版权声明 本文原创作者:谷哥的小弟作者博客地址:http://blog.csdn.net/lfdfhl论文背景 《Attention Is All You Need》这篇具有里程碑意义的论文,彻底改变了自然语言处理(NLP)的研究和应用格局。在此之前,循环神经网络(RNN)及其变体,如长短期记忆网络(LSTM),是处理…

【原创】java+springboot+mysql学生信息管理系统设计与实现

个人主页&#xff1a;程序猿小小杨 个人简介&#xff1a;从事开发多年&#xff0c;Java、Php、Python、前端开发均有涉猎 博客内容&#xff1a;Java项目实战、项目演示、技术分享 文末有作者名片&#xff0c;希望和大家一起共同进步&#xff0c;你只管努力&#xff0c;剩下的交…

内网穿透的应用-本地化部署Elasticsearch平替工具OpenObserve并实现无公网IP远程分析数据

文章目录 前言1. 安装Docker2. Docker镜像源添加方法3. 创建并启动OpenObserve容器4. 本地访问测试5. 公网访问本地部署的OpenObserve5.1 内网穿透工具安装5.2 创建公网地址 6. 配置固定公网地址 前言 本文主要介绍如何在Linux系统使用Docker快速本地化部署OpenObserve云原生可…

景联文科技:提供高质量多模态数据标注,推动智能化转型

随着人工智能技术的快速发展&#xff0c;多模态数据标注成为推动智能系统更深层次理解和应用的关键技术之一。 作为行业领先的多模态数据标注服务商&#xff0c;景联文科技凭借其在技术、流程和人才方面的综合优势&#xff0c;推出了全面的多模态标注解决方案&#xff0c;助力…

网上花店管理系统小程序的设计

管理员账户功能包括&#xff1a;系统首页&#xff0c;个人中心&#xff0c;管理员管理&#xff0c;客服聊天管理&#xff0c;基础数据管理&#xff0c;论坛交流管理&#xff0c;公告信息管理&#xff0c;用户管理&#xff0c;轮播图信息 微信端账号功能包括&#xff1a;系统首…

微波无源器件2 用于双极化波束形成网络的增强型双极化定向耦合器

摘要&#xff1a; 定向耦合器和混合相移器是用于实现波束形成网络的关键器件。通常一个波束形成网络用线极化和正交极化两个极化给天线馈电。双极化器件被用于降低波束形成网络的复杂性和尺寸。双极化定向耦合器由相同的作者提出。一种增强型的双极化耦合器在本文中提出。此器件…

【Java 优选算法】双指针(上)

欢迎关注个人主页&#xff1a;逸狼 创造不易&#xff0c;可以点点赞吗~ 如有错误&#xff0c;欢迎指出~ 目录 移动零 分析 代码 复写零 分析 代码 快乐数 分析 代码 盛最多水的容器 分析 代码 移动零 题目链接 分析 双指针算法,利用两个指针cur和dest将数组划分为三个区间…

基于Java的垃圾分类网站系统

你好呀&#xff0c;我是计算机学姐码农小野&#xff01;如果有相关需求&#xff0c;可以私信联系我。 开发语言&#xff1a;Java 数据库&#xff1a;MySQL 技术&#xff1a;SpringBoot框架&#xff0c;B/S架构 工具&#xff1a;MyEclipse, Tomcat 系统展示 首页 用户管理…

面试笔试 场景题(部分总结)

文章目录 题目--找出一堆随机数中的前 k 大数字PriorityQueue 类PriorityQueue 常用方法 题目--数组中的第 K 个最大元素题目--二叉搜索树中第 K 小的元素 题目–找出一堆随机数中的前 k 大数字 找出一堆随机数中的前 k 大数字(小根堆)&#xff0c;找出一堆随机数中的前 k 小数…

捷途山海T2纯电续航突破200km,直达208km!

若你向我询问“方盒子”造型的SUV该如何选择&#xff0c;我会毫不犹豫地推荐捷途山海T2。这款车型以其独特的硬派风格&#xff0c;在众多SUV中脱颖而出。不同于坦克300和北京BJ40的单一性格&#xff0c;捷途山海T2在双电机与高性能电池组的共同加持下&#xff0c;展现出了更为全…

大模型好书分享:《精通Transformer,从零开始构建最先进的NLP模型》(附PDF)

这本大模型书籍我已经上传CSDN&#xff0c;朋友们如果需要可以微信扫描下方CSDN官方认证二维码免费领取【保证100%免费】 内容简介 国内第1本Transformer——变形金刚红书 如果一定要说未来谁能引领人工智能世界&#xff0c;是Transformer而非chatGPT&#xff01; 编…

python-新冠病毒

题目描述 假设我们掌握了特定时间段内特定城市的新冠病毒感染病例的信息。在排名 i 的当天有 i 个案例&#xff0c;即&#xff1a; 第一天有一例感染第二天有两例感染第三天有三例感染以此类推...... 请计算 n 天内的感染总数和每天平均感染数。 输入 整数 n 表示天数&…

免费的文章生成器有哪些?盘点5款为你自动生成文章

文章生成器的普及&#xff0c;为创作者提供了全新的创作视角和效率提升途径。那么&#xff0c;市面上有哪些免费的文章生成器可供我们使用呢&#xff1f;接下来&#xff0c;本文将为大家详细介绍5款功能强大、操作简便的免费文章生成器&#xff0c;它们将有助大家在内容创作的道…