关于我、重生到500年前凭借C语言改变世界科技vlog.15——深入理解指针(4)

文章目录

  • 1.回调函数的介绍
  • 2. qsort使用实例
    • 2.1 qsort函数介绍
    • 2.2使用 qsort 函数排序整型数据
    • 2.3使用 qsort 排序结构数据
  • 3. qsort的模拟实现
  • 希望读者们多多三连支持
  • 小编会继续更新
  • 你们的鼓励就是我前进的动力!

1.回调函数的介绍

回调函数就是一个通过函数指针调用的函数

如果你把函数的指针(地址)作为参数传递给另一个函数,当这个指针被用来调用其所指向的函数时,我们就说这是回调函数

正如我们在上一篇 vlog 中讲到的转移表,就是经典的回调函数,回调函数不是函数本身自己实现的,而是在特定的事件或条件发生的时候由另一方调用,对该事件或条件进行响应

传送门:关于我、重生到500年前凭借C语言改变世界科技vlog.14——常见C语言算法

还是以一个简易的计算器做例子

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

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

每写一个计算方法都要写一种情况,不断地 scanf 输入,printf 输出,显得过于啰嗦

#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;
}
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 input = 1;
	do
	{

		printf("*************************\n");
		printf("    1:add        2:sub   \n");
		printf("    3:mul        4:div   \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;
}

把每种情况的函数统一由一个函数管理,把这些函数以参数的形式传给该函数,这些函数就被称为回调函数

值得注意的是,在上一篇 vlog 中的优化方案是使用函数指针数组,这里使用的是回调函数

2. qsort使用实例

2.1 qsort函数介绍

什么是qsort函数?

传送门:qsort-C++参考

在这里插入图片描述
qsort函数的作用:对数组的元素进行排序,对指向的数组的元素进行排序,每个元素字节长,使用函数确定顺序,此函数使用的排序算法通过调用指定的函数来比较元素对,并将指向它们的指针作为参数,该函数不返回任何值,但通过按照 定义对其元素重新排序来修改指向的数组的内容

包含头文件 #include <stdlib.h>, 其语法形式为:

void qsort (void* base, size_t num, size_t size,
int (compar)(const void,const void*));

base:指向要排序的数组的第一个对象的指针,转换为 .void*
num:指向的数组中的元素数,是无符号整型
size:数组中每个元素的大小(以字节为单位),是无符号整型
compare:指向比较两个元素的函数的指针,此函数被重复调用以比较两个元素

在这里插入图片描述
假设第一个指针为p1,第二个指针为p2,那么有以下结论:

1.如果p1指向的元素小于p2,则返回小于0的数字
2.如果二者相等,则返回0
3.如果p1指向的元素大于p2,则返回大于0的数字
4.默认排序为升序,若想降序以上结论反转即可

那么 qsort 函数是如何一个一个比较的呢?
只是单纯两个比较吗?需要加循环结构吗?

当qsort函数在执行排序过程中,每当需要比较两个数组元素以确定它们的相对顺序时,就会调用用户提供的这个比较函数

1.在划分步骤中,通常会选择一个基准元素(pivot),并通过设置两个指针(比如一个从数组开头,一个从数组结尾)来对数组进行划分,这两个指针的初始范围是从数组的起始地址和结束地址开始
2.例如,对于一个整数数组int arr[] = {1, 2, 3, 4,> 5},如果选择第一个元素作为基准元素,那么一个指针可能从&arr[0]开始(指向首元素),另一个指针可能从&arr[sizeof(arr) / sizeof(arr[0]) - 1]开始(指向尾元素)
3.然后这两个指针会根据与基准元素的比较结果(通过调用比较函数)进行移动,在移动过程中,它们的范围会不断变化,直到完成划分操作,使得数组被分成两部分,一部分元素小于基准元素,另一部分元素大于基准元素。此时这两个指针的最终范围就是划分后两部分数组的边界地址,比如一个指针可能停留在小于基准元素那部分数组的最后一个元素地址处,另一个指针可能停留在大于基准元素那部分数组的第一个元素地址处

2.2使用 qsort 函数排序整型数据

#include <stdio.h>
#include <stdlib.h>

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

强调 p1 和 p2 必须是 const void* 类型的指针

这里定义了int_cmp函数,qsort函数在对数组进行排序时,需要一个能比较数组元素大小关系的函数作为参数,int_cmp函数接受两个const void *类型的指针p1和p2,这种通用指针类型使得qsort函数可以处理各种类型数据的排序(虽然这里实际只用于整数数组),在函数内部,先将p1和p2这两个通用指针转换为int *类型指针,以便能解引用获取到对应的整数,然后通过计算这两个整数的差值并返回,返回值的正负情况决定了qsort函数对数组元素的排序顺序:返回值小于 0,表示p1所指向的整数小于p2所指向的整数,那么在排序结果中p1对应的元素会排在p2对应的元素之前;返回值大于 0,意味着p1所指向的整数大于p2所指向的整数,p1对应的元素会排在p2对应的元素之后;若返回值等于 0,则表明这两个整数相等,此时元素的相对顺序在排序中可保持原有顺序或按其他默认规则处理

2.3使用 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;
}
//假设按照名字来⽐较
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;
}

