C语言——深入理解指针(三)

C语言——深入理解指针(三)

1.回调函数是什么?

首先我们来回顾一下函数的直接调用

在这里插入图片描述
回调函数就是通过函数指针调用的函数。我们将函数的指针(地址)作为参数传递给另一个函数,当这个指针被用来调用其所指向的函数时,被调用的函数就是回调函数。
在这里插入图片描述

2.qsort函数

  • quick sort简称qsort,是C语言中提供的一个排序函数,是基于快速排序算法思想的一种排序算法。
  • 其优点有:现成的排序算法可直接使用;而且大部分情况下效率都是比冒泡排序高的;qsort函数可以排序任意类型的数据

function
在这里插入图片描述

void qsort (void* base,//指针,指向了被排序数组的第一个元素size_t num, // 这里是base指向的被排序数组的元素个数size_t size,//这里是base指向的被排序数组的元素大小(长度),单位是字节int (*compar)(const void*,const void*)//函数指针,指针指向的函数是用来比较被排序数组中的两个元素的);

在这里插入图片描述
该函数排序默认为升序,希望为降序,只需将参数p1,p2顺序反过来即可

3.qsort函数的使用

  • qsort函数对整型数组的排序:

我们在使用qsort函数排序时,常需要自己写一个比较的逻辑,来比较整形数据的大小,如我们可以在这里写一个int cmp_int(const void* p1,const void* p2)函数指针,而指针指向的函数是用来比较被排序数组中的两个元素的,里面的p1,p2则分别指向一个整型变量,然后qsort函数根据返回的结果进行排序。整体整合下来如下:

#include<stdio.h>
#include<stdlib.h>
#include<string.h>
//打印
void print_arr(int arr[], int sz)
{int i = 0;for (i = 0;i < sz;i++){printf("%d ", arr[i]);}printf("\n");
}
//写的是升序,若想改为降序,只需改变p1,p2的位置
int cmp_int(const void* p1,const void* p2)//const修饰函数参数,表示参数在函数体内不能被修改
{if (*(int*)p1 > *(int*)p2)//强制类型转换为整型return 1;else if (*(int*)p1 < *(int*)p2)return -1;elsereturn 0;//根据qsort函数的排序逻辑,上面这一部分也可简化为:return(*(int*)p1 - *(int*)p2);
}
void test()
{int arr[] = { 4,3,7,9,0,2,1,6 };int sz = sizeof(arr) / sizeof(arr[0]);print_arr(arr, sz);//打印排序前的数组//排序qsort(arr,sz,sizeof(arr[0]),cmp_int);print_arr(arr, sz);//打印排序后的数组
}
int main()
{test();return 0;
}

运行结果:
在这里插入图片描述

  • qsort函数对结构体数据的排序:

1.按照年龄比较,只需比较整形数据的大小:

#include<stdio.h>
#include<stdlib.h>
#include<string.h>
struct Stu
{char name[30];int age;
};
void print_stu(struct Stu arr[], int sz)
{int i = 0;for (i = 0;i < sz;i++){printf("%s:%d\n", arr[i].name, arr[i].age);}
}
int cmp_stu_by_age(const void* p1, const void* p2)
{return (*(struct Stu*)p1).age - (*(struct Stu*)p2).age;
}
void test()
{struct Stu arr[] = { {"Jack",28},{"Rose",25},{"liming",19} };int sz = sizeof(arr) / sizeof(arr[0]);qsort(arr, sz, sizeof(arr[0]), cmp_stu_by_age);print_stu(arr, sz);
}
int main()
{test();return 0;
}

运行结果:
在这里插入图片描述

2.按照名字比较,这里注意比较的是字符串的大小,注意这里不能使用> >= < <= == !=,需要使用strcmp()函数:

//按照名字比较
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
struct Stu
{char name[30];int age;
};
//p1指向了一个结构体变量
//p2指向了一个结构体变量
void print_stu(struct Stu arr[], int sz)
{int i = 0;for (i = 0; i < sz; i++){printf("%s: %d\n", arr[i].name, arr[i].age);}printf("\n");
}
int cmp_stu_by_name(const void* p1, const void* p2)
{return strcmp(((struct Stu*)p1)->name, ((struct Stu*)p2)->name);
}
void test()
{struct Stu arr[] = { {"zhangsan", 20},{"lisi", 38},{"wangwu", 18} };int sz = sizeof(arr) / sizeof(arr[0]);qsort(arr, sz, sizeof(arr[0]), cmp_stu_by_name);print_stu(arr, sz);
}
int main()
{test();return 0;
}

运行结果:
在这里插入图片描述

4.qsort函数模拟实现

这里,依照qsort函数的逻辑,模拟实现一个底层逻辑为冒泡排序但可排序任意类型的数据的函数(即泛型编程)。

  • 设计bubble_sort函数,只是底层算法和qsort函数不一样,但其参数可模拟qsort函数,第一个参数指向数组首元素的指针类比qsort我们记为base,同理第二个参数为size_t sz,第三个参数记为size_t width,第四个参数我们要写的是函数指针,因为不知道将要比较什么类型的数据,所以用void修饰取出的元素,而函数的返回类型因为知晓是整型则为int即int (cmp)(const void p1, const void* p2)
  • 和冒泡排序的底层逻辑一样,首先确定外层比较的趟数,然后内层决定趟内部比较的对数。外层只需要算出有几个元素来确定需要几趟,那么内层比较该如何比较?该如何确定一趟比较的对数呢?这里就不同于冒泡排序了
  • 这里需要将if(arr[j]>arr[j+1])修改,因为不知道类型,交换的时候并不仅仅是简单的比较整型元素的大小,但是由于此时已经知道比较方法cmp(),所以只需调用一下cmp(),现在需要将arr[j]和arr[j+1]这两个相邻元素的地址传到cmp()中一个给p1,一个给p2。现在只知道base指向数组首元素的地址,那该如何越过中间的元素获取j和j+的地址呢?如下图:
    在这里插入图片描述
  • 下面一个问题就是如何交换这两个元素。由于不能将它们整体交换,只知道这两个元素的地址,所以可以将它们转换为字节来进行交换,假设一个元素占四个字节,则可将第一个字节与第一个字节交换,以此类推从而实现元素的交换。写一个Swap函数,其参数我们则需要传两个元素的地址(char*)base + j * width, (char*)base + (j + 1) * width和元素的宽度width,然后在交换函数中,使用一个for循环,让其对应字节元素相交换就实现交换了。
    好,现在我们将其整合下来:
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
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);
}
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(cmp((char*)base + j * width, (char*)base + (j + 1) * width) > 0){Swap((char*)base + j * width, (char*)base + (j + 1) * width, width);}}}
}
void test()
{int arr[] = { 3,1,5,8,7,9,2,4,6,0 };int sz = sizeof(arr) / sizeof(arr[0]);print_arr(arr, sz);bubble_sort(arr, sz, sizeof(arr[0]), cmp_int);print_arr(arr, sz);
}
int main()
{test();  return 0;
}

