【C语言进阶篇】指针进阶(三)回调函数 - 使用并模拟实现qsort函数

目录

  • 引言
  • 回调函数
    • 回调函数的定义
    • 学习使用库函数qsort
    • 模拟实现通用排序

引言

此前我们已经了解,函数指针可以存放一个函数的地址,并且可以通过函数指针找到函数的地址并去调用它。
比如下面这样:
【C语言进阶篇】指针进阶(三)回调函数 - 使用并模拟实现qsort函数_第1张图片
我们还看了两段复杂代码:

//代码1 
(*(void (*)())0)();
//代码2
void (*signal(int, void(*)(int)))(int);

其中代码2经过重重分解,我们知道它是一个函数声明,函数的参数分别是一个 int 类型和一个函数指针类型。
如果要调用 signal 函数,传参的时候就要传一个函数指针过去,那么在 signal 函数内部就可以通过传过来的这个函数指针找到函数的地址并去调用它。
在一个函数里面调用另一个函数,这就引出了下面的回调函数。

回调函数

回调函数的定义

回调函数就是一个通过函数指针调用的函数。
如果你把函数的指针(地址)作为参数传递给另一个函数,当这个指针被用来调用其所指向的函数时,我们就说这是回调函数。
回调函数不是由该函数的实现方直接调用,而是在特定的事件或条件发生时由另外的一方调用的,用于对该事件或条件进行响应。

我们通过一个库函数 qsort 来理解这个问题。


学习使用库函数qsort

首先我们先来认识一下 qsort 函数:
【C语言进阶篇】指针进阶(三)回调函数 - 使用并模拟实现qsort函数_第2张图片
通过这个我们知道:

qsort 的作用是实现一次快速排序
返回类型为void
参数有四个,分别是:
void * base - 无类型指针
size_t num - 整型
size_t width - 整型
int (__cdecl * compare )(const void * elem1, const void * elem2) - 函数指针类型,指向函数的参数是两个无类型指针,返回类型是 int
为什么有很多无类型指针呢?这个在实现我们自己的万能排序函数时会解释。

这些参数分别是什么意义呢?

【C语言进阶篇】指针进阶(三)回调函数 - 使用并模拟实现qsort函数_第3张图片翻译一下:
第一个参数 base 是目标数组的首元素地址
第二个参数 num 是数组的元素个数
第三个参数 width 是数组每个元素的大小
第四个参数 compare 是一个比较函数
它的两个参数 elem1elem2 是两个需要比较的元素
它的返回值如下:
【C语言进阶篇】指针进阶(三)回调函数 - 使用并模拟实现qsort函数_第4张图片
elem1 > elem2 <=> compare < 0
elem1 = elem2 <=> compare = 0
elem1 < elem2 <=> compare > 0

现在了解的差不多了,我们试着调用它:

我先创建一个数组:
int arr[] = { 1, 3, 5, 7, 9, 2, 4, 6, 8, 0 };
传参时需要传数组名、数组元素个数和每个元素的大小,所以需要计算一下:
num = sizeof(arr) / sizeof(arr[0]);
width = sizeof(arr[0]);
那么问题来了,它也没给比较函数啊,所以我也没法传过去啊。
这,就需要我们自己实现比较函数了。

int int_cmp(const void* e1, const void* e2) 
{
 	return (*(int*)e1 - *(int*) e2);
}

首先函数定义部分直接照抄下来。
由于是进行整型数组排序,所以在进行强制类型转换时转换为整型指针,再解引用就得到了待比较的两个元素的值。
返回值为两个数的差值,与 compare 的返回值保持一致。


这里插一嘴啊,如果需要排序的元素是字符型,比较函数就不能这样写,因为字符型变量不能直接加加减减,所以需要用到 strcmp 函数。写法如下:

int cmp_char(const void* e1, const void* e2)
{
	return strcmp((char*)e1, (char*)e2);
}

所以参数部分都有了,代码基本就写完了,整合一下:

int int_cmp(const void *e1, const void* e2) 
{
 	return (*(int*)e1 - *(int*)e2);
}
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]);
    }
    return 0; 
}

代码运行起来:
在这里插入图片描述
结果也没问题。只不过我们这里实现的是升序排序,如果想实现降序排序则改变一下 cmp 函数即可。


模拟实现通用排序