strcmp - 是库函数,是专门用来比较两个字符串的大小的

该函数也和整数排列类似,只不过指针类型有所区别

3. qsort的模拟实现

使用回调函数,模拟实现qsort,
qsost底层采用的是快速排序的方法,在这里我们使用更简单的冒泡排序的排序算法来模拟实现qsort函数,对快排想要了解更多的,在上一篇 vlog 讲到了冒泡排序

传送门:关于我、重生到500年前凭借C语言改变世界科技vlog.14——常见C语言算法

首先是要实现排序模拟,那么运用冒泡排序就行,然后对两个数进行对比:

void Swap(char* buf1, char* buf2, size_t width)
{
    int i = 0;
    char tmp = 0;
    for (i = 0; i < width; i++)
    {
        tmp = *buf1;
        *buf1 = *buf2;
        *buf2 = tmp;

        buf1++;
        buf2++;
    }
}

void bubble_sort(void* base, size_t sz, size_t width, int (*cmp)(const void* p1, const void* p2))
{
    //趟数
    int i = 0;
    for (i = 0; i < sz - 1; i++)
    {
        //一趟内部的两两比较
        int j = 0;
        for (j = 0; j < sz - 1 - i; j++)
        {
            //if (arr[j] > arr[j + 1])
            //比较两个元素
            if (cmp((char*)base + j * width, (char*)base + (j + 1) * width) > 0)
            {
                //交换两个元素
                Swap((char*)base + j * width, (char*)base + (j + 1) * width, width);
            }
        }
    }
}

由于不知道用户排序的数据类型,传过来的数组首元素地址我们必须使用void*指针接收,不能进行解引用,且数据类型不能传参的,那我们该怎么找到相邻元素比较呢?

因为不知道接收数据的类型所以我们用char* 来一个字节一个字节移动,同样对比两个数也是如此,这就保证了这个模拟的函数能够接受各种类型的数据

使用 void* 指针实现了对不同数据排序,这种编程也叫做泛型编程

希望读者们多多三连支持

小编会继续更新

你们的鼓励就是我前进的动力!

请添加图片描述

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

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

相关文章

内网项目,maven本地仓库离线打包,解决Cannot access central in offline mode?

背景&#xff1a; 内网项目打包&#xff0c;解决Cannot access central in offline mode? 1、修改maven配置文件&#xff1a; localRepository改为本地仓库位置 <localRepository>D:\WorkSpace\WorkSoft\maven-repository\iwhalecloud-repository\business</loca…

