目录
回调函数
回调函数就是⼀个通过函数指针调⽤的函数。
如果你把函数的指针(地址)作为参数传递给另⼀个函数,当这个指针被⽤来调⽤其所指向的函数 时,被调⽤的函数就是回调函数。回调函数不是由该函数的实现⽅直接调⽤,⽽是在特定的事件或条件发⽣时由另外的⼀⽅调⽤的,⽤于对该事件或条件进⾏响应。
qsort函数
qsort - C++ 参考 (cplusplus.com)
qsort函数的介绍如下图:
void qsort (void* base, size_t num, size_t size, int (*compar)(const void*,const void*));
冒泡排序
冒泡排序的核心思想就是:两两相邻的元素进行比较
void print(int arr[],int sz)//打印数组
{
int i = 0;
for(i = 0;i<sz;i++)
{
printf("%d ",arr[i]);
}
}
void bubble_sort(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 tmp = arr[j];
arr[j] = arr[j+1];
arr[j+1] = tmp;
}
}
}
}
int main()
{
int arr[] = { 0,9,8,7,6,5,4,3,2,1 };
int sz = sizeof(arr)/sizeof(arr[0]);
print(arr,sz);//0 9 8 7 6 5 4 3 2 1
bubble_sort(arr,sz);
print(arr,sz);//0 1 2 3 4 5 6 7 8 9
return 0;
}
模拟实现
在原bubble_sort中,我们根据整形数组的地址以及计算所得其中的元素个数进行排序,这一点与qsort函数中前两项的参数恰好符合。
需要进行调整的是,size_t size(数组中每个元素的大小),int(*compar)(const void*,const void*)(指向比较两个元素函数的指针) 。
建立新的函数:
void bubble_sort(void* base, size_t sz, size_t width, int(*cmp)(const void*, const void*)
所以对比原bubble_sort,我们所建立的两层循环可以随数组的变化而变化,但是交换条件却要进行调整。原本的交换条件已经无法适应新的交换数据。
//符合条件进行交换
if(arr[j]>arr[j+1])
{
int tmp = arr[j];
arr[j] = arr[j+1];
arr[j+1] = tmp;
}
//需要对交换部分进行修改
交换条件的编写
根据qsort函数,我们得知,qsort是根据函数指针的返回结果来判断是否需要进行交换的,因此,我们也需要调用函数指针来完成交换条件的编写:
//按照升序排序
if(cmp(函数参数,后一个函数参数)>0)
{
//交换
}
类比整形数据的交换,我们需要找到每个元素在数组中所对应的下标进行访问,并且找到下一个元素进行比较。
//原bubble_sort交换条件
if(arr[j]>arr[j+1])
如何解决?
首先,我们需要根据数组的类型来确定数组+1能变化的步长。
当数组中的每个元素为char类型时,+1会跳过1个字节。为int类型时,+1会跳过4个字节。每个类型+1跳过的字节数各有差异。同时,bubble_sort在传参时的参数之一 size_t width 表示的是数组中每一个元素的大小(以字节为单位)。以单位字节的步长确定每个元素在数组中的位置是最精确方法。所以需要将数组指针类型强制转换为char*
根据循环条件j++,我们可以利用j的变化来完成对交换条件的编写
if(cmp((char*)base+j*width,(char*)base+(j+1)*width)>0)
{
//交换
}
(char*)base+j*width//前一个要进行比较的元素
(char*)base+(j+1)*width//后一个要进行比较的元素
交换方法的编写
因为传入的数组指针类型各不相同,要进行交换的方法也必须要符合各个类型。
根据交换条件,我们找到了要进行比较的前一个元素和后一个元素,所以在交换时,也同样是对这两个元素进行交换。同时,因为我们将这两个元素的数组指针类型强制转换位char*,所以当交换时也需要一个字节一个字节进行交换,这里可以调用形参width(表示数组中每个元素的大小,以字节为单位)套用循环进行交换。
我们可以自定义函数Swap来完成交换:
Swap((char*)base+j*width,(char*)base+(j+1)*width,width)
自定义函数Swap的定义:
void Swap(char* f1, char* f2, int width)
{
char tmp = 0;
int i = 0;
for(i = 0;i<width;i++)//一个字节一个字节交换两个元素
{
tmp = *f1;
*f1 = *f2;
*f2 = tmp;
f1++;
f2++;
}
}
不同类型参数比较函数的编写
对于bubble_sort中形式参数int(*cmp)(const void*, const void*),这个函数需要自己设计,对于不同类型需要给出不同的解决方案。
但相同的是,返回类型始终为int
当前一个参数大于后一个参数时,返回大于0的数
当前一个参数等于后一个参数时,返回0
当前一个参数小于后一个参数时,返回小于0的数
以int类型的参数为例:
当函数对参数进行接收时,我们不知道参数的类型,所以需要用void*来进行接收。
当数组为整形数组时,我们需要根据条件调用对应的回调函数。因为void*类型的指针不能进行解引用和+-整数的操作,我们需要先将指针类型强制转换为int*类型,再进行解引用操作。
比较前后两个元素的大小,我们可以利用减法,大时返回>0的数,等时返回0,小时返回<0的数。
int int_cmp(const void* p1, const void* p2)
{
return *(int*)p1-*(int*)p2;
}
以结构数据为例:
我们同样需要比较大小来返回值。
struct Stu
{
char name[20];
int age;
};
当根据name来进行比较时,我们需要对比字符串来完成。对比字符串就需要一个库函数strcmp来完成。
strcmp:
对比strcmp和qsort,它们的相同点是,返回值相同:
当前一个参数大于后一个参数时,返回大于0的数
当前一个参数等于后一个参数时,返回0
当前一个参数小于后一个参数时,返回小于0的数
所以在比较name时,可以如此编写:
int name_cmp(const void* p1, const void* p2)
{
return strcmp(((struct Stu*)p1)->name,((struct Stu*)p2)->name);
}
在比较age时,与int类型相似,可以如此编写:
int age_cmp(const void* p1, const void* p2)
{
return ((struct Stu*)p1)->age-((struct Stu*)p2)->age;
}
调试
元素类型为int的数组
#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>
#include<string.h>
#include<stdlib.h>
int int_cmp(const void* p1, const void* p2)
{
return *(int*)p1 - *(int*)p2;
}
void print(int arr[], int sz)//打印数组
{
int i = 0;
for (i = 0; i < sz; i++)
{
printf("%d ", arr[i]);
}
printf("\n");
}
void Swap(char* f1, char* f2, int width)
{
char tmp = 0;
int i = 0;
for (i = 0; i < width; i++)//一个字节一个字节交换两个元素
{
tmp = *f1;
*f1 = *f2;
*f2 = tmp;
f1++;
f2++;
}
}
void bubble_sort(void* base, size_t sz, size_t width, int(*cmp)(const void* , const void* ))//冒泡排序
{
int i = 0;
//需要进行多少趟排序
for (i = 0; i < sz - 1; i++)
{
int j = 0;
//每趟需要进行多少次
for (j = 0; j < sz - 1 - i; j++)
{
//符合条件进行交换
if (cmp((char*)base + j * width, (char*)base + (j + 1) * width) > 0)
{
//交换
Swap((char*)base + j * width, (char*)base + (j + 1) * width, width);
}
}
}
}
int main()
{
int arr[] = { 0,9,8,7,6,5,4,3,2,1 };
int sz = sizeof(arr) / sizeof(arr[0]);
print(arr, sz);//0 9 8 7 6 5 4 3 2 1
bubble_sort(arr, sz, sizeof(arr[0]), int_cmp);
print(arr, sz);//0 1 2 3 4 5 6 7 8 9
return 0;
}
排序结构数据:(name)
#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>
#include<string.h>
#include<stdlib.h>
struct Stu
{
char name[20];
int age;
};
int name_cmp(const void* p1, const void* p2)
{
return strcmp(((struct Stu*)p1)->name, ((struct Stu*)p2)->name);
}
void Swap(char* f1, char* f2, int width)
{
char tmp = 0;
int i = 0;
for (i = 0; i < width; i++)//一个字节一个字节交换两个元素
{
tmp = *f1;
*f1 = *f2;
*f2 = tmp;
f1++;
f2++;
}
}
void bubble_sort(void* base, size_t sz, size_t width, int(*cmp)(const void* , const void* ))//冒泡排序
{
int i = 0;
//需要进行多少趟排序
for (i = 0; i < sz - 1; i++)
{
int j = 0;
//每趟需要进行多少次
for (j = 0; j < sz - 1 - i; j++)
{
//符合条件进行交换
if (cmp((char*)base + j * width, (char*)base + (j + 1) * width) > 0)
{
//交换
Swap((char*)base + j * width, (char*)base + (j + 1) * width, width);
}
}
}
}
int main()
{
struct Stu arr[3] = { {"zhangsan",20},{"lisi",35},{"wangwu",18} };
int sz = sizeof(arr) / sizeof(arr[0]);
bubble_sort(arr, sz, sizeof(arr[0]), name_cmp);
return 0;
}
排序结构数据:(age)
#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>
#include<string.h>
#include<stdlib.h>
struct Stu
{
char name[20];
int age;
};
int age_cmp(const void* p1, const void* p2)
{
return ((struct Stu*)p1)->age - ((struct Stu*)p2)->age;
}
void Swap(char* f1, char* f2, int width)
{
char tmp = 0;
int i = 0;
for (i = 0; i < width; i++)//一个字节一个字节交换两个元素
{
tmp = *f1;
*f1 = *f2;
*f2 = tmp;
f1++;
f2++;
}
}
void bubble_sort(void* base, size_t sz, size_t width, int(*cmp)(const void* , const void* ))//冒泡排序
{
int i = 0;
//需要进行多少趟排序
for (i = 0; i < sz - 1; i++)
{
int j = 0;
//每趟需要进行多少次
for (j = 0; j < sz - 1 - i; j++)
{
//符合条件进行交换
if (cmp((char*)base + j * width, (char*)base + (j + 1) * width) > 0)
{
//交换
Swap((char*)base + j * width, (char*)base + (j + 1) * width, width);
}
}
}
}
int main()
{
struct Stu arr[3] = { {"zhangsan",20},{"lisi",35},{"wangwu",18} };
int sz = sizeof(arr) / sizeof(arr[0]);
bubble_sort(arr, sz, sizeof(arr[0]), age_cmp);
return 0;
}
总结
使用回调函数模拟实现qsort(冒泡的方式)对于我来说有些难度,尤其是在对交换条件和交换方法进行编写时需要考虑到数组指针的类型以及不同类型给指针带来的不同权重。但是通过类比原bubble_sort函数,对照着思考便可以理清我们的思路。在用回调函数对各个不同类型的参数进行比较时,也要考虑到类型所带来的差异。这次模拟实现对我们学习指针以及提高自己的思维能力有很大的帮助!