爆肝!回调函数的实用案例,建议收藏~(计算器改良,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

相关文章

中文分词jieba库的使用与实景应用(一)

知识星球:https://articles.zsxq.com/id_fxvgc803qmr2.html 目录 一.定义: 精确模式(默认模式): 全模式: 搜索引擎模式: paddle 模式(基于深度学习的分词模式): 二 自定义词典 三.文本解析   调整词出现的频率 四. 关键词提取 A. 基于TF-IDF算法的关键词提取 B. 基于TextRank算法的关键词提取

水位雨量在线监测系统概述及应用介绍

在当今社会,随着科技的飞速发展,各种智能监测系统已成为保障公共安全、促进资源管理和环境保护的重要工具。其中,水位雨量在线监测系统作为自然灾害预警、水资源管理及水利工程运行的关键技术,其重要性不言而喻。 一、水位雨量在线监测系统的基本原理 水位雨量在线监测系统主要由数据采集单元、数据传输网络、数据处理中心及用户终端四大部分构成,形成了一个完整的闭环系统。 数据采集单元:这是系统的“眼睛”,

Hadoop企业开发案例调优场景

需求 (1)需求:从1G数据中,统计每个单词出现次数。服务器3台,每台配置4G内存,4核CPU,4线程。 (2)需求分析: 1G / 128m = 8个MapTask;1个ReduceTask;1个mrAppMaster 平均每个节点运行10个 / 3台 ≈ 3个任务(4    3    3) HDFS参数调优 (1)修改:hadoop-env.sh export HDFS_NAMENOD

认识、理解、分类——acm之搜索

普通搜索方法有两种:1、广度优先搜索;2、深度优先搜索; 更多搜索方法: 3、双向广度优先搜索; 4、启发式搜索(包括A*算法等); 搜索通常会用到的知识点:状态压缩(位压缩,利用hash思想压缩)。

性能分析之MySQL索引实战案例

文章目录 一、前言二、准备三、MySQL索引优化四、MySQL 索引知识回顾五、总结 一、前言 在上一讲性能工具之 JProfiler 简单登录案例分析实战中已经发现SQL没有建立索引问题,本文将一起从代码层去分析为什么没有建立索引? 开源ERP项目地址:https://gitee.com/jishenghua/JSH_ERP 二、准备 打开IDEA找到登录请求资源路径位置

深入探索协同过滤:从原理到推荐模块案例

文章目录 前言一、协同过滤1. 基于用户的协同过滤(UserCF)2. 基于物品的协同过滤(ItemCF)3. 相似度计算方法 二、相似度计算方法1. 欧氏距离2. 皮尔逊相关系数3. 杰卡德相似系数4. 余弦相似度 三、推荐模块案例1.基于文章的协同过滤推荐功能2.基于用户的协同过滤推荐功能 前言     在信息过载的时代,推荐系统成为连接用户与内容的桥梁。本文聚焦于

csu 1446 Problem J Modified LCS (扩展欧几里得算法的简单应用)

这是一道扩展欧几里得算法的简单应用题,这题是在湖南多校训练赛中队友ac的一道题,在比赛之后请教了队友,然后自己把它a掉 这也是自己独自做扩展欧几里得算法的题目 题意:把题意转变下就变成了:求d1*x - d2*y = f2 - f1的解,很明显用exgcd来解 下面介绍一下exgcd的一些知识点:求ax + by = c的解 一、首先求ax + by = gcd(a,b)的解 这个

Andrej Karpathy最新采访:认知核心模型10亿参数就够了,AI会打破教育不公的僵局

夕小瑶科技说 原创  作者 | 海野 AI圈子的红人,AI大神Andrej Karpathy,曾是OpenAI联合创始人之一,特斯拉AI总监。上一次的动态是官宣创办一家名为 Eureka Labs 的人工智能+教育公司 ,宣布将长期致力于AI原生教育。 近日,Andrej Karpathy接受了No Priors(投资博客)的采访,与硅谷知名投资人 Sara Guo 和 Elad G

hdu1394(线段树点更新的应用)

题意:求一个序列经过一定的操作得到的序列的最小逆序数 这题会用到逆序数的一个性质,在0到n-1这些数字组成的乱序排列,将第一个数字A移到最后一位,得到的逆序数为res-a+(n-a-1) 知道上面的知识点后,可以用暴力来解 代码如下: #include<iostream>#include<algorithm>#include<cstring>#include<stack>#in

hdu1171(母函数或多重背包)

题意:把物品分成两份,使得价值最接近 可以用背包,或者是母函数来解,母函数(1 + x^v+x^2v+.....+x^num*v)(1 + x^v+x^2v+.....+x^num*v)(1 + x^v+x^2v+.....+x^num*v) 其中指数为价值,每一项的数目为(该物品数+1)个 代码如下: #include<iostream>#include<algorithm>