目录
模拟计算器案例
编辑
1、回调函数
2、转移表
冒泡排序
strcmp函数
编辑
qsort函数
测试qsort函数排序整型数据
***通过qsort实现冒泡排序***(重点)
(为什么要用强制转换:因为void*类型是方便输入的数据为任意类型,进入后不是int型无法计算,强制类型转换后才可以进行运算)
测试qsort函数排序结构体数据
初识指针(笔记)-CSDN博客
指针详解(笔记)-CSDN博客
指针详解(二)-CSDN博客
//使用回调函数改造前
/*
实现一个计算器
这个计算器可以实现整数的加减乘除
*/
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、定义回调函数,该函数接受两个整数参数并返回一个整数。
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;
}
转移表改造思路:
转移表是一种用于实现多路复用的数据结构,可以用来实现复杂的菜单驱动程序。使用转移表可以将用户输入的操作符映射到相应的操作上。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,表示无序,则继续下一趟冒泡排序。这样,就可以节省时间
#include
#include
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 quick sort
qsort 是库函数,这个函数可以完成任意类型的排序
1.qsort确实可以排序任意的数据类型
2.使用的时候,需要使用者传递一个函数的地址,
这个函数用来比较待排序数组中的两元素
正常使用冒泡排序
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;
}
}
}
}
void bubbleSort(int arr[], int sz)
解析在冒泡中,以下为用模拟qsort的解析
此为模拟void bubbleSort(int arr[], int sz)的函数
执行以下模拟冒泡的语句
每两个元素依次进入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;
}
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也指向一个元素
);
如果你感觉上述的代码对你有帮助,可以给我点个赞吗?
创作不易,谢谢各位的点赞,咱们下期见!