当前位置: 首页 > news >正文

【C语言】指针(5)

前言:上篇文章的末尾我们使用了转移表来解决代码冗余的问题,那我们还有没有什么办法解决代码冗余呢?有的这就是接下来要说的回调函数。
往期文章:
指针1
指针2
指针3
指针4

文章目录

  • 一,回调函数
  • 二,qsort实现快速排序
    • 1,void*指针
  • 三,qsort的模拟实现

一,回调函数

先来回顾一下上篇文章末尾的内容,写一个模拟计算的代码:

#include <stdio.h> 
int add(int a, int b) 
{ return a + b; 
}
int sub(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("*******   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 0: printf("退出程序\n"); break; default: printf("选择错误\n"); break; } } while (input); return 0; 
}

我们可以看到代码是非常冗余的,想要解决冗余的问题方法一在我们上一篇文章指针4(转移表)。方法二我们使用回调函数来实现。
那什么是回调函数呢?

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

我们给出具体的代码:

#include<stdio.h>
int add(int x, int y)
{return x + y;
}
int sub(int x, int y)
{return x - y;
}
void clac(int (*pf)(int, int))
{int x = 0, y = 0;printf("请输入两个数:");scanf("%d %d", &x, &y);int ret = (*pf)(x, y);printf("计算结果为:%d\n", ret);
}
int main()
{int x, y;int input = 1;int ret = 0;do {printf("*************************\n");printf("*******1:add 2:sub ******\n");printf("*******   0:exit   ******\n");printf("*************************\n");printf("请选择:");scanf("%d", &input);switch (input){case 1:clac(add);break;case 2:clac(sub);break;case 0:printf("退出程序\n");}} while (input);
}

通过上面的两段代码对比我们有两个改进之处:
一,将case语句中那些重复性的语句封装到了一个新建的函数中。
二,函数调用发生了变化,从原来的直接调用add函数sub函数变成了先调用clac函数再去调用add函数sub函数。这种函数称之为回调函数。

看明白这两点相信已经不难理解回调函数了,但是还有一点需要注意:回调函数不是由该函数的实现方直接调用,而是在特定的事件或条 件发生时由另外的一方调用的,用于对该事件或条件进行响应。
下面画一张图让你更好的理解:
在这里插入图片描述


二,qsort实现快速排序

在前面的文章中我们介绍了冒泡排序,这次我们来介绍一些qsort。q即quick快速的,sort是排序的意思。所以qsort就是快速排序俗称快排。
那怎么使用qsort来实现快速排序呢?首先我们先得了解一些qsort:

在这里插入图片描述
![在这里插入图片描述

void qsort( 
void *base, 
size_t num, 
size_t width, 
int (__cdecl *compare )(const void *elem1, const void *elem2 ) );

通过专业文献对qsort的描述我们知道qsort共有4个参数第一个参数

  1. void *base 是代表要传入的目标数组名即所需要排序的数组名。
  2. size_t num 是代表要传入的数组内有多少个元素。
  3. size_t width 是代表要传入的数组内元素的大小。
  4. int (*compare )(const void *elem1, const void *elem2 ) 是一个函数指针,函数指针的两个参数 类型都为void*也就是说在第四个参数中我们要传入一个函数且这个函数具有比较elem1elem2这两个元素大小的功能。

我们写一个排序整型数组的代码来给大家举例,让大家能更好的了解qsort的使用方法:

#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>
#include<stdlib.h>
/*
void qsort(void* base, //第一个参数为需要比较的数组的首地址size_t num,  //第二个参数为该数组的元素个数size_t width, //第三个参数为该数组每个元素的大小int(__cdecl* compare)(const void* elem1, const void* elem2));//第四个参数为函数指针,及传入一个能比较数组内部元素大小的函数的地址
*///实现比较两个函数大小的函数
int comp_int(const void* elem1, const void* elem2)
{return *(int*)elem1 - *(int*)elem2;//void*的指针不能直接使用 要强转后再解引用才能使用 //elem1>elem2  返回大于零的数//elem==elem2 返回0//elem1<elem2  返回小于零的数
}
//打印整型数组
void print(int* P_arr, int sz)//一维数组传参传过去的是数组的首地址
{int i = 0;for (i = 0;i < sz;i++){printf("%d ", *(P_arr + i));}
}
//排整型的数组
int test1()
{int arr1[10] = { 1,4,3,2,6,5,8,7,9,10 };int sz = sizeof(arr1) / sizeof(arr1[0]);qsort(arr1, sz, sizeof(arr1[0]), comp_int);//print(arr1, sz);
}
int main()
{//写一个test1来测试qsort排序整型数组test1();return 0;
}

我们来分析代码的含义:

在这里插入图片描述

1,void*指针

上面诸多地方提到了void*指针,下面我们给出解释:

在指针类型中有⼀种特殊的类型是 void* 类型的,可以理解为无具体类型的指针(或者叫泛型指 针),这种类型的指针可以用来接受任意类型地址。但是也有局限性, void*类型的指针不能直接进行指针的±整数和解引用的运算。

#include <stdio.h> 
int main() { int a = 10; int* pa = &a; char* pc = &a; return 0;
}

在上⾯的代码中,将⼀个int类型的变量的地址赋值给⼀个char*类型的指针变量。编译器会警告,是因为类型不兼容。而使用void*类型就不会有这样的问题。
我们再看看void*的指针接收别的类型的指针:

#include <stdio.h> 
int main() 
{ int a = 10; void* pa = &a; void* pc = &a; *pa = 10; *pc = 0; return 0; 
}

在这里插入图片描述

这里我们可以看到, void* 类型的指针可以接收不同类型的地址,但是无法直接进行指针运算。 那么 void* 类型的指针到底有什么用呢? ⼀般 void* 类型的指针是使⽤在函数参数的部分,用来接收不同类型数据的地址,这样的设计可以实现泛型编程的效果。

理解了void*类型,以及qsort如何使用了以后,我们就可以试着去排序一些其他类型的数据了。代码内容在下载文件处取噢。

熟练了使用qosrt来排序各种类型的数据后接下来我们就来模仿着造一个qosrt函数。

三,qsort的模拟实现

我们之前学过了冒泡排序并且知道冒泡排序有一个缺点就是只能排序固定类型的数据,而qsortt能排序任意类型的数据那我们能不能使用冒泡排序的方式来模拟实现qsort快速排序呢?答案是肯定的。
我们先写一个冒泡排序,然后再看看哪些地方需要修改:

#include<stdio.h>
void  bubble_sort(int arr[],int sz)
{int i=0;for(i=0;i<sz-1;i++){int j=0;for(j=0;j<sz-i-1;j++){//默认拍成升序if(arr[j]>arr[j+1]){int tmp=arr[j];arr[j]=arr[j+1];arr[j+1]=tmp;}}}
}
void print_arr(int arr[],int sz)
{int i=0;for(i=0;i<sz;i++){printf("%d ",arr[i]);}printf("\n");
}
void test()
{int arr[10]={1,3,5,7,9,2,4,6,8,0};int sz=sizeof(arr)/sizeof(arr[0]);bubble_sort(arr,sz);print_arr(arr,sz);
}
int main()
{test();return 0;
}

那如何知道那些地方需要修改呢?通过与qsort的对比我们可以得出以下几个地方需要改造:在这里插入图片描述
那我们就先来改造参数部分:void bubble_sort(void*base,int sz,int width,int (*cmp)(const void*e1,const void*e2)) 改造之后我们发现多了两个参数,一个是 width 代表单个元素大小;一个是 int (*cmp)(const void*e1,const void*e2)
这个函数指针。

其中一个修改的地方是从原来接受int类型的数组改为了void*类型原因是方便接受任意类型的数组。

其次我们来修改比较部分,上面分析了使用一个函数指针指向一个函数然后通过函数的返回值来得到e1和e2这两个元素的大小关系,这也是为什么函数指针的返回类型是int的原因。

int comp(const void* e1, const void* e2)
{return *(int*)e1 - *(int*)e2;
}

比较函数我们依然可以这样写但是要注意一个问题,现在是我们模仿qsort的逻辑,所以在bubble_sort这个函数里边就会涉及一个传参的问题,下面我们着重来探究传参问题:在这里插入图片描述

if(arr[j]>arr[j+1])——————>if(comp((char*)base+j*width,(chr*)base+(j+1)*width)>0)
//注意base就是arr

解决了参数问题,解决了传参问题接下来就是交换数据的问题了,如果不满足我们的升序要求则需要交换那怎么交换呢?通过上面的比较得出需要交换那我们既然已经得到了要交换的两个元素的地址,就不妨再封装一个函数来专门去交换元素的内容。
在这里插入图片描述

#include<stdio.h>
#include<stdlib.h>
void swap(char* buf1, char* buf2, int width)
{int i = 0;for (i = 0;i < width;i++){char tmp = *buf1;*buf1 = *buf2;*buf2 = tmp;buf1++;buf2++;}
}

解决完交换的问题我们就可以给出所有的代码啦:

int bubble_cmp(const void* e1, const void* e2)
{return *(int*)e1 - *(int*)e2;
}
void swap(char* buf1, char* buf2, int width)
{int i = 0;for (i = 0;i < width;i++){char tmp = *buf1;*buf1 = *buf2;*buf2 = tmp;buf1++;buf2++;}
}
int bubble_sort(void* base, int sz, int width, int (*cmp)(const void* e1, const void* e2))
{int i = 0;for (i = 0;i < sz-1;i++){int j = 0;for (j = 0;j < sz - 1 - i;j++){if (bubble_cmp((char*)base+j*width,(char*)base+(j+1)*width)>0){swap((char*)base + j * width, (char*)base + (j + 1) * width,width);}}}
}
void test2()
{int arr[10] = { 1,3,5,7,9,2,4,6,8,0 };int sz = sizeof(arr) / sizeof(arr[0]);bubble_sort(arr, sz, sizeof(arr[0]), bubble_cmp);print(arr, sz);
}
int main()
{test2();//使用冒泡排序来模拟qsortreturn 0;
}

好了以上就是本章的全部内容啦!
感谢能够看到这里的读者,如果我的文章能够帮到你那我甚是荣幸,文章有任何问题都欢迎指出!制作不易还望给一个免费的三连,你们的支持就是我最大的动力!
在这里插入图片描述

相关文章:

【C语言】指针(5)

前言&#xff1a;上篇文章的末尾我们使用了转移表来解决代码冗余的问题&#xff0c;那我们还有没有什么办法解决代码冗余呢&#xff1f;有的这就是接下来要说的回调函数。 往期文章: 指针1 指针2 指针3 指针4 文章目录 一&#xff0c;回调函数二&#xff0c;qsort实现快速排序1…...

大数据组件(四)快速入门实时数据湖存储系统Apache Paimon(2)

Paimon的下载及安装&#xff0c;并且了解了主键表的引擎以及changelog-producer的含义参考&#xff1a; 大数据组件(四)快速入门实时数据湖存储系统Apache Paimon(1) 利用Paimon表做lookup join&#xff0c;集成mysql cdc等参考&#xff1a; 大数据组件(四)快速入门实时数据…...

PLC通讯

PPI通讯 是西门子公司专为s7-200系列plc开发的通讯协议。内置于s7-200 CPU中。PPI协议物理上基于RS-485口&#xff0c;通过屏蔽双绞线就可以实现PPI通讯。PPI协议是一种主-从协议。主站设备发送要求到从站设备&#xff0c;从站设备响应&#xff0c;从站不能主动发出信息。主站…...

前端js进阶,ES6语法,包详细

进阶ES6 作用域的概念加深对js理解 let、const申明的变量&#xff0c;在花括号中会生成块作用域&#xff0c;而var就不会生成块作用域 作用域链本质上就是底层的变量查找机制 作用域链查找的规则是:优先查找当前作用域先把的变量&#xff0c;再依次逐级找父级作用域直到全局…...

Scrum方法论指导下的Deepseek R1医疗AI部署开发

一、引言 1.1 研究背景与意义 在当今数智化时代&#xff0c;软件开发方法论对于项目的成功实施起着举足轻重的作用。Scrum 作为一种广泛应用的敏捷开发方法论&#xff0c;以其迭代式开发、快速反馈和高效协作的特点&#xff0c;在软件开发领域占据了重要地位。自 20 世纪 90 …...

LINUX安装使用Redis

参考 Install Redis on Linux | Docs 安装命令 sudo apt-get install -y lsb-release curl gpgcurl -fsSL https://packages.redis.io/gpg | sudo gpg --dearmor -o /usr/share/keyrings/redis-archive-keyring.gpgsudo chmod 644 /usr/share/keyrings/redis-archive-keyrin…...

基于java新闻管理系统,推荐一款开源cms内容管理系统ruoyi-fast-cms

一、项目概述 1.1 项目背景 在信息高速流通的当下&#xff0c;新闻媒体行业每天都要处理和传播海量信息。传统的新闻管理模式依赖人工操作&#xff0c;在新闻采集、编辑、发布以及后续管理等环节中&#xff0c;不仅效率低下&#xff0c;而且容易出现人为失误。同时&#xff0…...

054 redisson

文章目录 使用Redisson演示可重入锁读写锁信号量闭锁获取三级分类redisson分布式锁 package com.xd.cubemall.product.config;import org.redisson.Redisson; import org.redisson.api.RedissonClient; import org.redisson.config.Config; import org.springframework.context…...

【数据结构】(12) 反射、枚举、lambda 表达式

一、反射 1、反射机制定义及作用 反射是允许程序在运行时检查和操作类、方法、属性等的机制&#xff0c;能够动态地获取信息、调用方法等。换句话说&#xff0c;在编写程序时&#xff0c;不需要知道要操作的类的具体信息&#xff0c;而是在程序运行时获取和使用。 2、反射机制…...

java实现二维码图片生成和编解码

java实现二维码图片生成和编解码 在wutool中&#xff0c;封装了二维码工具类&#xff0c;基于google的zxing库&#xff0c;实现二维码图片生成、编码和解码。 关于wutool wutool是一个java代码片段收集库&#xff0c;针对特定场景提供轻量解决方案&#xff0c;只要按需选择代…...

Java基础常见的面试题(易错!!)

面试题一&#xff1a;为什么 Java 不支持多继承 Java 不支持多继承主要是为避免 “菱形继承问题”&#xff08;又称 “钻石问题”&#xff09;&#xff0c;即一个子类从多个父类继承到同名方法或属性时&#xff0c;编译器无法确定该调用哪个父类的成员。同时&#xff0c;多继承…...

hugging face---transformers包

一、前言 不同于计算机视觉的百花齐放&#xff0c;不同网络适用不同情况&#xff0c;NLP则由Transformer一统天下。transformer是2017年提出的一种基于自注意力机制的神经网络架构&#xff0c;transformers库是hugging face社区创造的一个py库&#xff0c;通过该库可以实现统一…...

网络安全防护指南:筑牢网络安全防线(510)

一、网络安全的基本概念 &#xff08;一&#xff09;网络的定义 网络是指由计算机或者其他信息终端及相关设备组成的按照一定的规则和程序对信息收集、存储、传输、交换、处理的系统。在当今数字化时代&#xff0c;网络已经成为人们生活和工作中不可或缺的一部分。它连接了世…...

微信小程序实现拉卡拉支付

功能需求&#xff1a;拉卡拉支付&#xff08;通过跳转拉卡拉平台进行支付&#xff09;&#xff0c;他人支付&#xff08;通过链接进行平台跳转支付&#xff09; 1.支付操作 //支付 const onCanStartPay async (obj) > {uni.showLoading({mask: true})// 支付接口获取需要传…...

git从本地其他设备上fetch分支

在 Git 中&#xff0c;如果你想从本地其他设备上获取分支&#xff0c;可以通过以下几种方式实现。不过&#xff0c;需要注意的是&#xff0c;Git 本身是分布式版本控制系统&#xff0c;通常我们是从远程仓库&#xff08;如 GitHub、GitLab 等&#xff09;拉取分支&#xff0c;而…...

【干货教程】Windows电脑本地部署运行DeepSeek R1大模型(基于Ollama和Chatbox)

文章目录 一、环境准备二、安装Ollama2.1 访问Ollama官方网站2.2 下载适用于Windows的安装包2.3 安装Ollama安装包2.4 指定Ollama安装目录2.5 指定Ollama的大模型的存储目录 三、选择DeepSeek R1模型四、下载并运行DeepSeek R1模型五、常见问题解答六、使用Chatbox进行交互6.1 …...

基于 SSM框架 的 “捷邻小程序” 系统的设计与实现

大家好&#xff0c;今天要和大家聊的是一款基于 SSM框架 的 “捷邻小程序” 系统的设计与实现。项目源码以及部署相关事宜请联系我&#xff0c;文末附上联系方式。 项目简介 基于 SSM框架 的 “捷邻小程序” 系统设计与实现的主要使用者分为 管理员 和 用户&#xff0c;没有授…...

基于Springboot医院预约挂号小程序系统【附源码】

基于Springboot医院预约挂号小程序系统 效果如下&#xff1a; 小程序主页面 帖子页面 医生账号页面 留言内容页面 管理员主页面 用户管理页面 我的挂号页面 医生管理页面 研究背景 随着信息技术的飞速发展和互联网医疗的兴起&#xff0c;传统的医疗服务模式正面临着深刻的变…...

基于AVue的二次封装:快速构建后台管理系统的CRUD方案

基于AVue的二次封装&#xff1a;快速构建后台管理系统的CRUD方案 在开发后台管理系统时&#xff0c;表格是常见的组件之一。然而&#xff0c;使用原生的Element Plus实现CRUD&#xff08;增删改查&#xff09;功能往往需要编写大量重复代码&#xff0c;过程繁琐。即使借助类似…...

【含开题报告+文档+PPT+源码】基于springboot加vue 前后端分离的校园新闻审核发布管理系统

开题报告 本研究旨在设计并实现一套基于SpringBoot后端框架结合Vue前端技术的校园新闻发布系统&#xff0c;该系统面向学生用户群体提供了全面的功能服务。学生用户通过身份验证登录后&#xff0c;能够便捷高效地获取校园内的各类新闻资讯&#xff0c;实时了解校内动态。系统不…...

在软件开发中正确使用MySQL日期时间类型的深度解析

在日常软件开发场景中&#xff0c;时间信息的存储是底层且核心的需求。从金融交易的精确记账时间、用户操作的行为日志&#xff0c;到供应链系统的物流节点时间戳&#xff0c;时间数据的准确性直接决定业务逻辑的可靠性。MySQL作为主流关系型数据库&#xff0c;其日期时间类型的…...

MongoDB学习和应用(高效的非关系型数据库)

一丶 MongoDB简介 对于社交类软件的功能&#xff0c;我们需要对它的功能特点进行分析&#xff1a; 数据量会随着用户数增大而增大读多写少价值较低非好友看不到其动态信息地理位置的查询… 针对以上特点进行分析各大存储工具&#xff1a; mysql&#xff1a;关系型数据库&am…...

Cinnamon修改面板小工具图标

Cinnamon开始菜单-CSDN博客 设置模块都是做好的&#xff0c;比GNOME简单得多&#xff01; 在 applet.js 里增加 const Settings imports.ui.settings;this.settings new Settings.AppletSettings(this, HTYMenusonichy, instance_id); this.settings.bind(menu-icon, menu…...

让AI看见世界:MCP协议与服务器的工作原理

让AI看见世界&#xff1a;MCP协议与服务器的工作原理 MCP&#xff08;Model Context Protocol&#xff09;是一种创新的通信协议&#xff0c;旨在让大型语言模型能够安全、高效地与外部资源进行交互。在AI技术快速发展的今天&#xff0c;MCP正成为连接AI与现实世界的重要桥梁。…...

深入解析C++中的extern关键字:跨文件共享变量与函数的终极指南

&#x1f680; C extern 关键字深度解析&#xff1a;跨文件编程的终极指南 &#x1f4c5; 更新时间&#xff1a;2025年6月5日 &#x1f3f7;️ 标签&#xff1a;C | extern关键字 | 多文件编程 | 链接与声明 | 现代C 文章目录 前言&#x1f525;一、extern 是什么&#xff1f;&…...

UR 协作机器人「三剑客」:精密轻量担当(UR7e)、全能协作主力(UR12e)、重型任务专家(UR15)

UR协作机器人正以其卓越性能在现代制造业自动化中扮演重要角色。UR7e、UR12e和UR15通过创新技术和精准设计满足了不同行业的多样化需求。其中&#xff0c;UR15以其速度、精度及人工智能准备能力成为自动化领域的重要突破。UR7e和UR12e则在负载规格和市场定位上不断优化&#xf…...

【JavaWeb】Docker项目部署

引言 之前学习了Linux操作系统的常见命令&#xff0c;在Linux上安装软件&#xff0c;以及如何在Linux上部署一个单体项目&#xff0c;大多数同学都会有相同的感受&#xff0c;那就是麻烦。 核心体现在三点&#xff1a; 命令太多了&#xff0c;记不住 软件安装包名字复杂&…...

SAP学习笔记 - 开发26 - 前端Fiori开发 OData V2 和 V4 的差异 (Deepseek整理)

上一章用到了V2 的概念&#xff0c;其实 Fiori当中还有 V4&#xff0c;咱们这一章来总结一下 V2 和 V4。 SAP学习笔记 - 开发25 - 前端Fiori开发 Remote OData Service(使用远端Odata服务)&#xff0c;代理中间件&#xff08;ui5-middleware-simpleproxy&#xff09;-CSDN博客…...

Redis的发布订阅模式与专业的 MQ(如 Kafka, RabbitMQ)相比,优缺点是什么?适用于哪些场景?

Redis 的发布订阅&#xff08;Pub/Sub&#xff09;模式与专业的 MQ&#xff08;Message Queue&#xff09;如 Kafka、RabbitMQ 进行比较&#xff0c;核心的权衡点在于&#xff1a;简单与速度 vs. 可靠与功能。 下面我们详细展开对比。 Redis Pub/Sub 的核心特点 它是一个发后…...

JAVA后端开发——多租户

数据隔离是多租户系统中的核心概念&#xff0c;确保一个租户&#xff08;在这个系统中可能是一个公司或一个独立的客户&#xff09;的数据对其他租户是不可见的。在 RuoYi 框架&#xff08;您当前项目所使用的基础框架&#xff09;中&#xff0c;这通常是通过在数据表中增加一个…...