转移表和回调函数的使用(袖珍计算器的编写 / 模仿qsort的功能实现一个通用的冒泡排序)

本文主要是介绍转移表和回调函数的使用(袖珍计算器的编写 / 模仿qsort的功能实现一个通用的冒泡排序),希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

在上一篇已经对函数指针、函数指针的数组、函数指针数组的指针加以分析,那么如此繁多的 “指针/数组” 如何使用?
最常见的两个用途就是转换表和作为参数传递给另一个函数(回调函数)

转换表

我们取一段实现袖珍式计算器的程序代码:

switch(input);
case ADDresult=add(x,y);break;
case SUB:result=sub(x,y);break;
case MUL:result=mul(x,y);break;
case DIVresult=div(x,y);break;

我们可以看到,我们进行多少种运算,就需要写多少个case语句分支。对于一个新奇的具有上百个操作符的计算器,这条switch语句将会很长。

为了使用switch语句,表示操作符的代码必须是整数。如果它们是从零开始连续的整数,我们可以使用转换表来实现相同的任务。转换表就是一个函数指针数组。
创建一个转换表需要两个步骤:
首先,声明并初始化一个函数指针数组。唯一需要留心之处就是确保这些函数的原型出现在这个数组是声明之前。
double add(double,double);
double sub(double,double);
double mul(double,double);
double div(double,double);

...
double (*pfun[](double double)={ADD,SUB,MUL,DIV,...};
初始化列表中各个函数名的正确顺序取决于程序中用于表示每个操作符的整型代码。
第二个步骤是用下面这条语句替换前面整条switch语句。
ret=pfun[input](x,y)
input从数组中选择正确的函数指针,而函数调用操作符将执行这个函数。

下面就是用转移表的方法实现袖珍计算器

#define _CRT_SECURE_NO_WARNINGS 1
#include <stdio.h>
#include <stdlib.h>
#include <assert.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;
}
void calc(int(*pfun)(int ,int))
//函数的地址传参,使用函数指针,该指针指向的函数有两个整型参数,返回类型为int
{int x = 0;int y = 0;int ret = 0;printf("请输入两个数:\n");assert(2==scanf("%d%d", &x, &y));ret = pfun(x, y);printf("ret=%d\n", ret);
}
int main()
{int(*pfun[5])(int, int) = { 0, Add,Sub,Mul,Div};
//函数指针的数组(作用相当于switch语句)---转移表
//指向+、-、*、/这样的函数,参数为两个整型,返回类型为整型,数组第一个元素地址的下标为0,联系整个代码Add定义的为1,为了更好使用,所以将第一个空过去(设置为0)int choose = 0;do{menu();printf("请选择:");assert(1 == scanf("%d", &choose));if (choose >= 1 && choose <= 4)calc(pfun[choose]);//函数地址传参,将使用的函数else if (choose == 0){printf("退出程序\n");break;}elseprintf("非法输入!\n");} while (choose);system("pause");return 0;
}


简单的来说,转移表就是将存在多个同类型的函数放入函数指针数组中,达到简化了函数调用的代码量,从而优化程序。
注意:在转换表中,越界下标引用就像在其他任何数组中一样是不合法的。但一旦出现这种情况,把它诊断出来要困难的多。所以,在使用转移表时,一定要保证转移表所使用的下标位于合法的范围内。

回调函数

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

我们无法再上下文环境中为回调函数编写一个准确的原型,因为我们并不知道进行比较的值的类型。事实上,我们需要查找函数能作用于任何类型的值。解决这个难题的方法就是把参数类型声明为 void * ,表示“一个指向未知类型的指针”。

在这里我们使用回调函数模拟实现qsort(采用冒泡的方式)给大家讲解
首先给大家介绍一下什么是qsort函数,怎么使用!

qsort函数

  • 编译器函数库自带的快速排序函数

  • qsort 的函数原型是

void qsort(void*base,size_t num,size_t width,int(__cdecl*compare)(const void*,const void*));
各参数:1 待排序数组首地址 2 数组中待排序元素数量 3 各元素的占用空间大小 4 指向函数的指针
其中compare( (void *) & elem1, (void *) & elem2 )就是我们调用方自己要编写实现的函数,返回要求是

  • 用qsort实现整型排序