笔记整理—linux驱动开发部分(8)framebuffer类设备

framebuffer显示设备。 在应用层直接抽象位向DDR中存放图片。 在操作系统中&#xff0c;将上图分为两个部分&#xff1a;驱动应用。 使用复制的方法效率十分的低&#xff0c;所以有了内存映射方法实现图片的显示。 framebuffer帧&#xff08;铺满一个屏幕&#xff09;&#xff…

第三十章 章节练习商品列表组件封装

目录 一、需求说明 二、技术要点 三、完整代码 3.1. main.js 3.2. App.vue 3.3. MyTable.vue 3.4. MyTag.vue 一、需求说明 1. my-tag 标签组件封装 (1) 双击显示输入框&#xff0c;输入框获取焦点 (2) 失去焦点&#xff0c;隐藏输入框 (3) 回显标签信息 (4) 内…

EHOME视频平台EasyCVR萤石设备视频接入平台视频诊断技术可以识别哪些视频质量问题?

EasyCVR视频监控汇聚管理平台是一款针对大中型项目设计的跨区域网络化视频监控集中管理平台。萤石设备视频接入平台EasyCVR不仅具备视频资源管理、设备管理、用户管理、运维管理和安全管理等功能&#xff0c;还支持多种主流标准协议&#xff0c;如GB28181、GB35114、RTSP/Onvif…

如何在 PyQt 中启动“绘图循环”?

在 PyQt 中实现一个“绘图循环”可以使用 定时器&#xff08;QTimer&#xff09;&#xff0c;让应用程序在指定的时间间隔内反复触发一个绘图函数。这种方法对于需要持续更新绘图&#xff08;例如动画效果&#xff09;的情况特别有用。 1、问题背景 在GUI编程中&#xff0c;我…

Linux 线程控制

一. 线程互斥 1.1 线程互斥相关概念 临界资源&#xff1a;多线程执行流共享的资源就叫做临界资源。临界区&#xff1a;每个线程内部&#xff0c;访问临界资源的代码&#xff0c;就叫做临界区。互斥&#xff1a;任何时刻&#xff0c;互斥保证有且只有一个执行流进入临界区&…

分布式光伏发电的投融资计算

分布式光伏发电项目的成功实施离不开科学、合理的投融资计算&#xff0c;为光伏项目的前期开发提供切实可行的依据。 一、分布式光伏发电项目的投资成本 分布式光伏发电项目的投资成本包括多个方面&#xff0c;主要包括光伏组件采购成本、逆变器和支架系统成本、安装和施工成本…

MyBatis 返回 Map 或 List<Map>时,时间类型数据,默认为LocalDateTime,响应给前端默认含有‘T‘字符

一、问题 MyBatis 返回 Map 或 List时&#xff0c;时间类型数据&#xff0c;默认为LocalDateTime Springboot 响应给前端的LocalDateTime&#xff0c;默认含有’T’字符&#xff0c;如何统一配置去掉 二、解决方案 1、pom.xml 增加依赖&#xff08;2024.11.6 补充&#xff…

AMD显卡低负载看视频掉驱动(chrome edge浏览器) 高负载玩游戏却稳定 解决方法——关闭MPO

问题 折磨的开始是天下苦黄狗久矣&#xff0c;为了不再被讨乞丐的显存恶心&#xff0c;一怒之下购入了AMD显卡&#xff08;20GB显存确实爽 头一天就跑了3dmark验机&#xff0c;完美通过&#xff0c;玩游戏也没毛病 但是呢这厮是一点不省心&#xff0c;玩游戏没问题&#xff0c…

记录新建wordpress站的实践踩坑:wordpress 上传源码新建站因权限问题导致无法访问、配置新站建站向导以及插件主题上传配置的解决办法

