爆肝!回调函数的实用案例,建议收藏~(计算器改良,qsort快排函数应用实例,冒泡函数核心理解,模拟qsort函数)

本文主要是介绍爆肝!回调函数的实用案例,建议收藏~(计算器改良,qsort快排函数应用实例,冒泡函数核心理解,模拟qsort函数),希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

这里是目录哟~

    • 一、回调函数的实用案例
      • 1.0 回调函数简要介绍
      • 1.1 简易计算器改良
      • 1.2 qsort快排函数应用回调函数
        • 1.2.1 冒泡排序核心思想
        • 1.2.2 qsort函数简单介绍
          • 1.2.2.1 qsort函数对浮点型数组排序实例
          • 1.2.2.2 qsort函数对结构体数组排序实例
        • 1.2.3 利用冒泡排序算法实现qsort函数的模拟

一、回调函数的实用案例

1.0 回调函数简要介绍

回调函数是一个通过函数指针调用的函数。
意思是定义一个指针,这个指针指向一个函数,然后用这个指针来调用这个指针所指向的函数。
看上去十分容易,那只是你的脑子在欺骗你,你的手完全不会。[doge]

1.1 简易计算器改良

大多数初学者刚接触函数的时候一定写过简易的计算器,来实现加减乘除四则运算 , 虽然写得很繁琐 ,但依旧无法阻止我们产生学习编程的热情。

#include<stdio.h>
void menu()
{printf("*********************\n");printf("****1.Add   2.Sub****\n");printf("****3.Mul   4.Div****\n");printf("****** 0.Exit *******\n");printf("*********************\n");
}
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;
}
int main()
{int input = 0;int x = 0;int y = 0;int ret = 0;do{menu();scanf("%d", &input);switch (input){case 1:printf("input two numbers\n");scanf("%d %d", &x, &y);ret = Add(x, y);printf("%d\n", ret);break;case 2:printf("input two numbers\n");scanf("%d %d", &x, &y);ret = Sub(x, y);printf("%d\n", ret);break;case 3:printf("input two numbers\n");scanf("%d %d", &x, &y);ret = Mul(x, y);printf("%d\n", ret);break;case 4:printf("input two numbers\n");scanf("%d %d", &x, &y);ret = Div(x, y);printf("%d\n", ret);break;case 0:printf("Exit\n");break;default:printf("再乱输我就要生气咯!\n");break;}} while (input);return 0;
}

但这样冗余的代码就像老太太的裹脚布又臭又长 。
我在写的时候发现了有多个地方一直在重复一个操作,就是在case子句中一直重复着让让用户输入两个操作数,并且打印出来。
(虽然这个操作可以复制粘贴 )但是节约是一种美德,节约内存空间也是[doge]

  • 懒,才能推动进步
    我要尝试简化这个部分的代码,我发现case子句中只有函数的调用不同,所以我将不同函数作为一个参数传给我的一个自定义函数,于是case子句中的内容就变为如下形式
ret = Calc(Add);
printf("%d\n", ret);

那么问题来了,函数名代表什么呢?
众所周知,数组名代表数组的首元素地址,那函数名也应该如此,代表函数的地址
那么,我们的自定义函数中的参数应该设计成一个函数指针

int Cal((*fun)(int, int));

这么长一串的东西应该怎么来分析呢?
首先看参数部分,参数是一个函数指针,即一个指针,所以其变量名应该先于‘ * ’结合,并且被括号括起来,因为‘ * ’的优先级特别低。
然后其右边与()结合,代表这是个指向函数的指针,这个()是参数列表,列表内有两个整形参数。
最后再看返回值,实行计算后返回一个整形数据。

简易改良版如下:

#include<stdio.h>
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");
}
int Cal(int(*fun)(int, int))
{int x = 0;int y = 0;printf("input two numbers\n");scanf("%d %d", &x, &y);return fun(x, y);
}
int main()
{int input = 0;int x = 0;int y = 0;int ret = 0;do{menu();scanf("%d", &input);switch (input){case 1:ret = Cal(Add);printf("%d\n", ret);break;case 2:ret = Cal(Sub);printf("%d\n", ret);break;case 3:ret = Cal(Mul);printf("%d\n", ret);break;case 4:ret = Cal(Div);printf("%d\n", ret);break;case 0:printf("Exit\n");break;default:printf("再乱输我就要生气咯!\n");break;}} while (input);return 0;
}

