当前位置: 首页 > news >正文

系统性学习C语言-第十五讲-深入理解指针(5)

系统性学习C语言-第十五讲-深入理解指针(5)

  • 1. 回调函数是什么
    • 2. qsort 函数
    • 2.1 qsort 参数解析
      • 2.1.1 base 解析
      • 2.1.2 num 解析
      • 2.1.3 size 解析
      • 2.1.3 compar 解析
    • 2.2 qsort 函数使用举例
    • 2.3 qsort 函数排序结构体实例分析
    • 2.5 使用冒泡排序模拟实现 ` qsort ` 函数

1. 回调函数是什么

回调函数就是⼀个通过函数指针调⽤的函数。

如果你把函数的指针(地址)作为参数传递给另⼀个函数,当这个指针被⽤来调⽤其所指向的函数时,被调⽤的函数就是回调函数。

回调函数不是由该函数的实现⽅直接调⽤,⽽是在特定的事件或条件发⽣时由另外的⼀⽅调⽤的,⽤于对该事件或条件进⾏响应。

第13讲中我们写的计算机的实现的代码中,红⾊框中的代码是重复出现的,其中虽然执⾏计算的逻辑是区别的,

但是输⼊输出操作是冗余的,有没有办法,简化⼀些呢?

因为在修改过后的代码,只有调⽤函数的逻辑是有差异的,我们可以把调⽤的函数的地址以参数的形式传递过去,使⽤函数指针接收,

函数指针指向什么函数就调⽤什么函数,这⾥其实使⽤的就是回调函数的功能

未修改前的代码:

//使⽤回调函数改造前
#include <stdio.h>
int add(int a, int b)
{return a + b;
}
int sub(int a, int b)
{return a - b;
}
int mul(int a, int b)
{return a * b;
}
int div(int a, int b)
{return a / b;
}
int main()
{int x, y;int input = 1;int ret = 0;do{printf("*************************\n");printf(" 1:add 2:sub \n");printf(" 3:mul 4:div \n");printf("*************************\n");printf("请选择:");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;
}

修改后的代码:

#include <stdio.h>
int add(int a, int b)
{return a + b;
}
int sub(int a, int b)
{return a - b;
}
int mul(int a, int b)
{return a * b;
}
int div(int a, int b)
{return a / b;
}
void calc(int(*pf)(int, int))
{int ret = 0;int x, y;printf("输⼊操作数:");scanf("%d %d", &x, &y);ret = pf(x, y);printf("ret = %d\n", ret);
}
int main()
{int x, y;int input = 1;int ret = 0;do{printf("*************************\n");printf(" 1:add 2:sub \n");printf(" 3:mul 4:div \n");printf("*************************\n");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. qsort 函数

为了对于回调函数有更加深入的理解,我们需要对 qsort 函数进行学习与了解。

我们先从 qsort 函数的结构进行入手。

在这里插入图片描述
从图片中我们可以观察到,函数的返回类型为 void (不返回任何数据),第一个参数为 void* 类型,参数名为 base

第二个参数类型为 size_t ,参数名为 num ,第三个参数为 size_t ,参数名为 size

第四个参数为函数指针,指向的函数返回类型为 int ,函数的参数类型均为 const void*

目前的所有参数就构成了 qsort 函数,下面我们参数进行一一的深入讲解。

2.1 qsort 参数解析

2.1.1 base 解析

在这里插入图片描述
这里我们通过图片可以看出 base 参数的功能是指向数组中第一个需要被进行排序的元素,且它被转换成了 void* 类型的指针。

2.1.2 num 解析

在这里插入图片描述
这里通过图片可以观察出 num 参数的作用是 base 变量所指向数组中数组成员的个数,其实也可以引申为需要被排序元素的个数。

2.1.3 size 解析

在这里插入图片描述
这里通过图片可以观察出 size 参数的作用是存储被排序数组中每个成员的字节数大小。

2.1.3 compar 解析

在这里插入图片描述
先观察到图片中的第一句话 " Pointer to a function that compares two elements " ,

这句话向我们表明了 compar 函数的具体功能到底是什么,是用来比较两个元素的大小的。

下面那句话向我们说明,compar 函数会被 qsort 函数重复调用用来比较两个元素的大小,且实现 compar 函数时我们要遵循以下写法

int compar (const void* pi, const void* p2) (函数名与参数名不必一致)

对上面的原型进行总结就是,返回的类型要为 int ,两个参数的类型要均为 const void*

在这里插入图片描述
对于函数返回的值,图片也给出了具体的定义,在p1 小于 p2 时,返回小于 0 的值,在相等时返回 0 ,

p1 大于 p2 时,返回大于 0 的值。

在这里插入图片描述
图片中给出了一个实现范例,但在我们平时的实践中还有另外的实现方法,不必一定按照此模板进行实现。

对于参数的 const void* 类型在进行比较时一定要转换回原类型进行比较, 使用const void* 指针进行的行为比较是没有意义的,

所以一定要转换成原类型才可以。

2.2 qsort 函数使用举例

下面我们在代码中使用 qsort 函数,在实践中对其进行更加深入的理解。

#include <stdio.h>
//qosrt函数的使⽤者得实现⼀个⽐较函数
int int_cmp(const void * p1, const void * p2)
{return (*( int *)p1 - *(int *) p2);
}
int main()
{int arr[] = { 1, 3, 5, 7, 9, 2, 4, 6, 8, 0 };int i = 0;qsort(arr, sizeof(arr) / sizeof(arr[0]), sizeof (int), int_cmp);for (i = 0; i< sizeof(arr) / sizeof(arr[0]); i++){printf( "%d ", arr[i]);}printf("\n");return 0;
}

我们先对代码实现的 int_cmp 部分进行解析

int int_cmp(const void * p1, const void * p2)
{return (*( int *)p1 - *(int *) p2);
}

可以看到根据原型要求,返回类型为int ,参数类型为 const void * 在这里都实现了,但是我们要注意函数的具体实现部分,

在实现部分中我们要将参数由 const void* 类型强转成原先的指针类型(这里的传入的参数 p1 原先为整形指针),然后解引用进行比较

对于返回的数值,这里我们直接使 p1 减去 p2 ,这样在 p1 大于 p2 时就正巧返回大于 0 的数值,其他情况也是如此,

我们就不必根据返回类型的多种情况的多种取值进行繁琐的条件判断。

下面我们对主函数中对于 qsort 函数的调用部分进行解析。

int main()
{int arr[] = { 1, 3, 5, 7, 9, 2, 4, 6, 8, 0 };int i = 0;qsort(arr, sizeof(arr) / sizeof(arr[0]), sizeof (int), int_cmp);for (i = 0; i< sizeof(arr) / sizeof(arr[0]); i++){printf( "%d ", arr[i]);}printf("\n");return 0;
}

这里我们对参数进行一一对应,base 需要被排序数组的首元素 -> arr

num 需要被排序元素的个数 -> sizeof(arr) / sizeof(arr[0])size 被排序数组元素的字节大小 -> sizeof (int)

compar 比较函数 -> int_cmp

这样我们对于 qsort 函数的运用实例就分析完毕了。

2.3 qsort 函数排序结构体实例分析

在讲解完对于 qsort 函数对于整形的运用后,其他内置类型的运用我们也理应掌握了。

但是 qsort 函数还可以对于结构体这种自定义类型进行排序操作,下面我们也是举例然后进行解析。

struct Stu //学⽣
{char name[20];//名字int age;//年龄
};
//假设按照年龄来⽐较
int cmp_stu_by_age(const void* e1, const void* e2)
{return ((struct Stu*)e1)->age - ((struct Stu*)e2)->age;
}
//strcmp - 是库函数,是专⻔⽤来⽐较两个字符串的⼤⼩的
//假设按照名字来⽐较
int cmp_stu_by_name(const void* e1, const void* e2)
{return strcmp(((struct Stu*)e1)->name, ((struct Stu*)e2)->name);
}
//按照年龄来排序
void test2()
{struct Stu s[] = { {"zhangsan", 20}, {"lisi", 30}, {"wangwu", 15} };int sz = sizeof(s) / sizeof(s[0]);qsort(s, sz, sizeof(s[0]), cmp_stu_by_age);
}
//按照名字来排序
void test3()
{struct Stu s[] = { {"zhangsan", 20}, {"lisi", 30}, {"wangwu", 15} };int sz = sizeof(s) / sizeof(s[0]);qsort(s, sz, sizeof(s[0]), cmp_stu_by_name);
}
int main()
{test2();test3();return 0;
}

首先在结构体中,并没有对于一个结构体大于另一个结构体定义,所以只能我们自己去制定一个规则,比如结构体中有一个 age 成员,

两个结构体中,谁的 age 成员更大,就意味着哪个结构体更大,然后根据我们指定的这个规则去编写我们的比较函数,

这样我们就确定了判断哪个结构体更大的规则与函数,并且 qsort 函数就可以根据我们制定的规则去判断结构体的大小,

就像上面例子中按照年龄比较来实现:

int cmp_stu_by_age(const void* e1, const void* e2)
{return ((struct Stu*)e1)->age - ((struct Stu*)e2)->age;
}

在这个函数中我们就确定了用年龄来比较的规则,之后传入 qsort 函数中,函数就会根据我们的规则进行比较。

qsort(s, sz, sizeof(s[0]), cmp_stu_by_age);

下面我们再对 qsort 函数的调用部分进行解析:

//按照年龄来排序
void test2()
{struct Stu s[] = { {"zhangsan", 20}, {"lisi", 30}, {"wangwu", 15} };int sz = sizeof(s) / sizeof(s[0]);qsort(s, sz, sizeof(s[0]), cmp_stu_by_age);
}
//按照名字来排序
void test3()
{struct Stu s[] = { {"zhangsan", 20}, {"lisi", 30}, {"wangwu", 15} };int sz = sizeof(s) / sizeof(s[0]);qsort(s, sz, sizeof(s[0]), cmp_stu_by_name);
}

因为 qsort 函数是对数组进行排序,我们要创建一个结构体数组,对其进行初始化。

然后再对参数进行一一对应,前三个参数不变,base 对应结构体数组的首成员,num 对应数组成员,size 对应数组成员的字节大小,

接着我们根据想要进行排序的规则,调用对应的比较函数,我们就可以完成对应的 qsort 函数的调用。

2.5 使用冒泡排序模拟实现 qsort 函数

我们先将具体的实现代码完整呈现出来,在对代码的部分进行分部的具体讲解。

#include <stdio.h>
int int_cmp(const void * p1, const void * p2)
{return (*( int *)p1 - *(int *) p2);
}
void _swap(void *p1, void * p2, int size)
{int i = 0;for (i = 0; i< size; i++){char tmp = *((char *)p1 + i);*(( char *)p1 + i) = *((char *) p2 + i);*(( char *)p2 + i) = tmp;}
}
void bubble(void *base, int count , int size, int(*cmp )(void *, void *))
{int i = 0;int j = 0;for (i = 0; i< count - 1; i++){for (j = 0; j<count-i-1; 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[] = { 1, 3, 5, 7, 9, 2, 4, 6, 8, 0 };int i = 0;bubble(arr, sizeof(arr) / sizeof(arr[0]), sizeof (int), int_cmp);for (i = 0; i< sizeof(arr) / sizeof(arr[0]); i++){printf( "%d ", arr[i]);}printf("\n");return 0;
}

在具体的代码实现呈现出来后,我们先将目光聚焦于模拟 qsort 函数的实现部分

void bubble(void *base, int count , int size, int(*cmp )(void *, void *))
{int i = 0;int j = 0;for (i = 0; i< count - 1; i++){for (j = 0; j<count-i-1; j++){if (cmp ((char *) base + j*size , (char *)base + (j + 1)*size) > 0){_swap(( char *)base + j*size, (char *)base + (j + 1)*size,size);}}}
}

比较于 qsort 函数,我们模拟实现的函数参数部分变化不变,因为对于排序部分的实现,我们无需多余的参数。

基于冒泡排序的原理,在比较时我们发数组中相邻两个元素中,下表小的元素大于下标大的元素时,我们要将两元素位置进行交换。

所以接下来要对两个部分进行思考,如何实现对于两个 const void* 元素进行比较大小,以及如何对 const void* 的元素进行交换。

我们先对比较大小的部分进行实现。

int int_cmp(const void * p1, const void * p2)
{return (*( int *)p1 - *(int *) p2);
}

对于大小的比较我们只需将指针还原为原元素类型指针,返回相减之后的结果即可。

对于两个地址的交换则没有那么简单了。

void _swap(void *p1, void * p2, int size)
{int i = 0;for (i = 0; i< size; i++){char tmp = *((char *)p1 + i);*(( char *)p1 + i) = *((char *) p2 + i);*(( char *)p2 + i) = tmp;}
}

对于元素地址的交换我们需要采用逐地址交换的方法来实现,正巧 char* 类型的地址字节数为 1 ,能够让我们完成逐地址的交换,

这里注意在逐地址的交换中,我们要对地址进行解引用后再进行交换,强制类型转换的地址一个临时右值,不能作为左值进行更改,

在逐地址交换的循环中,我们要使用 size 数组每元素的字节数来作为限制我们循环的变量。

在以上部分的实现都明朗后对于主函数的编写我们就胸有成竹了。

int main()
{int arr[] = { 1, 3, 5, 7, 9, 2, 4, 6, 8, 0 };int i = 0;bubble(arr, sizeof(arr) / sizeof(arr[0]), sizeof (int), int_cmp);for (i = 0; i< sizeof(arr) / sizeof(arr[0]); i++){printf( "%d ", arr[i]);}printf("\n");return 0;
}

这里的主函数编写与 qsort 函数在主函数中的调用并无二般,故此不再赘述。

http://www.dtcms.com/a/276778.html

相关文章:

  • matplotlib:多个图表的绘制
  • RocketMQ-
  • 69 局部变量的空间分配
  • 系统引导修复
  • 功耗校准数据PowerProfile测试方法建议
  • (一)一阶数字低通滤波器---原理及其推导
  • 程序改错---字符串
  • 十三、K8s自定义资源Operator
  • 客户资源被挖?营销方案泄露?企业经营信息保护避坑指南
  • Python Day11
  • Agent任务规划
  • 【PMP备考】敏捷思维:驾驭不确定性的项目管理之道
  • QT中设计qss字体样式但是没有用【已解决】
  • 文件系统(精讲)
  • JVM与系统性能监控工具实战指南:从JVM到系统的全链路分析
  • 【每日刷题】阶乘后的零
  • SOEM build on ubuntu
  • Golang实战:使用 Goroutine 实现数字与字母的交叉打印
  • 使用bp爆破模块破解pikachu登录密码
  • 使用frp内网穿透:将本地服务暴露到公网
  • 张量类型转换
  • 深入探讨Java的ZGC垃圾收集器:原理、实战与优缺点
  • 格密码--数学基础--08最近向量问题(CVP)与格陪集
  • Mentor软件模块复杂,如何分角色授权最合理?
  • 【PTA数据结构 | C语言版】阶乘的递归实现
  • 串口屏的小记哦
  • 鸿蒙进程通信的坑之ServiceExtensionAbility
  • Datomic数据库简介(TBC)
  • Ntfs!LfsFlushLfcb函数分析之Ntfs!_LFCB->LbcbWorkque的背景分析3个restart页面一个普通页面的一个例子
  • 如何在IEEETrans格式的latex标题页插入图像