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

【c语言——指针详解(4)】


文章目录

  • 一、回调函数是什么?
  • 二、qsort的使⽤
    • 1、使⽤qsort函数排序整型数据
    • 2、使⽤qsort排序结构数据
  • 三、qsort函数的模拟实现


作者主页

一、回调函数是什么?

回调函数就是⼀个通过函数指针调⽤的函数。
如果你把函数的指针(地址)作为参数传递给另⼀个函数,当这个指针被⽤来调⽤其所指向的函数
时,被调⽤的函数就是回调函数。在上一篇文章中,我们实现计算器的第三种代码其实就是运用了回调函数。

//回调函数
int Add(int x, int y)
{return x + y;
}int Sub(int x, int y)
{return x - y;
}int Mul(int x, int y)
{return x * y;
}int Div(int x, int y)
{return x / y;
}void menu()
{printf("************************************\n");printf("******* 1.Add      2.Sub     *******\n");printf("******* 3.Mul      4.Div     *******\n");printf("*******       0.exit         *******\n");printf("************************************\n");
}void Calc(int (*pf)(int, int))
{int x = 0;int y = 0;int ret = 0;printf("请输入2个数\n");scanf("%d %d", &x, &y);ret = pf(x, y);printf("%d\n", ret);
}int main()
{int input = 0;do{menu();printf("请选择:");scanf("%d", &input);switch (input){case 1:Calc(Add);//Add是地址,传递给函数指针Calc,Calc再去调用函数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;
}

相似的代码抽象成函数:有了函数指针后,函数的调用,可以使用函数名来调用,也可以使用函数指针来调用。

二、qsort的使⽤

#include<stdlib.h>//qsort函数的头文件
#include<string.h>//strcmp的头文件

qsort是用来排序的库函数,直接用来排序数据
底层使用的是快速排序的方式,qsort函数可以排序任意类型的数据
排序方式有很多种,例如:
选择排序
插入排序
冒泡排序
快速排序
希尔排序

1、使⽤qsort函数排序整型数据

使用qsort函数前,我们先写一个冒泡排序的函数,对一组整型数据进行排序,排序为升序。

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 - 1 - i; 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");
}int main()
{int arr[] = { 9,8,7,6,5,4,3,2,1,0 };//排为升序int sz = sizeof(arr) / sizeof(arr[0]);bubble_sort(arr, sz);print_arr(arr, sz);return 0;
}

像这个函数只能排序整型数据,如果要排序其他类型数据的时候就无能为力了,这时就要用到qsort函数了。
接下来我们看一下qsort函数的原型:


void qsort(void* base, //指针,指向的是待排序的数组的第一个元素size_t num, //是base指向的待排序数组的元素个数size_t size,//base指向的待排序数组的元素的大小int(*compar)(const void*)//函数指针-指向的就是两个元素的比较函数)void*类型的指针是无具体类型的指针,它的作用就是接收任何类型的地址。
这种类型的指针不能直接解引用,也不能+-整数的运算qsort 函数的使用者-明确的知道要排序的是什么数据,这些数据应该如何比较,所以提供两个元素的比较函数

用qsort函数来实现一下冒泡排序:

#include<stdlib.h>//qsort函数的头文件void print_arr(int arr[], int sz)//打印整数
{int i = 0;for (i = 0; i < sz ; i++){printf("%d ", arr[i]);}printf("\n");
}int cmp_int(const void* p1, const void* p2)//比较函数
{/* if (*(int*)p1 > *(int*)p2)//将void*类型的指针p1强制转换为int*类型再解引用,p1和p2就是两个要比较的元素,如9和8{return 1;}else if (*(int*)p1 == *(int*)p2)return 0;elsereturn -1; *///简化:return *(int*)p1 - *(int*)p2;//想要降序就交换这两个数据
}void test1()
{int arr[] = { 1, 3, 5, 7, 9, 2, 4, 6, 8, 0 };int sz = sizeof(arr) / sizeof(arr[0]);qsort(arr, sz, sizeof(arr[0]), cmp_int);//4个参数print_arr(arr, sz);
}int main()
{test1();return 0;
}

在这里插入图片描述

2、使⽤qsort排序结构数据

写一段代码私用qsort排序结构体的数据:

struct Stu
{char name[20];int age;
};

这里的两个结构体元素怎么比较大小?

  1. 按照名字比较-字符串比较–strcmp,strcmp是按照对应着字符串中的字符的ASCII码值比较的
  2. 按照年龄比较-整型比较
#include<stdlib.h>//qsort函数的头文件
#include<string.h>//strcmp的头文件struct Stu 
{char name[20];int age;
};int cmp_stu_by_age(const void* p1, const void* p2) //按年龄排序
{return ((struct Stu*)p1)->age - ((struct Stu*)p2)->age; // 升序  // return ((struct Stu*)p2)->age - ((struct Stu*)p1)->age; // 降序  
}int cmp_stu_by_name(const void* p1, const void* p2) //按名字排序
{return strcmp(((struct Stu*)p1)->name, ((struct Stu*)p2)->name);//强制类型转换为结构体指针
}void print_stu_arr(struct Stu arr[], int sz) //打印结构体数据
{for (int i = 0; i < sz; i++) {printf("Name: %s, Age: %d\n", arr[i].name, arr[i].age);}
}void test2() 
{struct Stu arr[3] = { {"zhangsan", 20}, {"lisi", 35}, {"wangwu", 18} };int sz = sizeof(arr) / sizeof(arr[0]);//qsort(arr, sz, sizeof(arr[0]), cmp_stu_by_age);//按年龄排序qsort(arr, sz, sizeof(arr[0]), cmp_stu_by_name);//按名字排序print_stu_arr(arr, sz);
}int main() 
{test2();return 0;
}

三、qsort函数的模拟实现

可以使⽤回调函数,来模拟实现qsort(采⽤冒泡的⽅式),bubble—sort来模拟实现qsort,。在这之前我们先看一组代码。


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 - 1 - i; j++){if (arr[j] > arr[j + 1]){int tmp = arr[j];arr[j] = arr[j + 1];arr[j + 1] = tmp;}}}
}