1.2 qsort快排函数应用回调函数

众所周知,排序是很重要的,初学者一般就接触过选择排序和冒泡排序,其中冒泡排序估计是强行硬是把代码背下来了。
下面还是希望能用通俗易懂的图文带大家深入理解一下冒泡排序的核心思想。

1.2.1 冒泡排序核心思想

相邻两数比较,大数往后挪,就像是重的石头沉入水底,轻的气泡往上浮。
把每一次最重的石头沉入它应该到的位置时的这个过程称作一趟冒泡排序,那么多少趟下来才能将所有元素放在各自应该到的位置呢?很显然,如果10个元素,只需要把九个元素放在其应该所在的位置即可,那么剩下的一个元素当然只有唯一的位置放了,而那个位置恰好就是该剩下的元素的最终位置,10个元素完成升序也就需要9趟冒泡排序

再来细说每一趟冒泡排序是如何实现的,从第一个元素开始,相邻的两元素两两比较,如果前面元素大于后面元素,则两元素交换位置,如果前面元素小于后面元素,则不交换位置,一直比较到最后一个元素,这就是第一趟冒泡排序。
而紧接着下一趟冒泡排序略微有些不同,一开始还是依次两两比较,但是不需要比较数组中的最后一个元素,因为在第一趟的冒泡排序中,我们已经将一个最大的数放在最后了,所以它已经到自己最终的位置,我们就不再管它了。

在这里插入图片描述

1.2.2 qsort函数简单介绍

qsort函数是C的一个库函数,头文件为#include<stdlib.h>
这里推荐一个程序员必备的库函数查询网址
链接: http://www.cplusplus.com/
可以查询C\C++的库函数的用法,非常方便,值得放进收藏夹。

如下是qsort函数的函数声明:
在这里插入图片描述
在这里插入图片描述
qsort函数的第四个参数是一个函数指针,指向的是一个程序员根据需要排序的数组类型来写的函数。

正因为如此,qsort才能实现对不同的数据类型进行灵活排序

1.2.2.1 qsort函数对浮点型数组排序实例
#include<stdio.h>
#include<stdlib.h>
int SortByDouble(const void* e1, const void* e2)
{return *(double*)e1 - *(double*)e2;
}
int main()
{double arr[] = { 9.0,8.0,7.0,6.0,5.0,4.0,3.0,2.0,1.0 };int sz = sizeof(arr) / sizeof(arr[0]);qsort(arr, sz, sizeof(arr[0]), SortByDouble);return 0;
}

注意:void*可以接受任何指针类型,但是不可以对其进行解引用操作,但是可以对其接受到的指针类型进行强制类型转换,即如上自定义函数SortByDouble中的return部分。

1.2.2.2 qsort函数对结构体数组排序实例
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
struct stu
{char name[8];int age;
};
int SortByAge(const void* e1, const void* e2)
{return ((struct stu*)e1)->age - ((struct stu*)e2)->age;
}
int SortByName(const void* e1, const void* e2)
{return strcmp(((struct stu*)e1)->name, ((struct stu*)e2)->name);
}int main()
{struct stu s[] = { {"zhangsan",20},{"lisi",19},{"wangwu",18} };int sz = sizeof(s) / sizeof(s[0]);qsort(s, sz, sizeof(s[0]), SortByAge);qsort(s, sz, sizeof(s[0]), SortByName);return 0;
}

结构体就更加复杂了,而且需要小心算结构体数组元素个数和每个元素大小的时候都是不管结构体的成员变量。
所以千万不要以为自己是在对结构体中的年龄进行排序,而计算数组大小的时候用了:

int sz = sizeof(s)/sizeof(s[0].age);//错误实例
不应该去让s[0]访问到age

还有一点是结构体定义一定要放在最前面,放错位置了,都会导致自定义函数显示未定义的错误

1.2.3 利用冒泡排序算法实现qsort函数的模拟

