系统性学习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
函数在主函数中的调用并无二般,故此不再赘述。