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

C语言第八章指针四

一.回调函数的概念

        回调函数就是一个通过函数指针调用的函数。 如果你把函数的指针(地址)作为参数传递给另一个函数,当这个指针被用来调用其所指向的函数时,被调用的函数就是回调函数。回调函数不是由该函数的实现方直接调用,而是在特定的事件或条件发生时由另外的一方调用的,用于对该事件或条件进西行响应。

        上一节中,我们写的计算机的实现的代码中,我们可以把调用的函数的地址以参数的形式传递过去,使用函数指针接收,函数指针指向什么函数就调用什么函数,这里其实使用的就是回调函数的功能。

//使⽤回调函数改造前 
#include <stdio.h>
int add(int a, int b)    //加法函数
{return a + b;
}
int sub(int a, int b)    //减法函数
{return a - b;
}
int mul(int a, int b)    //乘法函数
{return a * b;
}
int div(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(" 3:mul            4:div \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 3:printf("输⼊操作数:");scanf("%d %d", &x, &y);ret = mul(x, y);printf("ret = %d\n", ret);break;case 4:printf("输⼊操作数:");scanf("%d %d", &x, &y);ret = div(x, y);printf("ret = %d\n", ret);break;case 0:printf("退出程序\n");break;default:printf("选择错误\n");break;}} while (input);    //当输入为0时,推出循环,计算结束return 0;
}
//使⽤回到函数改造后 
#include <stdio.h>
int add(int a, int b)
{return a + b;
}
int sub(int a, int b)
{return a - b;
}
int mul(int a, int b)
{return a * b;
}
int div(int a, int b)
{return a / b;
}void calc(int(*pf)(int, int))    //定义一个计算函数,参数为函数指针类型
{int ret = 0;int x, y;printf("输⼊操作数:");scanf("%d %d", &x, &y);ret = pf(x, y);        //在此调用计算函数,通过函数的地址进行间接调用printf("ret = %d\n", ret);    
}int main()
{int input = 1;do{printf("*************************\n");printf(" 1:add            2:sub \n");printf(" 3:mul            4:div \n");printf("************************\n");printf("请选择:");scanf("%d", &input);switch (input)    //根据输入值的不同,执行不同的函数{case 1:calc(add);        //根据输入的值,用函数指针调用函数break;case 2:calc(sub);        //将对应的函数地址传给calc函数,用于进一步的间接函数调用break;case 3:calc(mul);break;case 4:calc(div);break;case 0:printf("退出程序\n");    break;default:printf("选择错误\n");break;}} while (input);    //当输入为0时,退出循环,结束计算return 0;
}

        上述两个代码,分别为未使用回调函数和使用了回调函数。后者通过在main函数输入值的不同,从而传给calc函数的函数指针不同,这样就可以调用对应函数指针的函数了。这种通过函数指针调用的函数,把函数的地址当作另一个的参数,在通过该函数指针进行间接调用时,被调用的函数就是回调函数。比如上方代码的四个加减乘除函数就是回调函数。

二.qsort函数

       1.qsort函数的介绍

        在C语言中,qsort函数是C标准库<stdlib.h>提供的快速排序函数,用于对任意类型数据进行排序,通用性强,通过回调函数实现自定义比较逻辑。下面是给出的C标准库下的qsort函数的函数声明。

void qsort(
void *base,              // 待排序数组的首元素地址
size_t nmemb,            // 待排序数组元素的个数
size_t size,             // 每个元素的大小(单位是字节)
int (*compar)(const void *, const void *)         // 比较函数指针,决定排序的规则
);

        上述函数声明中,base的类型为void *,同样的在比较函数的函数参数中,参数类型均为const void *,实现了泛型编程,这样可以对任意类型的数据进行排序。下面介绍一下qsort函数每个参数的用意。

        base参数接收的是待排序数组的首元素的地址,便于排序操作的开始。通过快速排序的逻辑进行排序。

        nmemb参数为size_t类型,代表的是该函数参数为无符号整型。该参数表示的是待排序数组的元素个数。

        size参数为size_t类型,代表的是该函数参数为无符号整型。该函数参数表示的是每个元素的内存大小。

        compar参数为函数指针类型,为比较函数的指针,用来决定排序的规则。里面两个函数的参数为前后两个待排序元素的指针。通过函数指针回调函数,返回一个整型的值,参考cplusplus网站,知道了前者元素大于后者返回正数,小于则返回负数,两者相等则返回数字0。

        通过上述的描述,相信大家对qsort函数依然模糊,不过,没关系。我们先看该函数时怎么使用的。

2.操作符知识(补充)

        在操作符章节,我们遗留了一个操作符未进行说明,这个操作符就是->,结构体成员间接访问操作符,这个操作符和‘.’的区别在于已知条件不同。

        .操作符在已知结构体变量名时,使用该操作符就可以得到结构体成员的内容。具体使用格式是:结构体名.成员名

        ->操作符是在已知结构体变量的地址时使用的,使用该操作符可以通过地址得到结构体成员的内容。使用时的格式为:结构体地址->成员名。

3.字符串大小比较

        在比较两数字大小的时候,我们通常会使用大于小于等于些许的操作符进行关系的判断,但是在比较字符串的大小时,这种方法就不再成立了。所以在C语言标准库中,提供了字符串大小比较的函数。下方是cplusplus官网中对该函数的描述。

        通过上述函数的学习,可以得知:比较字符串的函数名为strcmp,返回值为int类型。函数参数为需要比较的两个字符串。类型均为const char *的字符指针类型。通过阅读返回值的规则,可以得知:当str1大于str2时,返回一个大于0的整数。小于时返回一个小于0的负数。当两者字符串大小相等时,返回数字0。这点刚好和qsort函数中函数指针的返回值规则相同。

        在字符串大小比较时,并不是比较的是字符串的长短,而是从头逐一的比较字符串中字符的ASCII码值的大小,例如字符串a:cdefg和字符串b:cdefkkk。两个字符串比较时,前四个字符的ASCII码值相同,所以比较第五个字符g和k的ASCII码值的大小,比较出来哪一个字符的ASCII码值大,则该字符串的大小就大。

4.qsort函数的使用

#include <stdio.h>
#include <stdlib.h>
//qosrt函数的使⽤者得实现⼀个⽐较函数 
int int_cmp(const void * p1, const void * p2)
{return (*( int *)p1 - *(int *) p2);
}int main()
{int arr[] = { 1, 3, 5, 7, 9, 2, 4, 6, 8, 0 };int i = 0;qsort(arr, sizeof(arr) / sizeof(arr[0]), sizeof (int), int_cmp);for (i = 0; i< sizeof(arr) / sizeof(arr[0]); i++){printf( "%d ", arr[i]);}printf("\n");return 0;
}

        上面是qsort函数对整型值进行排序的使用。通过官网的内容一一进行传参,第一个是数组首元素的地址,所以将数组名传过去。第二个为数组元素的个数,所以用sizeof操作符进行计算,将计算结果进行传参。第三个函数参数为数组元素的大小(单位是字节),同样用sizeof操作符进行计算,并将计算结果传过去。最后一个函数参数为函数指针,所以我们需要写一个函数,然后将该函数指针传过来。通过官网内容,得知该函数在前后元素大小关系不同时,返回不同的整型值。我们需要先将函数的两个参数强制类型转换为整型指针类型,才能进行解引用操作,并且按照官网内容返回对应大小关系的值。这样进行qsort排序就可以得到一个升序的整型数组了。

struct Stu  //定义学⽣结构体
{char name[20];    //名字 int age;    //年龄 
};//假设按照年龄来⽐较 
int cmp_stu_by_age(const void* e1, const void* e2)
{return ((struct Stu*)e1)->age - ((struct Stu*)e2)->age;
}//strcmp - 是库函数,是专⻔⽤来⽐较两个字符串的⼤⼩的,字符串不可以用大于小于等于符号进行比较
//假设按照名字来⽐较 
int cmp_stu_by_name(const void* e1, const void* e2)
{return strcmp(((struct Stu*)e1)->name, ((struct Stu*)e2)->name);
}//按照年龄来排序 
void test2()
{struct Stu s[] = { {"zhangsan", 20}, {"lisi", 30}, {"wangwu", 15} };int sz = sizeof(s) / sizeof(s[0]);qsort(s, sz, sizeof(s[0]), cmp_stu_by_age);
}//按照名字来排序 
void test3()
{struct Stu s[] = { {"zhangsan", 20}, {"lisi", 30}, {"wangwu", 15} };int sz = sizeof(s) / sizeof(s[0]);qsort(s, sz, sizeof(s[0]), cmp_stu_by_name);
}int main()
{test2();    //按照年龄排序test3();    //按照名字进行排序return 0;
}

        上面是qsort函数对结构体类型的数据进行排序的代码实践。在上述代码中,分别对结构体成员年龄和名字进行了排序。

三.qsort函数的模拟实现

1.冒泡排序回顾

        在排序中,有许多高质量的算法,比如选择排序、快速排序、冒泡排序......在qsort函数中,用的就是快速排序算法的逻辑,下面我们进行qsort函数的模拟实现,就用简单的冒泡排序吧。虽然实现的算法不同,但是效果是相同的。下面我们来回顾一下冒泡排序。

#include <stdio.h>void bubbleSort(int arr[], int n) 
{for (int i = 0; i < n - 1; i++) {for (int j = 0; j < n - i - 1; j++) {if (arr[j] > arr[j + 1]) {// 交换相邻元素int temp = arr[j];arr[j] = arr[j + 1];arr[j + 1] = temp;}}}
}int main() {int arr[] = {64, 34, 25, 12, 22, 11, 90};int n = sizeof(arr) / sizeof(arr[0]);bubbleSort(arr, n);printf("排序后的数组: \n");for (int i = 0; i < n; i++)printf("%d ", arr[i]);return 0;
}

        上述冒泡排序中,运用了bubblesort函数进行排序,可以对整型数组进行排序,但是缺点是:不能对其他类型进行排序。要对其它类型的数据进行排序就需要改变该函数的参数,以及内部交换元素的逻辑。

2.实现代码

#include <stdio.h>int int_cmp(const void * p1, const void * p2)
{return (*( int *)p1 - *(int *) p2);
}void _swap(void *p1, void * p2, int size)
{int i = 0;for (i = 0; i< size; i++){char tmp = *((char *)p1 + i);*(( char *)p1 + i) = *((char *) p2 + i);*(( char *)p2 + i) = tmp;}
}void bubble(void *base, int count , int size, int(*cmp )(void *, void *))
{int i = 0;int j = 0;for (i = 0; i< count - 1; i++){for (j = 0; j<count-i-1; j++){if (cmp ((char *) base + j*size , (char *)base + (j + 1)*size) > 0){_swap(( char *)base + j*size, (char *)base + (j + 1)*size, size);}
}int main()
{int arr[] = { 1, 3, 5, 7, 9, 2, 4, 6, 8, 0 };int i = 0;bubble(arr, sizeof(arr) / sizeof(arr[0]), sizeof (int), int_cmp);for (i = 0; i< sizeof(arr) / sizeof(arr[0]); i++){printf( "%d ", arr[i]);}printf("\n");return 0;
}

3.模拟详解

        在讲述模拟实现之前,先进行qsort函数的讲解。在qsort函数中,为了能够对任意类型的数据进行排序。所以使用了void*类型。因为void*类型可以接收任何类型数据的指针,实现了泛型编程。qsort函数的第四个参数是返回值为int类型的函数指针类型。原因是在程序员使用qsort函数时,只需要调用qsort函数,并为qsort函数写一个比较函数。返回正数、负数或者0,用于表示函数两个参数之间的大小关系,从而在需要时进行交换两元素。

        需要知道的是,qsort函数为编译器程序员写的函数,他不知道你需要排序什么类型的数据,所以在函数参数中采用了void *类型接收,并且还需要我们自己写出一个判断大小的函数。在此判断大小的函数中,因为我们知道待排序元素的类型,所以这样就通过函数指针把我们写的判断大小的函数和编译器程序员写的qsort函数联系起来了,在使用期间,我们只需要将自己写的判断大小函数指针传给qsort函数作为其第四个参数即可完成数据的排序。

        在上述qsort函数的模拟实现中,我们用了冒泡排序的算法对任意数据进行排序。在模拟实现时,采用与官方内容相同的参数,base用来指向待排序数组的第一个元素的地址;count用于接收数组元素个数;width用于接收数组元素的大小(单位是字节);cmp时函数指针类型,用回调函数的逻辑进行调用自己书写的int_cmp函数,比较了两元素的大小,运用if语句进行判断是否需要交换位置,需要的话调用_swap函数,不需要的话,则完成一轮冒泡排序。当完成冒泡排序的所有趟数时,排序结束。

http://www.xdnf.cn/news/17663.html

相关文章:

  • 【接口自动化】初识pytest,一文讲解pytest的安装,识别规则以及配置文件的使用
  • Jotai:React轻量级状态管理新选择
  • Code Exercising Day 10 of “Code Ideas Record“:StackQueue part02
  • SQL三剑客:DELETE、TRUNCATE、DROP全解析
  • CentOS7.9 离线安装mysql数据库
  • CPP继承
  • Windows执行kubectl提示拒绝访问【Windows安装k8s】
  • `sk_buff` 结构体详解(包含全生命周期解析)
  • 数学建模:控制预测类问题
  • 全面了解机器语言之kmeans
  • 010601抓包工具及证书安装-基础入门-网络安全
  • 【Matplotlib】中文显示问题
  • 企业级WEB应用服务器TOMCAT — WEB技术详细部署
  • 正点原子esp32s3探测土壤湿度
  • openpnp - 顶部相机如果超过6.5米影响通讯质量,可以加USB3.0信号放大器延长线
  • Effective C++ 条款34:区分接口继承和实现继承
  • 数据库面试题集
  • DFT的几点理解(二)
  • 计算二分类误差时的常见错误及解决方案
  • 农经权二轮延包—已有软件与后续研究
  • Spring之【详解AOP】
  • NLP 2025全景指南:从分词到128专家MoE模型,手撕BERT情感分析实战(第四章)
  • scanpy单细胞转录组python教程(三):单样本数据分析之数据标准化、特征选择、细胞周期计算、回归等
  • 制动电阻烧损记录学习
  • Spark执行计划与UI分析
  • JVM调优好用的内存分析工具!
  • jvm有哪些垃圾回收器,实际中如何选择?
  • 工业相机选择规则
  • leetcode经典题目——单调栈
  • 机器学习第八课之K-means聚类算法