官方文档&#xff1a;How to install WordPress – Advanced Administration Handbook | Developer.WordPress.org 但是没写权限问题&#xff0c;可以下载到 wordpress官方包。 把下载的wordpresscn的包解压并上传到服务器目录下&#xff0c;但是因为是root上传导致了权限问题…

【大数据学习 | kafka】kafka的偏移量管理

1. 偏移量的概念 消费者在消费数据的时候需要将消费的记录存储到一个位置&#xff0c;防止因为消费者程序宕机而引起断点消费数据丢失问题&#xff0c;下一次可以按照相应的位置从kafka中找寻数据&#xff0c;这个消费位置记录称之为偏移量offset。 kafka0.9以前版本将偏移量信…

迪杰斯特拉算法

迪杰斯特拉算法 LeetCode 743. 网络延迟时间 https://blog.csdn.net/xiaoxi_hahaha/article/details/110257368 import sysdef dijkstra(graph, source):"""dijkstra算法:param graph: 邻接矩阵:param source: 出发点&#xff0c;源点:return:""&…

[Unity Demo]从零开始制作空洞骑士Hollow Knight第十八集补充:制作空洞骑士独有的EventSystem和InputModule

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 文章目录 前言一、制作空洞骑士独有的EventSystem和InputModule总结 前言 hello大家好久没见&#xff0c;之所以隔了这么久才更新并不是因为我又放弃了这个项目&#xff0c;而…

K8S群集调度二

一、污点(Taint) 和 容忍(Tolerations) 1.1、污点(Taint) 设置在node上是对pod的一种作用 节点的亲和性&#xff0c;是Pod的一种属性&#xff08;偏好或硬性要求&#xff09;&#xff0c;它使Pod被吸引到一类特定的节点 而Taint 则相反&#xff0c;它使节点能够排斥一类特…

MySQL45讲 第十六讲 “order by”是怎么工作的?

文章目录 MySQL45讲 第十六讲 “order by”是怎么工作的&#xff1f;一、引言二、全字段排序&#xff08;一&#xff09;索引创建与执行情况分析&#xff08;二&#xff09;执行流程&#xff08;三&#xff09;查看是否使用临时文件 三、rowid 排序&#xff08;一&#xff09;参…

HTML 基础标签——结构化标签<html>、<head>、<body>

文章目录 1. <html> 标签2. <head> 标签3. <body> 标签4. <div> 标签5. <span> 标签小结 在 HTML 文档中&#xff0c;使用特定的结构标签可以有效地组织和管理网页内容。这些标签不仅有助于浏览器正确解析和渲染页面&#xff0c;还能提高网页的可…

【原创】java+ssm+mysql电费管理系统设计与实现

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

浅谈QT中Tab键的切换逻辑

浅谈QT中Tab键的切换逻辑 无意中发现在输入界面中按下Tab键时&#xff0c;没有按照预想的顺序切换焦点事件&#xff0c;如下图所示 这个现象还是很有趣&#xff0c;仔细观察了下&#xff0c;默认的切换顺序是按照控件拖入顺序&#xff0c;那么知道了这个问题想要解决起来就很简…

Linux系统编程学习 NO.10——进程的概念(1)

前言 本篇文章主要了解进程的概念。 #j 冯诺依曼体系结构 什么是冯诺依曼体系结构&#xff1f; 冯诺伊曼体系结构是计算机体系结构的一种经典范式&#xff0c;由计算机科学家约翰冯诺伊曼&#xff08;John von Neumann&#xff09;提出。该体系结构在计算机设计中起到了重要…

如何查看局域网内的浏览记录?总结五种方法,按步操作!一学就会!「管理小白须知」

如何查看局域网内的浏览记录&#xff1f; 你是否也曾为如何有效监控局域网内的浏览记录而苦恼&#xff1f; 监控局域网内电脑的浏览记录是确保员工工作效率、维护网络安全以及规范上网行为的重要手段。 别担心&#xff0c;今天我们就来聊聊这个话题&#xff0c;为你揭秘五种简…