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

(C语言)理解 回调函数 和 qsort函数

一. 回调函数

  1. 什么是回调函数?

回调函数(Callback Function)是通过 函数指针 调用的函数。其本质是:
将函数作为参数传递另一个函数,并在特定条件下被调用,实现 反向控制

  2. 回调函数的使用

回调函数的主要用途包括事件处理和排序算法。通过传递函数指针作为参数,可以实现代码的灵活性可重用性。例如,在一个简单的计算器程序中,可以通过传递不同的计算函数(如加法、减法等)来简化代码。

  3. 示例代码

以下是一个简单的回调函数示例,展示了如何使用回调函数进行加法和减法操作:

#include <stdio.h>

// 定义回调函数类型
typedef int (*Operation)(int, int);

// 定义加法函数
int add(int a, int b) 
{
    return a + b;
}

// 定义减法函数
int jian(int a, int b) 
{
    return a - b;
}

// 使用回调函数进行计算
int calculate(int a, int b, Operation op)
 {
    return op(a, b);
}

int main() 
{
    int x = 10, y = 5;
    printf(" %d\n", calculate(x, y, add));
    printf(" %d\n", calculate(x, y, jian));
    return 0;
}

二.    qsort函数

1 qsort 函数的定义

qsort 函数是C语言标准库中的一个排序函数,用于对数组进行快速排序。它的完整声明如下:

#include <stdlib.h>
void qsort(void *base, 
           size_t nmemb, 
           size_t size,
           int (*compar)(const void * e1, const void * e2));
  • base:指向要排序的数组的第一个元素的指针。
  • nmemb:数组中元素的个数。
  • size:数组中每个元素的大小,以字节为单位。
  • compar:用来比较两个元素的函数,即函数指针(回调函数)。

2.compar参数

  • compar参数指向一个比较两个元素的函数。比较函数的原型应该像下面这样。注意两个形参必须是const void *型,同时在调用compar 函数(compar实质为函数指针,这里称它所指向的函数也为compar)时,传入的实参也必须转换成const void *型。在compar函数内部会将const void *型转换成实际类型。
  • int compar(const void *e1, const void *e2);
  • 如果compar返回值小于0(< 0),那么e1所指向元素会被排在e2所指向元素的左面
  • 如果compar返回值等于0(= 0),那么e1所指向元素与e2所指向元素的顺序不确定;
  • 如果compar返回值大于0(> 0),那么e1所指向元素会被排在e2所指向元素的右面

这里再强调一下 

   参数类型:必须为   const void*

参考代码: 

以下是一个使用 qsort 函数对整型数组进行升序排序的示例:

qsort  一般默认是升序

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

int comp(const void* e1, const void* e2)
{
	return (*(int*)e1 - *(int*)e2);
}
int main()
{
	int arr[6] = { 44,0,520,1314,888,444};
	int sz = sizeof(arr) / sizeof(arr[0]);
	qsort(arr, sz, sizeof(arr[0]), comp);
	/*for (int i = 0; i < sz; i++)
		printf("%d ", arr[i]);*/
	return 0;
}

运行结果: 

以下是一个使用 qsort 函数对结构体数组进行排序的示例, 

#include<stdio.h>
#include<stdlib.h>
#include<string.h>
typedef struct student
{
    int id;
    char name[10];
    int grade; 
}student;
  
int cmp1(const void *a, const void *b)//一级排序
{
    student *s1 = (student*)a;
    student *s2 = (student*)b;
    return s1->id - s2->id;
}
 
int cmp2(const void *a,const void *b)//二级排序
{
    student *s1 = (student*)a;
    student *s2 = (student*)b;
    if(strcmp(s1->name , s2->name) != 0)
        return strcmp(s1->name , s2->name);  
    else   
        return s1->id - s2->id;              
}
 
int cmp3(const void *a,const void *b)//三级排序
{
    student *s1 = (student*)a;
    student *s2 = (student*)b;
    if(s1->grade != s2->grade)   
        return s1->grade - s2->grade;
    else
    {
        if(strcmp(s1->name , s2->name) != 0)
            return strcmp(s1->name , s2->name);
        else
            return s1->id - s1->id;
    }
}
 