运行结果:
在这里插入图片描述
上面已经可以完全排序整型数据了,现在我们来测试排序结构体数据,排序的思想一样,继续用bubble_sort()函数。

#include<stdio.h>
#include<stdlib.h>
#include<string.h>
struct Stu
{char name[30];int age;
};
void print_stu(struct Stu arr[], int sz)
{int i = 0;for (i = 0;i < sz;i++){printf("%s:%d\n", arr[i].name, arr[i].age);}
}
int cmp_stu_by_age(const void* p1, const void* p2)
{return (*(struct Stu*)p1).age - (*(struct Stu*)p2).age;
}
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(cmp((char*)base + j * width, (char*)base + (j + 1) * width) > 0){Swap((char*)base + j * width, (char*)base + (j + 1) * width, width);}}}
}
void test()
{struct Stu arr[] = { {"Jack",28},{"Rose",25},{"liming",19} };int sz = sizeof(arr) / sizeof(arr[0]);bubble_sort(arr, sz, sizeof(arr[0]), cmp_stu_by_age);//bubble_sort(arr, sz, sizeof(arr[0]), cmp_stu_by_name);print_stu(arr, sz);
}
int main()
{test();
}

按年龄排序的运行结果:
在这里插入图片描述

到这里今天的内容就结束了
谢谢观看!
这篇内容是我在qsort函数上的总结,如果你觉得有用,不妨点个赞收藏一下让更多人看到,非常欢迎在评论区交流指正,一起进步~感谢读到这里的每一位朋友!
“技术的路上,一个人走可能会很慢,但一群人同行就会更有力量!”

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