上述代码可以用来排序整数数据。
在使用冒泡排序的情况下,假设我要改造这个函数,让他能够排序任意类型的数据,哪些地方需要改造呢?我们知道两个整型元素可以直接使用>比较,但是两个字符串,两个结构体元素是不能使用>比较的。
可以把两个元素比较的方法,封装成函数,然后把函数的地址传给排序函数。

#include<stdlib.h>//qsort函数的头文件
#include<string.h>//strcmp的头文件void print_arr(int arr[], int sz)//打印
{int i = 0;for ( i = 0; i < sz; i++) {printf("%d ", arr[i]);}printf("\n");
}int cmp_int(const void* p1, const void* p2) //比较函数
{return (*(int*)p1 - *(int*)p2); // 升序  // return (*(int*)p2 - *(int*)p1); // 降序  
}void Swap(char* buf1, char* buf2, size_t width) //这是对地址的四个字节(width)一 一交换从而实现整个交换
{int i = 0;for ( i = 0; i < width; i++) {char 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++){//比较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 test3() 
{int arr[] = { 3, 1, 7, 8, 5, 3, 4, 9, 0, 6 };int sz = sizeof(arr) / sizeof(arr[0]);bubble_sort(arr, sz, sizeof(arr[0]), cmp_int);print_arr(arr, sz);
}int main() 
{test3();return 0;
}

在这里插入图片描述

这段代码实现了一个冒泡排序算法,用于对一个整型数组进行排序,并打印排序后的结果。

  • print_arr(int arr[], int sz):这个函数接受一个整型数组arr和数组的大小sz作为参数,并打印数组中的每个元素。
  • cmp_int(const void* p1, const void* p2):这是一个比较函数,用于比较两个整型值。它接受两个指向void的指针(任何类型),将它们转换为指向int的指针,并返回两个整数的差值。这个函数的返回值用于确定排序的顺序(升序或降序)。
  • Swap(char* buf1, char* buf2, size_t width):这个函数用于交换两个元素的内容。它接受两个指向char的指针(代表元素的起始地址)和一个size_t类型的值width(代表元素的宽度,以字节为单位)。然后,它逐个字节地交换两个元素的内容。注意,对于整型数组来说,这种方法不是最高效的,因为它逐个字节地处理数据,而不是直接交换整个整型值。
  • bubble_sort(void* base, size_t sz, size_t width, int (cmp)(const void p1, const void* p2)):这是冒泡排序算法的实现。它接受一个指向数组起始位置的指针base、数组的大小sz、元素的宽度width和一个比较函数cmp作为参数。算法通过多次遍历数组,比较并交换相邻的元素,直到数组完全排序。

同样这个模拟实现的冒泡排序算法也能对结构体元素进行比较:

#include<stdlib.h>//qsort函数的头文件
#include<string.h>//strcmp的头文件struct Stu
{char name[20];int age;
};int cmp_stu_by_age(const void* p1, const void* p2) //按年龄排序
{return ((struct Stu*)p1)->age - ((struct Stu*)p2)->age; // 升序  // return ((struct Stu*)p2)->age - ((struct Stu*)p1)->age; // 降序  
}int cmp_stu_by_name(const void* p1, const void* p2) //按名字排序
{return strcmp(((struct Stu*)p1)->name, ((struct Stu*)p2)->name);//强制类型转换为结构体指针
}void print_stu_arr(struct Stu arr[], int sz) //打印结构体数据
{for (int i = 0; i < sz; i++){printf("Name: %s, Age: %d\n", arr[i].name, arr[i].age);}
}void Swap(char* buf1, char* buf2, size_t width) //这是对地址的四个字节(width)一 一交换从而实现整个交换
{int i = 0;for ( i = 0; i < width; i++) {char 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++){//比较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 test4()
{struct Stu arr[3] = { {"zhangsan",20},{"lisi",35},{"wangwu",18} };int sz = sizeof(arr) / sizeof(arr[0]);bubble_sort(arr, sz, sizeof(arr[0]), cmp_stu_by_name);//bubble_sort(arr, sz, sizeof(arr[0]), cmp_stu_by_age);print_stu_arr(arr, sz);
}int main() 
{test4();return 0;
}

按名字排序:
在这里插入图片描述

相关文章:

【c语言——指针详解(4)】

文章目录 一、回调函数是什么&#xff1f;二、qsort的使⽤1、使⽤qsort函数排序整型数据2、使⽤qsort排序结构数据 三、qsort函数的模拟实现 作者主页 一、回调函数是什么&#xff1f; 回调函数就是⼀个通过函数指针调⽤的函数。 如果你把函数的指针&#xff08;地址&#xf…...

C# (.net6)实现Redis发布和订阅简单案例

概念&#xff1a; 在 .NET 6 中使用 Redis 的/订发布阅模式。发布/订阅&#xff08;Pub/Sub&#xff09;是 Redis 支持的一种消息传递模式&#xff0c;其中一个或多个发布者向一个或多个订阅者发送消息,Redis 客户端可以订阅任意数量的频道。 多个客户端可以订阅一个相同的频道…...

【golang】gorm 使用map实现in 条件查询用法

当 where 字典的值为数组时 gorm 会自动转换为条件 IN 查询 where : map[string]interface{}{} where["id"] [1,2,3] where["name"] "zhangsan"type userList struct {Id int "gorm:id"Name string "gorm:name" } Table.…...

理论篇| 移动端爬虫

移动应用的快速发展和广泛普及带来了海量的数据,这些数据对于市场分析、用户行为洞察和业务优化具有重要价值。然而,由于移动应用的特殊性和防护措施,传统的爬虫技术在采集移动应用数据方面面临许多挑战。因此,App爬虫采集与逆向在爬虫领域的重要性不可低估 然而,App采集…...

systemd实现seatunnel自动化启停

在 systemd 中,您可以通过配置服务单元文件来设置服务在失败或退出后自动重启。这对于确保关键服务在意外退出时能够自动恢复运行非常有用。下面是实现 systemd 自动重启服务的步骤: 通用操作 1. 创建或编辑服务单元文件 假设服务单元文件位于 /etc/systemd/system/my-ser…...

MySQL-08.DDL-表结构操作-创建-案例

一.MySQL创建表的方式 1.首先根据需求文档定义出原型字段&#xff0c;即从需求文档中可以直接设计出来的字段 2.再在原型字段的基础上加上一些基础字段&#xff0c;构成整个表结构的设计 我们采用基于图形化界面的方式来创建表结构 二.案例 原型字段 各字段设计如下&…...

完成Sentinel-Dashboard控制台数据的持久化-同步到Nacos

本次案例采用的是Sentinel1.8.8版本 一、Sentinel源码环境搭建 1、下载Sentinel源码工程 git clone https://github.com/alibaba/Sentinel.git 2、导入到idea 这里可以先运行DashboardApplication.java试一下是否运行成功&#xff0c;若成功&#xff0c;源码环境搭建完毕&a…...

RocketMq详解:三、RocketMq通用生产和消费方法改造

文章目录 1.背景2.通用方法改造2.1添加maven依赖2.2 RocketMq基础配置2.3 配置类2.5 消息传输的对象和结果2.4 消息生产者2.5 消息消费者2.6 功能测试 1.背景 在第二章&#xff1a;《RocketMq详解&#xff1a;二、SpringBoot集成RocketMq》中我们已经实现了消费基本生产和消费…...

基于SpringBoot+Vue+Uniapp的仓库点单小程序的详细设计和实现

2. 详细视频演示 文章底部名片&#xff0c;联系我获取更详细的演示视频 3. 论文参考 4. 项目运行截图 代码运行效果图 代码运行效果图 代码运行效果图 代码运行效果图代码运行效果图 代码运行效果图 5. 技术框架 5.1 后端采用SpringBoot框架 Spring Boot 是一个用于快速开发…...

R语言从多波段tif数据中逐个提取单波段数据

在遥感和地理信息系统&#xff08;GIS&#xff09;领域&#xff0c;将多个波段存储在一个文件中可以更有效地进行数据压缩和管理&#xff0c;减少了存储空间的需求。 在R语言中&#xff0c;处理多波段栅格数据通常涉及以下步骤&#xff1a; 读取数据&#xff1a;使用raster包中…...

华为海思:大小海思的双轮驱动战略分析

华为海思,作为华为旗下的半导体设计部门,近年来在芯片设计领域取得了显著成就,成为了中国乃至全球芯片设计的重要力量。实际上,华为海思并非单一实体,而是由两个主要分支构成:大海思和小海思。这两个分支虽然同属华为海思,但在定位、产品布局以及市场策略上有所不同,共…...

LeetCode | 704.二分查找

标准的二分查找&#xff0c;直接上模板&#xff01; class Solution(object):def search(self, nums, target):""":type nums: List[int]:type target: int:rtype: int"""l 0r len(nums) - 1while l < r:mid (l r 1) / 2if nums[mid] …...

TCP三握四挥

TCP三握(简述) 一开始&#xff0c;客户端和服务端都处于closed状态&#xff0c;服务端主动监听某个端口&#xff0c;处于listen状态 一握要进行C-S的第一个SYN发送&#xff0c;客户端会随机初始化序列号(client_isn)并将其置于TCP首部的序列号字段中&#xff0c;并且将SYN标志…...

java项目之大型商场应急预案管理系统(源码+文档)

项目简介 大型商场应急预案管理系统实现了以下功能&#xff1a; 大型商场应急预案管理系统的主要使用者管理员功能有个人中心&#xff0c;员工管理&#xff0c;预案信息管理&#xff0c;预案类型管理&#xff0c;事件类型管理&#xff0c;预案类型统计管理&#xff0c;事件类…...

【C++】--内存管理

&#x1f47e;个人主页: 起名字真南 &#x1f47b;个人专栏:【数据结构初阶】 【C语言】 【C】 目录 1 C/C内存分布2 C语言中动态内存管理方式 &#xff1a;3 C内存管理方式3.1 new/delete操作内置类型3.2 new和delete操作自定义类型 4 operator new与operator delete4.1 opera…...

【设计模式系列】模板方法模式

一、什么是模板方法模式 模板方法模式&#xff08;Template Method Pattern&#xff09;是一种行为型设计模式&#xff0c;它在父类中定义一个算法的框架&#xff0c;允许子类在不改变算法结构的情况下重写算法的某些特定步骤。这种模式非常适合于那些存在共同行为的类&#x…...

java8 Stream流详细API及用法

目录 整理的更全面的API及用法 创建Stream流 中间操作 filter 过滤 map 映射 flatMap 扁平映射 sorted 排序 limit 截断 skip 跳过 distinct 去重 peek 遍历 终端操作 forEach 遍历 forEachOrdered 顺序遍历 min 统计最小值 max 统计最大值 count 统计元素数量 f…...

Redis——持久化

文章目录 Redis持久化Redis的两种持久化的策略定期备份&#xff1a;RDB触发机制rdb的触发时机&#xff1a;手动执行save&bgsave保存测试不手动执行bgsave测试bgsave操作流程测试通过配置&#xff0c;自动生成rdb快照RDB的优缺点 实时备份&#xff1a;AOFAOF是否会影响到red…...

川字结构布局/国字结构布局

1.串字结构布局 <!DOCTYPE html> <html lang"en"> <head><meta charset"UTF-8"><meta name"viewport" content"widthdevice-width, initial-scale1.0"><title>Document</title><style&g…...

2013年国赛高教杯数学建模C题古塔的变形解题全过程文档及程序

2013年国赛高教杯数学建模 C题 古塔的变形 由于长时间承受自重、气温、风力等各种作用&#xff0c;偶然还要受地震、飓风的影响&#xff0c;古塔会产生各种变形&#xff0c;诸如倾斜、弯曲、扭曲等。为保护古塔&#xff0c;文物部门需适时对古塔进行观测&#xff0c;了解各种变…...

微信小程序之bind和catch

这两个呢&#xff0c;都是绑定事件用的&#xff0c;具体使用有些小区别。 官方文档&#xff1a; 事件冒泡处理不同 bind&#xff1a;绑定的事件会向上冒泡&#xff0c;即触发当前组件的事件后&#xff0c;还会继续触发父组件的相同事件。例如&#xff0c;有一个子视图绑定了b…...

渲染学进阶内容——模型

最近在写模组的时候发现渲染器里面离不开模型的定义,在渲染的第二篇文章中简单的讲解了一下关于模型部分的内容,其实不管是方块还是方块实体,都离不开模型的内容 🧱 一、CubeListBuilder 功能解析 CubeListBuilder 是 Minecraft Java 版模型系统的核心构建器,用于动态创…...

Keil 中设置 STM32 Flash 和 RAM 地址详解

文章目录 Keil 中设置 STM32 Flash 和 RAM 地址详解一、Flash 和 RAM 配置界面(Target 选项卡)1. IROM1(用于配置 Flash)2. IRAM1(用于配置 RAM)二、链接器设置界面(Linker 选项卡)1. 勾选“Use Memory Layout from Target Dialog”2. 查看链接器参数(如果没有勾选上面…...

vue3 定时器-定义全局方法 vue+ts

1.创建ts文件 路径&#xff1a;src/utils/timer.ts 完整代码&#xff1a; import { onUnmounted } from vuetype TimerCallback (...args: any[]) > voidexport function useGlobalTimer() {const timers: Map<number, NodeJS.Timeout> new Map()// 创建定时器con…...

数据库分批入库

今天在工作中&#xff0c;遇到一个问题&#xff0c;就是分批查询的时候&#xff0c;由于批次过大导致出现了一些问题&#xff0c;一下是问题描述和解决方案&#xff1a; 示例&#xff1a; // 假设已有数据列表 dataList 和 PreparedStatement pstmt int batchSize 1000; // …...

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

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

多模态大语言模型arxiv论文略读(108)

CROME: Cross-Modal Adapters for Efficient Multimodal LLM ➡️ 论文标题&#xff1a;CROME: Cross-Modal Adapters for Efficient Multimodal LLM ➡️ 论文作者&#xff1a;Sayna Ebrahimi, Sercan O. Arik, Tejas Nama, Tomas Pfister ➡️ 研究机构: Google Cloud AI Re…...

什么是Ansible Jinja2

理解 Ansible Jinja2 模板 Ansible 是一款功能强大的开源自动化工具&#xff0c;可让您无缝地管理和配置系统。Ansible 的一大亮点是它使用 Jinja2 模板&#xff0c;允许您根据变量数据动态生成文件、配置设置和脚本。本文将向您介绍 Ansible 中的 Jinja2 模板&#xff0c;并通…...

深度学习习题2

1.如果增加神经网络的宽度&#xff0c;精确度会增加到一个特定阈值后&#xff0c;便开始降低。造成这一现象的可能原因是什么&#xff1f; A、即使增加卷积核的数量&#xff0c;只有少部分的核会被用作预测 B、当卷积核数量增加时&#xff0c;神经网络的预测能力会降低 C、当卷…...

根目录0xa0属性对应的Ntfs!_SCB中的FileObject是什么时候被建立的----NTFS源代码分析--重要

根目录0xa0属性对应的Ntfs!_SCB中的FileObject是什么时候被建立的 第一部分&#xff1a; 0: kd> g Breakpoint 9 hit Ntfs!ReadIndexBuffer: f7173886 55 push ebp 0: kd> kc # 00 Ntfs!ReadIndexBuffer 01 Ntfs!FindFirstIndexEntry 02 Ntfs!NtfsUpda…...