由于水平限制,下面用冒泡排序来实现。
我现在要写一个函数,能对各种类型(整型、字符型、结构体类型)实现排序。要怎么做呢?

首先,我写这个函数的时候并不知道使用者会对哪种类型的数组进行排序,所以接收参数的类型不确定,所以要用一个 void* 指针来接收数组首元素地址。
其次,需要排序的元素数量也要知道啊,因为这决定了冒泡排序循环的次数。
再次,数组首元素传过来的是一个 void* 无类型指针,无法直接调用,但我还需要用它来找到需要比较的元素。以 int[ ] 数组为例,现在传过来的是它首元素四个字节空间中首字节的地址,将指针强制类型转换为 int* 就可以访问数组的首元素,强制类型转换后+1就指向到了第二个元素。但是,问题是我还是不知道传过来的数据是什么类型啊。但如果我知道数组每个元素的大小 width,把指针强制转换为 char* ,一次加一个 width,那不就指向下一个元素的地址了嘛。以此类推,就可以通过传来的 void* 指针访问到数组的每个元素并拿他们进行比较

有了以上的分析基础,我们差不多可以写出函数:

void bubble_sort(void* base, int sz, int width, int(*cmp)(const void* e1, const void* e2))
{
	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);
		}
	}
}

其中 cmp 比较函数是使用者自己写的,swap 函数是交换两个元素,那问题就来了,函数是怎么完成两个元素的交换?
int arr[2] 整形数组为例,先对数组初始化:
arr[0] = 0x44332211;
arr[1] = 0x55667788;
arr 传给 swap ,而在在传参时把 arr 强制类型转换为 char* ,那么函数如何用 char* 完成两个数据的交换呢?下面用一张图解释一下:
【C语言进阶篇】指针进阶(三)回调函数 - 使用并模拟实现qsort函数_第5张图片
这里只演示了交换两次的效果。
图中的 4 是哪里来的呢?是 width 。而要交换几次呢?是 width 次。

所以现在应该能写出 swap 函数:

void swap(char* e1, char* e2, int width)
{
	for (int i = 0; i < width; i++)
	{
		char tmp = *(e1 + i);
		*(e1 + i) = *(e2 + i);
		*(e2 + i) = tmp;
	}
}

可以用下面代码测试一下写的排序函数:

//整型排序测试
int cmp_int(const void* e1, const void* e2)
{
	return *(int*)e1 - *(int*)e2;
}
void test_int()
{
	int arr[10] = { 10,9,8,7,6,5,4,3,2,1 };
	int sz = sizeof(arr) / sizeof(arr[0]);
	bubble_sort(arr, sz, sizeof(arr[0]), cmp_int);
}

//字符型排序测试
int cmp_char(const void* e1, const void* e2)
{
	return strcmp((char*)e1, (char*)e2);
}
void test_char()
{
	char ch[] = "asdfghjkl";
	int sz = sizeof(ch) / sizeof(ch[0]);
	bubble_sort(ch, sz, sizeof(ch[0]), cmp_char);
}

//结构体排序测试
struct Stu
{
	char name[20];
	int age;
};
//按姓名
int cmp_struct_age(const void* e1, const void* e2)
{
	return ((struct Stu*)e1)->age - ((struct Stu*)e2)->age;
}
void test_struct_name()
{
	struct Stu s[] = { {"zhangsan", 38}, {"lisi", 28}, {"wangwu", 18} };
	int sz = sizeof(s) / sizeof(s[0]);
	bubble_sort(s, sz, sizeof(s[0]), cmp_struct_name);
}
//按年龄
int cmp_struct_age(const void* e1, const void* e2)
{
	return ((struct Stu*)e1)->age - ((struct Stu*)e2)->age;
}
void test_struct_age()
{
	struct Stu s[] = { {"zhangsan", 38}, {"lisi", 28}, {"wangwu", 18} };
	int sz = sizeof(s) / sizeof(s[0]);
	bubble_sort(s, sz, sizeof(s[0]),cmp_struct_age);
}

到这里指针进阶的理论知识就结束了,但是想学好光有理论功夫是不够的,还得多刷题。所以继续加油吧!
最后,封面图送上:
【C语言进阶篇】指针进阶(三)回调函数 - 使用并模拟实现qsort函数_第6张图片

你可能感兴趣的:(C语言,c语言,c++,后端,经验分享,开发语言)