kettle 8.2 ETL项目【四、加载数据】

一、dim_store表结构,数据来源于业务表,且随时间会有增加,属于缓慢变化维(SCD)类型二 转换步骤如下 详细步骤如下

【测试报告】SoundWave(Java+Selenium+Jmeter自动化测试)

一、项目背景 随着数字音乐内容的爆炸式增长&#xff0c;用户对于便捷、高效的音乐管理与播放需求日益增强。传统的本地音乐管理方式已无法满足多设备同步、在线分享与个性化推荐等现代需求。为此&#xff0c;我们设计并开发了一款基于Spring Boot框架的SoundWave&#xff0c;旨…

C++ 类和对象详解(1)

类和对象是 C 面向对象编程的核心概念&#xff0c;它们为代码提供了更好的封装性、可读性和可维护性。本文将从类的定义开始&#xff0c;逐步讲解访问限定符、类域、实例化、对象大小计算、this 指针等关键知识&#xff0c;并对比 C 语言与 C 在实现数据结构时的差异&#xff0…

奈飞工厂:算法优化实战

推荐系统的算法逻辑与优化技巧在流媒体行业的 “用户注意力争夺战” 中&#xff0c;推荐系统是决定成败的核心武器。对于拥有2.3 亿全球付费用户的奈飞&#xff08;Netflix&#xff09;而言&#xff0c;其推荐系统每天处理数十亿次用户交互&#xff0c;最终实现了一个惊人数据&…

【人工智能99问】BERT的训练过程和推理过程是怎么样的?(24/99)

文章目录BERT的训练过程与推理过程一、预训练过程&#xff1a;学习通用语言表示1. 数据准备2. MLM任务训练&#xff08;核心&#xff09;3. NSP任务训练4. 预训练优化二、微调过程&#xff1a;适配下游任务1. 任务定义与数据2. 输入处理3. 模型结构调整4. 微调训练三、推理过程…

[TryHackMe]Challenges---Game Zone游戏区

这个房间将涵盖 SQLi&#xff08;手动利用此漏洞和通过 SQLMap&#xff09;&#xff0c;破解用户的哈希密码&#xff0c;使用 SSH 隧道揭示隐藏服务&#xff0c;以及使用 metasploit payload 获取 root 权限。 1.通过SQL注入获得访问权限 手工注入 输入用户名 尝试使用SQL注入…

北京JAVA基础面试30天打卡09

1.MySQL存储引擎及区别特性MyISAMMemoryInnoDBB 树索引✅ Yes✅ Yes✅ Yes备份 / 按时间点恢复✅ Yes✅ Yes✅ Yes集群数据库支持❌ No❌ No❌ No聚簇索引❌ No❌ No✅ Yes压缩数据✅ Yes❌ No✅ Yes数据缓存❌ NoN/A✅ Yes加密数据✅ Yes✅ Yes✅ Yes外键支持❌ No❌ No✅ Yes…

AI时代的SD-WAN异地组网如何落地?

在全球化运营与数字化转型浪潮下&#xff0c;企业分支机构、数据中心与云服务的跨地域互联需求激增。传统专线因成本高昂、部署缓慢、灵活性差等问题日益凸显不足。SD-WAN以其智能化调度、显著降本、敏捷部署和云网融合的核心优势&#xff0c;成为实现高效、可靠、安全异地组网…

css中的color-mix()函数

color-mix() 是 CSS 颜色模块&#xff08;CSS Color Module Level 5&#xff09;中引入的一个强大的颜色混合函数&#xff0c;用于在指定的颜色空间中混合两种或多种颜色&#xff0c;生成新的颜色值。它解决了传统颜色混合&#xff08;如通过透明度叠加&#xff09;在视觉一致性…

Github desktop介绍(GitHub官方推出的一款图形化桌面工具,旨在简化Git和GitHub的使用流程)