int cmp_int(const void*el, const void*el)//整型比较回调函数
{return (*(int*)el) - (*(int*)el);
}
void print_int(int arr[],int sz)//打印数组
{int i = 0;for (i = 0; i < sz; i++){printf("%d ", arr[i]);}printf("\n");
}
int main()
{int arr[] = { 1,3,5,7,9,2,4,6,8,0 };int sz = sizeof(arr)/sizeof(*arr);qsort(arr, sz, sizeof(arr[0]), cmp_int);print_int(arr, sz);system("pause");return 0;
}

  • 用qsort实现结构体排序
    创建一个存储姓名和年龄的结构体,分别通过姓名或者年龄来进行排序
    • 通过姓名
      需要实现结构体中字符串的排序,恰好strcmp不仅可以比较字符串字符之间的大小,恰好返回值是int型。
struct STU
{char name[20];int age;
};void print_stu(struct st arr[],int sz)//打印{int i = 0;for (i = 0; i < sz; i++){printf("name=%s age=%d\n", arr[i].name,arr[i].age);}printf("\n");}
int cmp_stu(const void*el, const void*el)// 用于结构体比较的回调函数
{return strcmp(((struct stu*)el)->name, ((struct stu*)el)->name);
}
int main()
{struct STU stu[3] = { {"张三",20},{"李四",30},{"王五"40 };int sz = sizeof(stu) / sizeof(stu[0]);qsort(stu, sz, sizeof(stu[0]), cmp_stu);print_stu(stu, sz);system("pasue");return 0;
}

  • 用年龄排序
    只需要将回调函数的name改成age的比较即可
int cmp_stu(const void*el, const void*el)// 用于结构体比较的回调函数
{return (((struct stu*)el)->age)-(((struct stu*)el)->age);
}

使用回调函数模拟实现qsort

思路:

  • 因为传入bubble_sort函数的指针是void*类型,为了能够适应各种类型,必须将传来得指针强制类型转换成(char*)类型,从而就可以利用目标元素得类型来准确判断每个元素所占空间,从而进行排序工作。

  • 其次就是自编比较回调函数的代码段。其中cmp(参数1,参数2),传参是传的比较两个数的地址,将base转换成char*,一次比较一个字节,下一个元素为其地址加上其宽度,即 * (char *)base+width

  • 对于交换,因为每个类型最小字节数是1 ,所以可以无差别得通过宽度进行每个字节得交换,在本设计中,我们把交换过程封装成Swap()函数来进行交换

#define _CRT_SECURE_NO_WARNINGS 1
#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
#include <string.h>
struct STU
{char name[20];int age;
};
int cmp_int(const void * e1, const void * e2)
{assert(e1 && e2);return *(int *)e1 - *(int *)e2;
}
int cmp_stu(const void * e1, const void * e2)
{assert(e1 && e2);return strcmp(((struct STU *)e1)->name, ((struct STU *)e2)->name);
}
int cmp_stu_age(const void * e1, const void * e2)
{assert(e1 && e2);return (*(struct STU *)e1).age - (*(struct STU *)e2).age;
}
void print_int(int arr[], int sz)
{int i = 0;for (i = 0; i < sz; i++){printf("%d ", arr[i]);}printf("\n");
}
void print_stu(struct STU stu[], int sz)
{int i = 0;for (i = 0; i < sz; i++){printf("name=%s age=%d\n", stu[i].name,stu[i].age);}printf("\n");
}
void Swap(char * buf1, char * buf2, int width)//交换两个数
{int i = 0;assert(buf1 && buf2);for (i = 0; i < width; i++){char tmp = *buf1;*buf1 = *buf2;*buf2 = tmp;buf1++;buf2++;}
}
void bubble_sort(void *base, int sz, int width, int (*cmp)(const void *e1, const void *e2))//冒泡排序
{int i = 0;int j = 0;assert(base && cmp);for (i = 0; i < sz - 1; i++){for (j = 0; j < sz - 1 - i; j++){if (cmp((char *)base + width*j, (char *)base + width*(j + 1))>0)Swap((char *)base + width*j, (char *)base + width*(j + 1), width);}}
}
int main()
{int arr[] = { 1, 3, 5, 7, 9, 2, 4, 6, 8, 0 };struct STU stu[3] = { { "张三", 20 }, { "李四", 30 }, { "王五", 40 } };int sz1 = sizeof(arr) / sizeof(arr[0]);int sz2 = sizeof(stu) / sizeof(stu[0]);//bubble_sort(arr, sz1, sizeof(arr[0]), cmp_int);//排序整型bubble_sort(stu, sz2, sizeof(stu[0]), cmp_stu);//排序结构体(名字)bubble_sort(stu, sz2, sizeof(stu[0]), cmp_stu_age);//排序结构体(年龄)//print_int(arr, sz1);//打印整型print_stu(stu, sz2);//打印结构体system("pause");return 0;
}




注意:
在使用比较函数中的指针之前,它们必须被强制转换成正确的类型。因为强制类型转换能够躲过一般的类型检查。

这篇关于转移表和回调函数的使用(袖珍计算器的编写 / 模仿qsort的功能实现一个通用的冒泡排序)的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



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

相关文章

使用Java解析JSON数据并提取特定字段的实现步骤(以提取mailNo为例)

《使用Java解析JSON数据并提取特定字段的实现步骤(以提取mailNo为例)》在现代软件开发中,处理JSON数据是一项非常常见的任务,无论是从API接口获取数据,还是将数据存储为JSON格式,解析... 目录1. 背景介绍1.1 jsON简介1.2 实际案例2. 准备工作2.1 环境搭建2.1.1 添加

Oracle的to_date()函数详解

《Oracle的to_date()函数详解》Oracle的to_date()函数用于日期格式转换,需要注意Oracle中不区分大小写的MM和mm格式代码,应使用mi代替分钟,此外,Oracle还支持毫... 目录oracle的to_date()函数一.在使用Oracle的to_date函数来做日期转换二.日

Java实现任务管理器性能网络监控数据的方法详解

《Java实现任务管理器性能网络监控数据的方法详解》在现代操作系统中,任务管理器是一个非常重要的工具,用于监控和管理计算机的运行状态,包括CPU使用率、内存占用等,对于开发者和系统管理员来说,了解这些... 目录引言一、背景知识二、准备工作1. Maven依赖2. Gradle依赖三、代码实现四、代码详解五

java如何分布式锁实现和选型

《java如何分布式锁实现和选型》文章介绍了分布式锁的重要性以及在分布式系统中常见的问题和需求,它详细阐述了如何使用分布式锁来确保数据的一致性和系统的高可用性,文章还提供了基于数据库、Redis和Zo... 目录引言:分布式锁的重要性与分布式系统中的常见问题和需求分布式锁的重要性分布式系统中常见的问题和需求

SpringBoot基于MyBatis-Plus实现Lambda Query查询的示例代码

《SpringBoot基于MyBatis-Plus实现LambdaQuery查询的示例代码》MyBatis-Plus是MyBatis的增强工具,简化了数据库操作,并提高了开发效率,它提供了多种查询方... 目录引言基础环境配置依赖配置(Maven)application.yml 配置表结构设计demo_st

如何使用celery进行异步处理和定时任务(django)

《如何使用celery进行异步处理和定时任务(django)》文章介绍了Celery的基本概念、安装方法、如何使用Celery进行异步任务处理以及如何设置定时任务,通过Celery,可以在Web应用中... 目录一、celery的作用二、安装celery三、使用celery 异步执行任务四、使用celery

使用Python绘制蛇年春节祝福艺术图

《使用Python绘制蛇年春节祝福艺术图》:本文主要介绍如何使用Python的Matplotlib库绘制一幅富有创意的“蛇年有福”艺术图,这幅图结合了数字,蛇形,花朵等装饰,需要的可以参考下... 目录1. 绘图的基本概念2. 准备工作3. 实现代码解析3.1 设置绘图画布3.2 绘制数字“2025”3.3

Jsoncpp的安装与使用方式

《Jsoncpp的安装与使用方式》JsonCpp是一个用于解析和生成JSON数据的C++库,它支持解析JSON文件或字符串到C++对象,以及将C++对象序列化回JSON格式,安装JsonCpp可以通过... 目录安装jsoncppJsoncpp的使用Value类构造函数检测保存的数据类型提取数据对json数

python使用watchdog实现文件资源监控

《python使用watchdog实现文件资源监控》watchdog支持跨平台文件资源监控,可以检测指定文件夹下文件及文件夹变动,下面我们来看看Python如何使用watchdog实现文件资源监控吧... python文件监控库watchdogs简介随着Python在各种应用领域中的广泛使用,其生态环境也

el-select下拉选择缓存的实现

《el-select下拉选择缓存的实现》本文主要介绍了在使用el-select实现下拉选择缓存时遇到的问题及解决方案,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的... 目录项目场景:问题描述解决方案:项目场景:从左侧列表中选取字段填入右侧下拉多选框,用户可以对右侧