模拟计算器案例
//使用回调函数改造前 /* 实现一个计算器 这个计算器可以实现整数的加减乘除 */ 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) { if (y == 0) { printf("除数不能为0\n"); return -1; } else { return x / y; } } void menu() { printf("*************************\n"); printf("**** 1.add 3.sub ****\n"); printf("**** 3.mul 4.div ****\n"); printf("**** 0.exit ****\n"); printf("*************************\n"); } int main() { int x, y; int input = 1; int ret = 0; do { menu(); 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); return 0; }
这是一个基本的计算器程序,可以实现整数的加减乘除和退出功能。程序使用了一个简单的菜单驱动方式,让用户可以通过输入数字来选择要执行的操作。但是有大量的代码复用,在当前的代码中,加、减、乘、除的操作都是类似的,但是代码却是重复的。如果能够将这些操作封装到一个函数中,并通过参数来区分不同的操作,那么代码就会更加简洁和易于维护。
解决这些问题的思路如下:
1、使用转移表
2、使用回调函数
1、回调函数
回调函数是什么?
回调函数就是一个通过函数指针调用的函数。
如果你把函数的指针(地址)作为参数传递给另一个函数,当这个指针被用来调用其所指向的函数时,被调用的函数就是回调函数。回调函数不是由该函数的实现方直接调用,而是在
特定的事件或条件发生时由另外的一方调用的,用于对该事件或条件进行响应。
相同(相似)的代码出现了多份,就显得有些冗余,有没有办法,简化一些呢?
我们可以把调用的函数的地址以参数的形式传递过去,使用函数指针接收,函数指针指向什么函数就调用什么函数,这里其实使用的就是回调函数的功能。回调函数改造思路:
1、定义回调函数,该函数接受两个整数参数并返回一个整数。
2、在主函数中,创建一个数组,其中包含所有可能的操作符和对应的回调函数。
3、根据用户输入的操作符,查找相应的回调函数并调用它。
4、将结果存储在一个变量中,并将其打印出来。
1、先定义一个函数calc,这个函数接受一个函数指针pf作为参数。
2、在calc函数内部,首先定义了三个整数变量:x、y和ret。
3、然后,程序会输出"请输入两个操作数:",并使用scanf函数从用户处获取两个整数输入,分别赋值给x和y。
4、接着,使用函数指针pf调用函数,并将x和y作为参数传递。函数的返回值被赋值给ret。
5、最后,程序会输出这个返回值。
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) { if (y == 0) { printf("除数不能为0\n"); return -1; } else { return x / y; } } void menu() { printf("*************************\n"); printf("**** 1.add 3.sub ****\n"); printf("**** 3.mul 4.div ****\n"); printf("**** 0.exit ****\n"); printf("*************************\n"); } void calc(int(*pf)(int, int)) //通过函数指针调用函数 //把函数的指针(地址)作为参数传递给另一个函数,当这个指针被用来调用其所指向的函数时, //被调用的函数就是回调函数 { int x = 0, y = 0, ret = 0; printf("请输入两个操作数:"); scanf("%d %d", &x, &y); ret = pf(x, y); printf("%d\n", ret); } int main() { int input = 0; int x = 0; int y = 0; int ret = 0; do { menu(); //用回调函数的方法解决switch太长的问题 printf("请选择:"); scanf("%d", &input); switch (input) { case 1: calc(Add); break; case 2: calc(Sub); break; case 3: calc(Mul); break; case 4: calc(Div); break; case 0: printf("退出计算器\n"); break; default: printf("选择错误,请重新选择\n"); break; } } while (input); return 0; }
2、转移表
转移表改造思路:
转移表是一种用于实现多路复用的数据结构,可以用来实现复杂的菜单驱动程序。使用转移表可以将用户输入的操作符映射到相应的操作上。1、创建一个转移表,该表以操作符为键,以对应的操作函数为值。
2、在主函数中,使用scanf()函数读取用户输入的操作符。
3、使用转移表查找相应的操作函数,并将其调用。
4、将结果存储在一个变量中,并将其打印出来。为什么要用NULL?
因为选项中0是exit,所以不能把Add放在第一个
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) { if (y == 0) { printf("除数不能为0\n"); return -1; } else { return x / y; } } void menu() { printf("*************************\n"); printf("**** 1.add 3.sub ****\n"); printf("**** 3.mul 4.div ****\n"); printf("**** 0.exit ****\n"); printf("*************************\n"); } int main() { int input = 0; int x = 0; int y = 0; int ret = 0; do { menu(); //函数指针数组的方法解决switch太长的问题 //这里的函数指针数组,被称为转移表 int (*pfArr[])(int, int) = { NULL,Add,Sub,Mul,Div }; // 0 1 2 3 printf("请选择:"); scanf("%d", &input); if (input == 0) { printf("退出计算器\n"); } else if (input >= 1 && input <= 4) { printf("请输入两个操作数:"); scanf("%d %d", &x, &y); ret = pfArr[input](x, y); printf("%d\n", ret); } else { printf("选择错误,重新选择\n"); } } while (input); return 0; }
冒泡排序
先解析一下void bubbleSort(int arr[], int sz)
冒泡排序的核心思想就是:两两相邻的元素进行比较先写一个基本框架再实现函数定义部分 ,先外层循环确定趟数,再内层循环确定每趟交换的对数,最后判断相邻元素大小,如果不满足顺序就交换
void bubble_sort(int* arr, int sz) { //趟数 int i = 0, j = 0; for (i = 0; i < sz - 1; i++) { //一趟冒泡排序的过程 //两两元素相邻比较 for (j = 0; j < sz - i - 1; j++) { if (arr[j] > arr[j + 1]) { int t = arr[j]; arr[j] = arr[j + 1]; arr[j+1] = t; } } } } void print(int* arr, int sz) { int i = 0; for (i = 0; i < sz; i++) { printf("%d ", arr[i]); } printf("\n"); } int main() { //将一组整数排序为升序 int arr[10] = { 3,1,2,4,5,6,7,8,9,10 }; int sz = sizeof(arr) / sizeof(arr[0]); bubble_sort(arr, sz); print(arr, sz); return 0; }
但是上述代码还可以再进行优化,试想一下,如果要排序的数组是
9,0,1,2,3,4,5,6,7,8 第一趟排序完便已经升序了 ,但是还在不停的循环。所以,我们可以这样优化。
加入flag变量,表示数组当前是否有序。而判断有序的方法,则是如果一趟冒泡排序下来,没有一对交换,则证明有序。 反之,如果有交换,则flag置为0,表示无序,则继续下一趟冒泡排序。这样,就可以节省时间
strcmp函数
#include <stdio.h> #include <string.h> int main () { char str1[15]; char str2[15]; int ret; strcpy(str1, "abcdef"); strcpy(str2, "ABCDEF"); ret = strcmp(str1, str2); if(ret < 0) { printf("str1 小于 str2"); } else if(ret > 0) { printf("str1 大于 str2"); } else { printf("str1 等于 str2"); } return(0); }
特别注意:strcmp(const char *s1,const char * s2) 这里面只能比较字符串,即可用于比较两个字符串常量,或比较数组和字符串常量,不能比较数字等其他形式的参数。
ANSI 标准规定,返回值为正数,负数,0 。而确切数值是依赖不同的C实现的。
当两个字符串不相等时,C 标准没有规定返回值会是 1 或 -1,只规定了正数和负数。
有些会把两个字符的 ASCII 码之差作为比较结果由函数值返回。
qsort函数
函数调用的使用:qsort quick sort
qsort 是库函数,这个函数可以完成任意类型的排序
1.qsort确实可以排序任意的数据类型
2.使用的时候,需要使用者传递一个函数的地址,
这个函数用来比较待排序数组中的两元素
测试qsort函数排序整型数据
正常使用冒泡排序
void bubbleSort(int arr[], int sz) { int i = 0; //趟 for (i = 0; i < sz - 1; i++) { //每一趟冒泡排序的过程 int j = 0; for (j = 0; j < sz - 1 - i; j++) { if (arr[j] > arr[j + 1]) { int t = arr[j]; arr[j] = arr[j + 1]; arr[j + 1] = t; } } } }
***通过qsort实现冒泡排序***(重点)
void bubbleSort(int arr[], int sz)
解析在冒泡中,以下为用模拟qsort的解析
此为模拟void bubbleSort(int arr[], int sz)的函数
arr进入void bubbleSort2函数后
执行以下模拟冒泡的语句
每两个元素依次进入cmp进行比较
(为什么要用强制转换:因为void*类型是方便输入的数据为任意类型,进入后不是int型无法计算,强制类型转换后才可以进行运算)
返回值大于0执行Swap交换语句
交换后继续循环判断,直到结束
int cmp_int(const void*p1,const void* p2) { return *(int*)p1 - *(int*)p2; } void print_arr(int arr[], int sz) { int i = 0; for (i = 0; i < sz; i++) { printf("%d ", arr[i]); } printf("\n"); } void Swap(char* buf1, char* buf2, unsigned int width) { int i = 0; for (i = 0; i < width; i++)//一个一个字节交换 { char tmp = *buf1; *buf1 = *buf2; *buf2 = tmp; buf1++; buf2++; } } void bubbleSort2(void* base, unsigned int sz, unsigned int width, int (*cmp)(const void* p1,const void* p2)) /* void* base - 这是要排序的内存块的起始地址。 unsigned int sz - 这是内存块的大小,以字节为单位。 unsigned int width - 这是每个元素的大小,以字节为单位。 int (*cmp)(const void* p1, const void* p2) - 这个函数应该返回一个整数,表示两个元素的相对顺序。 如果第一个元素应该排在第二个元素之前,那么这个函数应该返回负数。如果两个元素相等, 那么这个函数应该返回0。如果第一个元素应该排在第二个元素之后,那么这个函数应该返回正数。 */ { int i = 0; //趟 for (i = 0; i < sz - 1; i++) { //每一趟冒泡排序的过程 int j = 0; for (j = 0; j < sz - 1 - i; j++) { //if (arr[j] > arr[j + 1]) if (cmp((char*)base + j * width, (char*)base + (j + 1) * width) > 0) //这里为什么要用char* //如果用int*,则需要跳过24/4个字节,才能表示为一个结构体元素 //如果要计算结构体的大小,转换为char*最好算 { /*int t = arr[j]; arr[j] = arr[j + 1]; arr[j + 1] = t;*/ //交换 Swap((char*)base + j * width, (char*)base + (j + 1) * width, width); } } } } void test3() { int arr[] = { 3,1,5,7,9,2,4,0,8,6 }; int sz = sizeof(arr) / sizeof(arr[0]); //设计并实现bubbleSort2(),这个函数能够排序任意类型的数据 bubbleSort2(arr, sz, sizeof(arr[0]), cmp_int); print_arr(arr, sz); } int main() { test3(); return 0; }
测试qsort函数排序结构体数据
int cmp_int(const void*p1,const void* p2) { return *(int*)p1 - *(int*)p2; } void print_arr(int arr[], int sz) { int i = 0; for (i = 0; i < sz; i++) { printf("%d ", arr[i]); } printf("\n"); } void Swap(char* buf1, char* buf2, unsigned int width) { int i = 0; for (i = 0; i < width; i++)//一个一个字节交换 { char tmp = *buf1; *buf1 = *buf2; *buf2 = tmp; buf1++; buf2++; } } void bubbleSort2(void* base, unsigned int sz, unsigned int width, int (*cmp)(const void* p1,const void* p2)) /* void* base - 这是要排序的内存块的起始地址。 unsigned int sz - 这是内存块的大小,以字节为单位。 unsigned int width - 这是每个元素的大小,以字节为单位。 int (*cmp)(const void* p1, const void* p2) - 这个函数应该返回一个整数,表示两个元素的相对顺序。 如果第一个元素应该排在第二个元素之前,那么这个函数应该返回负数。如果两个元素相等, 那么这个函数应该返回0。如果第一个元素应该排在第二个元素之后,那么这个函数应该返回正数。 */ { int i = 0; //趟 for (i = 0; i < sz - 1; i++) { //每一趟冒泡排序的过程 int j = 0; for (j = 0; j < sz - 1 - i; j++) { //if (arr[j] > arr[j + 1]) if (cmp((char*)base + j * width, (char*)base + (j + 1) * width) > 0) //这里为什么要用char* //如果用int*,则需要跳过24/4个字节,才能表示为一个结构体元素 //如果要计算结构体的大小,转换为char*最好算 { /*int t = arr[j]; arr[j] = arr[j + 1]; arr[j + 1] = t;*/ //交换 Swap((char*)base + j * width, (char*)base + (j + 1) * width, width); } } } } struct Stu { char name[20]; int age; }; int cmp_stu_by_name(const void* p1,const void* p2) { return strcmp(((struct Stu*)p1)->name, ((struct Stu*)p2)->name); } int cmp_stu_by_age(const void* p1, const void* p2) { return (((struct Stu*)p1)->age - ((struct Stu*)p2)->age); } void test4() { struct Stu arr[] = { {"zhangsan",18},{"list",35}, {"wangwu",15} }; int sz = sizeof(arr) / sizeof(arr[0]); //sizeof(arr[0]) //一个结构体元素24个字节 bubbleSort2(arr, sz, sizeof(arr[0]), cmp_stu_by_age); //打印arr数组的内容 int i = 0; for (i = 0; i < sz; i++) { printf("%s %d\n", arr[i].name, arr[i].age); } } int main() { test4(); return 0; }
这是按年龄排序的结果
这是按名字排序的结果
void qsort(
void* base,//base 指向了要排序的数组的第一个元素 (待排序数组的起始位置)
//qsort可能排序任意类型的数据,为了能够接收任意的可能的指针类型,设计成void*
size_t num,//base指向的数组中的元素个数(待排序的数组的元素个数)
size_t size,//base指向的数组中元素的大小(待排序的数组的元素大小,单位是字节)
int(*compar)(const void*p1, const void*p2)
//该函数指针指向的是一个函数
//指向的函数是用来比较待排序数组中的两个元素的
//函数的使用者提供一个函数
//函数指针 - 指针指向的函数是用来比较数组中的2个元素的
//p1指向一个元素,p2也指向一个元素
);
如果你感觉上述的代码对你有帮助,可以给我点个赞吗?
创作不易,谢谢各位的点赞,咱们下期见!