文章目录**1. 简化 Git 操作****2. 代码版本控制****3. 团队协作****4. 代码托管与共享****5. 集成与扩展****6. 跨平台支持****7. 适合的使用场景****总结**GitHub Desktop 是 GitHub 官方推出的一款图形化桌面工具&#xff0c;旨在简化 Git 和 GitHub 的使用流程&#xff0c;…

整数规划-分支定界

内容来自:b站数学建模老哥 如:3.4,先找小于3的,再找大于4的 逐个

JetPack系列教程(六):Paging——让分页加载不再“秃”然

前言 在Android开发的世界里&#xff0c;分页加载就像是一场永无止境的马拉松&#xff0c;每次滚动到底部&#xff0c;都仿佛在提醒你&#xff1a;“嘿&#xff0c;朋友&#xff0c;还有更多数据等着你呢&#xff01;”但别担心&#xff0c;Google大佬们早就看透了我们的烦恼&a…

扎实基础!深入理解Spring框架,解锁Java开发新境界

大家好&#xff0c;今天想和大家聊聊Java开发路上绕不开的一个重要基石——Spring框架。很多朋友在接触SpringBoot、SpringCloud这些现代化开发工具时&#xff0c;常常会感到吃力。究其原因&#xff0c;往往是对其底层的Spring核心机制理解不够透彻。Spring是构建这些高效框架的…

Heterophily-aware Representation Learning on Heterogeneous Graphs

Heterophily-Aware Representation Learning on Heterogeneous Graphs (TPAMI 2025) 计算机科学 1区 I:18.6 top期刊 📌 摘要 现实世界中的图结构通常非常复杂,不仅具有全局结构上的异质性,还表现出局部邻域内的强异质相似性(heterophily)。虽然越来越多的研究揭示了图…

计算机视觉(7)-纯视觉方案实现端到端轨迹规划(思路梳理)

基于纯视觉方案实现端到端轨迹规划&#xff0c;需融合开源模型、自有数据及系统工程优化。以下提供一套从模型选型到部署落地的完整方案&#xff0c;结合前沿开源技术与工业实践&#xff1a; 一、开源模型选型与组合策略 1. 感知-预测一体化模型 ViP3D&#xff08;清华&#…

Nginx 屏蔽服务器名称与版本信息(源码级修改)

Nginx 屏蔽服务器名称与版本信息&#xff08;源码级修改&#xff09; 一、背景与目的 在生产环境部署 Nginx 时&#xff0c;默认配置会在 Server 响应头中暴露服务类型&#xff08;如 nginx&#xff09;和版本号&#xff08;如 nginx/1.25.4&#xff09;。这些信息可能被攻击者…

从钢板内部应力视角,重新认识护栏板矫平机

一、为什么钢板会“自带波浪”&#xff1f; 钢卷在热轧后冷却、卷取、长途运输、多次吊运时&#xff0c;不同部位受到的温度、张力、碰撞并不一致&#xff0c;内部会产生不均匀的残余应力。应力大的区域想“伸长”&#xff0c;应力小的区域想“缩短”&#xff0c;宏观上就表现为…

C++中的`auto`与`std::any`:功能、区别与选择建议

引言 在C编程中&#xff0c;auto和std::any是两个功能强大但用途不同的工具。理解它们的区别和适用场景对于编写高效、可维护的代码至关重要。本文将详细介绍auto和std::any的基本概念、使用方法、适用场景以及它们之间的区别&#xff0c;并提供选择建议&#xff0c;帮助开发者…

【Linux】进程(Process)

一、什么是进程二、进程的创建三、进程的状态四、僵尸进程五、孤儿进程六、进程的优先级 以及 并发/并行七、进程的切换一、什么是进程&#xff1f;什么是进程呢(一)?官方话来说&#xff1a;进程是一个执行实例、正在执行的程序、是系统资源分配的基本单位按课本官方话可能有一…

销售管理系统哪个好?14款软件深度对比

本文将深入对比14款销售管理系统&#xff1a;1.纷享销客&#xff1b; 2.Zoho CRM&#xff1b; 3.神州云动 CRM&#xff1b; 4.励销云 CRM&#xff1b; 5.Microsoft Dynamics 365 CRM&#xff1b; 6.悟空 CRM&#xff1b; 7.泛微 CRM&#xff1b; 8.HubSpot CRM&#xff1b; 9.…