【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个参数第一个参数
void *base
是代表要传入的目标数组名即所需要排序的数组名。size_t num
是代表要传入的数组内有多少个元素。size_t width
是代表要传入的数组内元素的大小。int (*compare )(const void *elem1, const void *elem2 )
是一个函数指针,函数指针的两个参数 类型都为void*
也就是说在第四个参数中我们要传入一个函数且这个函数具有比较elem1
和elem2
这两个元素大小的功能。
我们写一个排序整型数组的代码来给大家举例,让大家能更好的了解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)
前言:上篇文章的末尾我们使用了转移表来解决代码冗余的问题,那我们还有没有什么办法解决代码冗余呢?有的这就是接下来要说的回调函数。 往期文章: 指针1 指针2 指针3 指针4 文章目录 一,回调函数二,qsort实现快速排序1…...

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

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

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

Scrum方法论指导下的Deepseek R1医疗AI部署开发
一、引言 1.1 研究背景与意义 在当今数智化时代,软件开发方法论对于项目的成功实施起着举足轻重的作用。Scrum 作为一种广泛应用的敏捷开发方法论,以其迭代式开发、快速反馈和高效协作的特点,在软件开发领域占据了重要地位。自 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 项目背景 在信息高速流通的当下,新闻媒体行业每天都要处理和传播海量信息。传统的新闻管理模式依赖人工操作,在新闻采集、编辑、发布以及后续管理等环节中,不仅效率低下,而且容易出现人为失误。同时࿰…...
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、反射机制定义及作用 反射是允许程序在运行时检查和操作类、方法、属性等的机制,能够动态地获取信息、调用方法等。换句话说,在编写程序时,不需要知道要操作的类的具体信息,而是在程序运行时获取和使用。 2、反射机制…...
java实现二维码图片生成和编解码
java实现二维码图片生成和编解码 在wutool中,封装了二维码工具类,基于google的zxing库,实现二维码图片生成、编码和解码。 关于wutool wutool是一个java代码片段收集库,针对特定场景提供轻量解决方案,只要按需选择代…...

Java基础常见的面试题(易错!!)
面试题一:为什么 Java 不支持多继承 Java 不支持多继承主要是为避免 “菱形继承问题”(又称 “钻石问题”),即一个子类从多个父类继承到同名方法或属性时,编译器无法确定该调用哪个父类的成员。同时,多继承…...

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

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

微信小程序实现拉卡拉支付
功能需求:拉卡拉支付(通过跳转拉卡拉平台进行支付),他人支付(通过链接进行平台跳转支付) 1.支付操作 //支付 const onCanStartPay async (obj) > {uni.showLoading({mask: true})// 支付接口获取需要传…...
git从本地其他设备上fetch分支
在 Git 中,如果你想从本地其他设备上获取分支,可以通过以下几种方式实现。不过,需要注意的是,Git 本身是分布式版本控制系统,通常我们是从远程仓库(如 GitHub、GitLab 等)拉取分支,而…...

【干货教程】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框架 的 “捷邻小程序” 系统的设计与实现
大家好,今天要和大家聊的是一款基于 SSM框架 的 “捷邻小程序” 系统的设计与实现。项目源码以及部署相关事宜请联系我,文末附上联系方式。 项目简介 基于 SSM框架 的 “捷邻小程序” 系统设计与实现的主要使用者分为 管理员 和 用户,没有授…...

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

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

【含开题报告+文档+PPT+源码】基于springboot加vue 前后端分离的校园新闻审核发布管理系统
开题报告 本研究旨在设计并实现一套基于SpringBoot后端框架结合Vue前端技术的校园新闻发布系统,该系统面向学生用户群体提供了全面的功能服务。学生用户通过身份验证登录后,能够便捷高效地获取校园内的各类新闻资讯,实时了解校内动态。系统不…...

从WWDC看苹果产品发展的规律
WWDC 是苹果公司一年一度面向全球开发者的盛会,其主题演讲展现了苹果在产品设计、技术路线、用户体验和生态系统构建上的核心理念与演进脉络。我们借助 ChatGPT Deep Research 工具,对过去十年 WWDC 主题演讲内容进行了系统化分析,形成了这份…...
STM32+rt-thread判断是否联网
一、根据NETDEV_FLAG_INTERNET_UP位判断 static bool is_conncected(void) {struct netdev *dev RT_NULL;dev netdev_get_first_by_flags(NETDEV_FLAG_INTERNET_UP);if (dev RT_NULL){printf("wait netdev internet up...");return false;}else{printf("loc…...
spring:实例工厂方法获取bean
spring处理使用静态工厂方法获取bean实例,也可以通过实例工厂方法获取bean实例。 实例工厂方法步骤如下: 定义实例工厂类(Java代码),定义实例工厂(xml),定义调用实例工厂ÿ…...

MySQL 8.0 OCP 英文题库解析(十三)
Oracle 为庆祝 MySQL 30 周年,截止到 2025.07.31 之前。所有人均可以免费考取原价245美元的MySQL OCP 认证。 从今天开始,将英文题库免费公布出来,并进行解析,帮助大家在一个月之内轻松通过OCP认证。 本期公布试题111~120 试题1…...
CMake控制VS2022项目文件分组
我们可以通过 CMake 控制源文件的组织结构,使它们在 VS 解决方案资源管理器中以“组”(Filter)的形式进行分类展示。 🎯 目标 通过 CMake 脚本将 .cpp、.h 等源文件分组显示在 Visual Studio 2022 的解决方案资源管理器中。 ✅ 支持的方法汇总(共4种) 方法描述是否推荐…...
《C++ 模板》
目录 函数模板 类模板 非类型模板参数 模板特化 函数模板特化 类模板的特化 模板,就像一个模具,里面可以将不同类型的材料做成一个形状,其分为函数模板和类模板。 函数模板 函数模板可以简化函数重载的代码。格式:templa…...
Go 语言并发编程基础:无缓冲与有缓冲通道
在上一章节中,我们了解了 Channel 的基本用法。本章将重点分析 Go 中通道的两种类型 —— 无缓冲通道与有缓冲通道,它们在并发编程中各具特点和应用场景。 一、通道的基本分类 类型定义形式特点无缓冲通道make(chan T)发送和接收都必须准备好࿰…...
A2A JS SDK 完整教程:快速入门指南
目录 什么是 A2A JS SDK?A2A JS 安装与设置A2A JS 核心概念创建你的第一个 A2A JS 代理A2A JS 服务端开发A2A JS 客户端使用A2A JS 高级特性A2A JS 最佳实践A2A JS 故障排除 什么是 A2A JS SDK? A2A JS SDK 是一个专为 JavaScript/TypeScript 开发者设计的强大库ÿ…...

【从零学习JVM|第三篇】类的生命周期(高频面试题)
前言: 在Java编程中,类的生命周期是指类从被加载到内存中开始,到被卸载出内存为止的整个过程。了解类的生命周期对于理解Java程序的运行机制以及性能优化非常重要。本文会深入探寻类的生命周期,让读者对此有深刻印象。 目录 …...
纯 Java 项目(非 SpringBoot)集成 Mybatis-Plus 和 Mybatis-Plus-Join
纯 Java 项目(非 SpringBoot)集成 Mybatis-Plus 和 Mybatis-Plus-Join 1、依赖1.1、依赖版本1.2、pom.xml 2、代码2.1、SqlSession 构造器2.2、MybatisPlus代码生成器2.3、获取 config.yml 配置2.3.1、config.yml2.3.2、项目配置类 2.4、ftl 模板2.4.1、…...