指针的进阶
7.回调函数之详细理解
我们今天来详细了解回调函数及其应用,首先我们先来想一想曾经学过的冒泡排序,其基本思想:通过两个元素的比较,将较大值后移,最终实现数组升序的目标。我们用一段代码进行演示。
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 temp = 0;
temp = arr[j];
arr[j] = arr[j + 1];
arr[j + 1] = temp;
}
}
}
}
int main()
{
int arr[] = { 3,1,4,5,8,9,2,0,7,6 };
int sz = sizeof(arr) / sizeof(arr[0]);
int i = 0;
bubble_sort(arr, sz);
for (i = 0; i < sz; i++)
{
printf("%d ", arr[i]);
}
return 0;
}
但是冒泡排序只能实现整型数据,如果是浮点型,结构体怎么进行比较呢?在这里,介绍一个C语言库函数中的函数:qsort;
qsort有四个参数,第一个参数:void* base指向了需要排序的数组的第一个数组,第二个参数:size_t num是排序的元素个数,第三个参数:size_t size指的是排序的一个元素的大小,第四个参数:int (*compar)(const void*,const void*)是函数指针,这个函数指针指向的函数能够比较base指向数组中的两个元素。 当前面元素大于后面元素返回大于0的数,等于返回0;前面元素小于后面元素返回小于0的数。
这里有一个问题void*指针到底代表了什么?其实它指的是无具体类型的指针,它可以指向int,float,等等,因为不知道它具体的类型,所以不能再使用解引用*,和指针加减运算,因为并不能知道它具体类型。
我们再来看一下实际应用时,怎样使用qsort函数进行比较。
(1)使用qsort对数组进行排序
int cmp_int(const void* p1, const void* p2)
{
return (*(int*)p1 - *(int*)p2);
//既然不知道void*具体代表什么类型,就强制类型转换,然后再解引用得到值
}
//cmp_int函数是对arr数组元素进行比较的函数
void Print(int arr[], int sz)
{
int i = 0;
for (i = 0; i < sz; i++)
{
printf("%d ", arr[i]);
}
}
test1()
{
int arr[10] = { 9,8,7,6,5,4,3,2,1,0 };
int sz = sizeof(arr) / sizeof(arr[0]);
qsort(arr, sz, sizeof(arr[0]), cmp_int);
//arr数组首元素地址,sz数组大小,sizeof求出比较元素字节大小
Print(arr, sz);
}
int main()
{
test1();
return 0;
}
(2)使用qsort对结构体进行排序
struct Stu
{
char name[20];
int age;
};
int cmp_stu_age(const void* p1, const void* p2)
{
return ((struct Stu*)p1)->age - ((struct Stu*)p2)->age;
//先把p1强制类型转换成结构体指针,再进行解引用找到age
}
void Print(struct Stu arr[], int sz)
{
int i = 0;
for (i = 0; i < sz; i++)
{
printf("%d ", arr[i].age);
}
printf("\n");
}
void test2()
{
struct Stu arr[] = { {"zhangsan",20},{"lisi",17},{"zhangjing",25} };
int sz = sizeof(arr) / sizeof(arr[0]);
qsort(arr, sz, sizeof(arr[0]), cmp_stu_age);
Print(arr, sz);
}
int main()
{
test2();
return 0;
}
综上所述,qsort的特点是基于快速排序思想,适用于任何类型的数据进行排序。那么我们今天,基于冒泡排序的思想,模拟实现qsort函数。
那么当模拟实现此函数时就需要进行对照参数进行设计,我们设计的函数名为bubble_sort,那么对照其第一个参数设计为void* base,第二个参数则为int num,第三个参数为int size,第四个参数为int (*compar)(const void*,const void*)。我们将四个参数设计完之后,需要考虑其代码内部实现,qsort函数基于快速排序思想,那我们就基于冒泡排序的思想,处理函数内部。
void Swap(char* buf1, char* buf2, int size)
{
int i = 0;
char tmp = 0;
for (i = 0; i < size; i++)
{
tmp = *buf1;
*buf1 = *buf2;
*buf2 = tmp;//易错
buf1++;
buf2++;
}
}
void bubble_sort(void* base, int num, int size, int(*cmp)(const void*, const void*))
{
int i = 0;
//此时num为要排序元素个数,相当于在冒泡排序中sz;这一步相当于跑躺数
for (i = 0; i < num - 1; i++)
{
int j = 0;
//每一趟内部的比较
for (j = 0; j < num - i-1; j++)
{
//cmp为函数指针,需要传参,原来为arr[j]和arr[j+1]比较,现在则为函数指针
//相当于需要将两者的地址传给cmp,再让cmp进行比较。
if (cmp((char*)base + j * size, (char*)base + (j + 1) * size) > 0)
{
Swap((char*)base + j * size, (char*)base + (j + 1) * size, size);
}
}
}
}
//就比如int arr={1,2,3,4,5,6,7,8,9} base为arr,num为10,size为4
int cmp_int(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 test()
{
int arr[10] = { 3,1,5,2,4,7,9,6,8,0 };
int sz = sizeof(arr) / sizeof(arr[0]);
bubble_sort(arr, sz, sizeof(arr[0]), cmp_int);
Print(arr, sz);
}
int main()
{
test();
return 0;
}
我们现在逐个分析每个函数的作用,我们用bubble_sort函数代替qsort函数实现排序思想,用冒泡排序代替其快速排序,在bubble_sort中,我们是函数设计者,我们根据冒泡排序核心,实现对四个参数的利用,以及实现排序。Swap函数则是实现交换的思想,剩下的test和cmp_int函数都是我们为了验证bubble_sort函数写出的函数,是从应用角度出发,理解这个概念后,我们来看一些里面比较复杂的代码。
if (cmp((char*)base + j * size, (char*)base + (j + 1) * size) > 0)
{
Swap((char*)base + j * size, (char*)base + (j + 1) * size, size);
}
这一段函数是核心,其核心思想利用bubble_sort函数传进来的函数指针,进行对元素的比较,由于bubble_sort可实现对所有数据的排序,所有我们要使用指针,而不是像冒泡排序那样直接进行比较,if (cmp((char*)base + j * size, (char*)base + (j + 1) * size) > 0)这一段函数就实现了判断,之所以这里使用char*是为了解引用时候只读一个字节,这样可以和后面的相乘联系起来。大家还有什么问题?欢迎在评论区讨论。