int main()
{
    int i,N,C;
    scanf("%d %d",&N,&C);
     
    student *stu;
    stu=(student*)malloc(N*sizeof(student));
 
    for(i = 0 ; i < N ; i++)
        scanf("%d %s %d" , &stu[i].id , stu[i].name , &stu[i].grade);
    switch(C)
    {
        case 1: qsort(stu, N, sizeof(student), cmp1);break;//一级排序
        case 2: qsort(stu, N, sizeof(student), cmp2);break;//二级排序
        case 3: qsort(stu, N, sizeof(student), cmp3);break;//三级排序
    }
    printf("排序结果:\n");
    for(i = 0 ; i < N ; i++)
        printf("%03d %s %d\n" , stu[i].id , stu[i].name , stu[i].grade);
    return 0;
}

三 .调试技巧与常见陷阱

1. 调试建议

  • 在比较函数中添加打印语句:

  • int compare_debug(const void *a, const void *b)
     { 
    int x = *(int*)a; 
    int y = *(int*)b;
     printf("Comparing %d vs %d\n", x, y); 
    return x - y; 
    }

2. 常见错误


掌握回调函数与qsort的配合使用,不仅能写出更通用的代码,更能深入理解C语言"函数即数据"的哲学思想。这种设计模式在文件操作、事件处理、算法策略等场景中广泛应用,是提升编程能力的重要阶梯。

  • 错误1:忘记类型转换

    // 错误写法: return *a - *b; 
    // a和b是void指针!
     // 正确: return *(int*)a - *(int*)b;

  • 错误2:整数溢出

  •  int compare_unsafe(const void *a, const void *b) 
    {
     return *(int*)a - *(void*)b;
     // 可能溢出! 
    }
     // 改进版:
     int compare_safe(const void *a, const void *b) 
    { 
    int x = *(int*)a; 
    int y = *(int*)b; 
    return (x > y) ? 1 : ((x < y) ? -1 : 0); 
    }

  • 错误3:修改const数据

    int compare_wrong(const void *a, const void *b) 
    {
     *(int*)a = 10;
     // 尝试修改原始数据!
     return ...;
     }
    四.最佳实践总结
  • 严格遵循比较函数规范
    确保返回正确的-1/0/1,而不仅仅是差值

  • 优先使用const修饰
    比较函数参数应声明为const void*

  • 复杂结构预先处理
    对频繁排序的大型结构,可建立索引数组

  • 跨平台注意字节序
    处理网络传输数据时考虑大小端问题

  • 性能关键处慎用
    高频调用场景可考虑特定优化

相关文章:

  • 如何理解变量提升和 var、let、const间的区别
  • 如何使用 DeepEval 优化 Elasticsearch 中的 RAG 检索
  • java后端怎么写好根据角色控制查询不同数据,
  • AOP+Nacos实现动态数据源切换
  • 企业级云MES全套源码,支持app、小程序、H5、台后管理端
  • 【AI】在AWS AI芯片服务上部署运行Qwen 2.5模型
  • 科技云报到:AI Agent打了个响指,商业齿轮加速转动
  • Android 第四次面试总结(自定义 View 与事件分发深度解析)
  • pytorch小记(十):pytorch中torch.tril 和 torch.triu 详解
  • 一场由 ES 分片 routing 引发的问题
  • 【含文档+PPT+源码】基于小程序的智能停车管理系统设计与开发
  • 【数据分享】1999—2023年地级市固定资产投资和对外经济贸易数据(Shp/Excel格式)
  • 咖啡点单小程序毕业设计(JAVA+SpringBoot+微信小程序+完整源码+论文)
  • 卷积神经网络(CNN)与反向传播
  • 威联通 NAS 的 Docker 镜像与安装 logseq
  • 案例驱动的 IT 团队管理:创新与突破之路:第三章 项目攻坚:从流程优化到敏捷破局-3.2.3技术债务的可视化管理方案
  • 永磁同步电机模型第二篇之两相电机实时模型
  • 使用 ESP8266 和 Android 应用程序实现基于 IOT 的语音控制家庭自动化
  • Apache DolphinScheduler:一个可视化大数据工作流调度平台
  • VSTO(C#)Excel开发13:实现定时器
  • 下达专项资金、党政主官田间调研……全国多地力保夏粮稳收
  • 媒体:机票盲盒值不值得开?年轻人正用行为博弈的逻辑重构规则
  • 全国铁路昨日发送2311.9万人次,同比增长11.7%创历史新高
  • 五大白酒去年净利超1500亿元:贵州茅台862亿领跑,洋河营收净利齐降
  • 三大上市猪企:前瞻应对饲料原材料价格波动
  • 旭辉控股集团:去年收入477.89亿元,长远计划逐步向轻资产业务模式转型