在这里插入图片描述

其实qsort函数的算法并不是冒泡排序,但我为什么说是模拟实现qsort呢?
请看下图:
在这里插入图片描述
发现了吗?我写的这个函数的参数简直就是和qsort函数的参数异曲同工之妙啊~
所以接下来我需要实现的就是对BubbleSort函数进行定义了。
如下是大体的框架:
在这里插入图片描述
比较难的是如何设置cmp函数和swap函数内的参数。
cmp函数接收的是两个相邻元素的地址,但是元素的类型可以有很多种,且所占字节大小也不同,这就为我们设下了一个难题。
但是我们知道char类型所占字节是最小的,我们是不是可以用它来表示所有变量呢?
所以先将首元素地址强制类型转换成char*,再对其加某个值,可以让其向后移动而指向不同的元素。
那么加上什么值才合适呢?
这是位于内部循环,j一直在发生变换,我们又知道每个元素所占字节大小,所以根据上述推论,我们有以下结果。

if(cmp((char*)base + j * size, (char*)base + (j + 1) * size)>0

接下来是swap函数内部的参数
swap函数是满足了前者大于后者,就对这两个数进行交换。
swap函数的参数应该是两个元素的地址,并且是和cmp()函数中的参数一样,所以有如下结果:

swap((char*)base + j * size, (char*)base + (j + 1) * size)

大体搭构好了,接下来就是实现Swap函数了
在这里插入图片描述
等一下,这样的Swap函数真的对吗?如果传过去的是两个元素的地址,我们真的能成功交换他们吗?
答案是否定的,因为我们平常写的交换两元素的函数是这样的

Swap(char* buf1, char* buf2)
{while((*buf1)&&(*buf2)){int tmp = *buf1;*buf1 = *buf2;*buf2 = tmp;buf1++;buf2++;}		
}

发现问题了吗?我们每次++的时候都已经确定这个指针会跳过几个字节,而现在我们写的Swap函数可并不知道自己参数的类型,所以不能保证每次++后跳过的字节都正确。
讲了半天,我们最需要的是知道我们交换的元素所占字节大小是多少
所以需要给Swap函数增加一个参数size
修改后:

BubbleSort(void* base, int count, int size, int (*cmp)(const void*, const void*))
{int i = 0;for (i = 0; i < count - 1; i++){int j = 0;for (j = 0; j < count - 1 - i; j++){if (cmp((char*)base + j * size, (char*)base + (j + 1) * size) > 0)//比较大小{Swap((char*)base + j * size, (char*)base + (j + 1) * size, size);//交换两元素}}}
}

Swap函数的实现

void Swap(void* buf1, void* buf2, int size)
{int i = 0;for (i = 0; i < size; i++)//每次循环交换两个元素的一个字节{char tmp = *((char*)buf1 + i);*((char*)buf1 + i) = *((char*)buf2 + i);*((char*)buf2 + i) = tmp;}
}

每次循环交换两个元素的一个字节。
直至把一个元素所占的字节全部交换完成。
这样我们就把整个qsort函数的模拟实现了!
最后附上源码:

#include<stdio.h>
#include<stdlib.h>
#include<string.h>
int SortByInt(const void* e1, const void* e2)
{return *(int*)e1 - *(int*)e2;
}void Swap(void* buf1, void* buf2, int size)
{int i = 0;for (i = 0; i < size; i++){char tmp = *((char*)buf1 + i);*((char*)buf1 + i) = *((char*)buf2 + i);*((char*)buf2 + i) = tmp;}
}
BubbleSort(void* base, int count, int size, int (*cmp)(const void*, const void*))
{int i = 0;for (i = 0; i < count - 1; i++){int j = 0;for (j = 0; j < count - 1 - i; 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[] = { 9,8,7,6,5,4,3,2,1,0 };int sz = sizeof(arr) / sizeof(arr[0]);BubbleSort(arr, sz, sizeof(arr[0]), SortByInt);return 0;
}

妹有开源精神是不行的![doge]

这篇关于爆肝!回调函数的实用案例,建议收藏~(计算器改良,qsort快排函数应用实例,冒泡函数核心理解,模拟qsort函数)的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/524099

相关文章

Python中re模块结合正则表达式的实际应用案例

《Python中re模块结合正则表达式的实际应用案例》Python中的re模块是用于处理正则表达式的强大工具,正则表达式是一种用来匹配字符串的模式,它可以在文本中搜索和匹配特定的字符串模式,这篇文章主... 目录前言re模块常用函数一、查看文本中是否包含 A 或 B 字符串二、替换多个关键词为统一格式三、提

MySQL count()聚合函数详解

《MySQLcount()聚合函数详解》MySQL中的COUNT()函数,它是SQL中最常用的聚合函数之一,用于计算表中符合特定条件的行数,本文给大家介绍MySQLcount()聚合函数,感兴趣的朋... 目录核心功能语法形式重要特性与行为如何选择使用哪种形式?总结深入剖析一下 mysql 中的 COUNT

Java MQTT实战应用

《JavaMQTT实战应用》本文详解MQTT协议,涵盖其发布/订阅机制、低功耗高效特性、三种服务质量等级(QoS0/1/2),以及客户端、代理、主题的核心概念,最后提供Linux部署教程、Sprin... 目录一、MQTT协议二、MQTT优点三、三种服务质量等级四、客户端、代理、主题1. 客户端(Clien

MySQL 中 ROW_NUMBER() 函数最佳实践

《MySQL中ROW_NUMBER()函数最佳实践》MySQL中ROW_NUMBER()函数,作为窗口函数为每行分配唯一连续序号,区别于RANK()和DENSE_RANK(),特别适合分页、去重... 目录mysql 中 ROW_NUMBER() 函数详解一、基础语法二、核心特点三、典型应用场景1. 数据分

java向微信服务号发送消息的完整步骤实例

《java向微信服务号发送消息的完整步骤实例》:本文主要介绍java向微信服务号发送消息的相关资料,包括申请测试号获取appID/appsecret、关注公众号获取openID、配置消息模板及代码... 目录步骤1. 申请测试系统2. 公众号账号信息3. 关注测试号二维码4. 消息模板接口5. Java测试

MySQL数据库的内嵌函数和联合查询实例代码

《MySQL数据库的内嵌函数和联合查询实例代码》联合查询是一种将多个查询结果组合在一起的方法,通常使用UNION、UNIONALL、INTERSECT和EXCEPT关键字,下面:本文主要介绍MyS... 目录一.数据库的内嵌函数1.1聚合函数COUNT([DISTINCT] expr)SUM([DISTIN

Python get()函数用法案例详解

《Pythonget()函数用法案例详解》在Python中,get()是字典(dict)类型的内置方法,用于安全地获取字典中指定键对应的值,它的核心作用是避免因访问不存在的键而引发KeyError错... 目录简介基本语法一、用法二、案例:安全访问未知键三、案例:配置参数默认值简介python是一种高级编

python 常见数学公式函数使用详解(最新推荐)

《python常见数学公式函数使用详解(最新推荐)》文章介绍了Python的数学计算工具,涵盖内置函数、math/cmath标准库及numpy/scipy/sympy第三方库,支持从基础算术到复杂数... 目录python 数学公式与函数大全1. 基本数学运算1.1 算术运算1.2 分数与小数2. 数学函数

MySQL中的索引结构和分类实战案例详解

《MySQL中的索引结构和分类实战案例详解》本文详解MySQL索引结构与分类,涵盖B树、B+树、哈希及全文索引,分析其原理与优劣势,并结合实战案例探讨创建、管理及优化技巧,助力提升查询性能,感兴趣的朋... 目录一、索引概述1.1 索引的定义与作用1.2 索引的基本原理二、索引结构详解2.1 B树索引2.2

从入门到精通MySQL 数据库索引(实战案例)

《从入门到精通MySQL数据库索引(实战案例)》索引是数据库的目录,提升查询速度,主要类型包括BTree、Hash、全文、空间索引,需根据场景选择,建议用于高频查询、关联字段、排序等,避免重复率高或... 目录一、索引是什么?能干嘛?核心作用:二、索引的 4 种主要类型(附通俗例子)1. BTree 索引(