冒泡排序(进阶):运用回调函数,模拟实现库函数qsort

本文探讨了C标准库函数qsort的使用,重点介绍了如何通过回调函数实现根据不同条件对整型数组和结构体数组进行排序的灵活性。作者模拟了qsort的通用实现,利用void*进行泛型编程,展示了回调函数在处理不同类型数据时的便捷性。
摘要由CSDN通过智能技术生成

C 库函数 - qsort()

声明:

void qsort(void *base, size_t nitems, size_t size, int (*compar)(const void *, const void*))

参数说明:

base -- 指向要排序的数组的第一个元素的指针。
nitems -- 由 base 指向的数组中元素的个数。
size -- 数组中每个元素的大小,以字节为单位。
compar -- 用来比较两个元素的函数。

描述:
库函数qsort对数组进行排序

callback回调函数:

回调函数,光听名字就比普通函数要高大上一些,那到底什么是回调函数呢?上网查阅资料,说法可谓是众说纷纭。
但最让我印象深刻的是百度百科的说法:

回调函数就是一个通过函数指针调用的函数。
如果你把函数的指针(地址)作为参数传递给另一个函数,
当这个指针被用来调用其所指向的函数时,我们就说这是回调函数。

我们来看一下回调函数的使用,体会一下。这里以整型数组排序为例。

//回调函数如下
//认为如果x y 符合排序要求返回1,不符合返回0;
typedef int(*Cmp)(int x, int y);       //这里的Cmp是函数指针的类型

int less(int x, int y)
{
	//这个函数表示用于升序排序
	//如果x比y小,就是符合排序要求
	return x < y ? 1 : 0;
}
int greater(int x, int y)
{
	//降序排序
	return x>y ? 1 : 0;
}

//冒泡排序
void bubbleSort(int arr[], int len, Cmp cmp)
{
	for (int bubble = 0; bubble < len; bubble++)
	{
		for (int cur = len - 1; bubble < cur; cur--)
		{
			if (cmp(arr[cur], arr[cur - 1])==1)
			{
				int tmp = arr[cur];
				arr[cur] = arr[cur - 1];
				arr[cur - 1] = tmp;
			}
		}
	}
}

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

这段代码里,cmp函数的指针作为参数传入bubble函数中,这个指针又返回来调用cmp函数,作为冒泡排序的条件。
我们日常写代码时,也可以通过回调函数来满足调用者不同的需求,达到一段代码、多种用途的目的。比如本例中,cmp函数指针实现两种不同的排序方式(升序和降序)。

再来体会一下结构体排序中回调函数的使用:

//创建结构体
typedef struct Student
{
	int id;
	char name[100];
	int score;
}Student;

// 比较规则的声明
typedef int(*CmpStudent)(Student* x, Student *y);       //结构体传入函数时隐式转换成指针

//冒泡排序
void bubblesortStudent(Student arr[], int len, CmpStudent cmp)
{
	for (int bubble = 0; bubble < len; bubble++)
	{
		for (int cur = len - 1; cur>bubble; cur--)
		{
			if (cmp(&arr[cur], &arr[cur - 1]))      //符合排序要求就进行交换
			{
				Student tmp = arr[cur - 1];
				arr[cur - 1] = arr[cur];
				arr[cur] = tmp;
			}
		}
	}
}

//ID升序
int cmpIdless(Student* x, Student* y)
{
	return x->id < y->id ? 1 : 0;
}
//ID降序
int cmpIdgreater(Student* x, Student* y)
{
	return x->id > y->id ? 1 : 0;
}
//如果分数相同按照id升序排序
//如分数不同,按照分数升序排序
int cmpScorelessAndIdless(Student* x, Student* y)
{
	if (x->score == y->score)
		return x->id < y->id ? 1 : 0;
	return x->score < y->score ? 1 : 0;
}

int main()
{
	Student students[] = 
	{
		{ 1, "张三", 96 },
		{ 2, "李四", 90 },
		{ 3, "王五", 94 },
		{ 4, "赵六", 98 },
		{ 5, "陈七", 94 },
	};
	int n = sizeof(students) / sizeof(students[0]);
	bubblesortStudent(students, n, cmpScorelessAndIdless);
	for (int j = 0; j < n; ++j)
	{
		printf("%s\n", students[j].name);
	}

	return 0;
}

qsort的模拟实现

思路:
根据描述我们知道,qsort用来对数组进行排序,那么问题来了,都有哪些数组呢?
对于我们日常所学,大多为整型数组,字符数组,字符串数组,结构体数组等等。
如何做一个可以对这些不同类型的数组进行统一排序的函数呢?
在C语言中,泛型编程的用法是非常局限的,这里我们需要了解的就是用void*来实现泛型编程达到函数多重用法的目的。
用函数指针形成回调函数,作为排序的条件,之后我们排序函数只需要加上这个排序条件,再正常排序即可。

我们试试将上面两段代码合并从成通用的:

//创建结构体
typedef struct Student
{
	int id;
	char name[100];
	int score;
}Student

//             通过void* 进行泛型编程 ,模拟实现qsort


//指定比较规则
//这里的void*是通用类型,如果是比较整形,这里的void*就使用int*赋值
//如果是比较结构体类型,就用student*赋值
typedef int(*Cmp)(void*, void*);


//这里的len表示数组元素个数,  unitlen表示每个元素的大小
//本来每个元素的大小是包含在元素的类型中的,但此处我们使用void*,
//来支持多种不同类型的数组,于是每个元素到底多长,据需要程序员手动指定
void bubbleSortGeneral(void* arr, int len, int unitlen,Cmp cmp)
{
	//这里void*是不能解引用的,所以不能使用arr[i]来取到数组元素,那怎么办呢?

	for (int bound = 0; bound < len; bound++)
	{	
		//我们需要对数组进行强制类型转换,这里统一转成char*,再按照char的计算方式来计算		
		//如果传入的数组是整型数组
		//正常的取法: arr[cur-1]    ==>    arr+(cur-1)         因为这里的-1是减1个整形,四个字节
		//转成char的取法:arr[cur-1]    ==>    arr+(cur-1)*unitlen    这里的-1是减1个char,1个字节
		for (int cur = len - 1; cur>bound; cur--)
		{
			char* carr = (char*)arr;
			//p1指向cur-1这个元素的首地址
			char* p1 = carr + (cur - 1)*unitlen;
			//p2指向cur这个元素的首地址	
			char* p2 = carr + cur*unitlen;
			if (cmp(p1, p2) != 1)
			{
				//交换
				//先得有一个临时空间
				char tmp[1024] = { 0 };
				//交换三连
				memcpy(tmp, p1, unitlen);
				memcpy(p1, p2, unitlen);
				memcpy(p2, tmp, unitlen);
			}
		}
	}
}

//比较函数
//整型数组的交换规则
int cmpint(void* x, void* y)
{
	//把x和y当成两个int*理解
	int* ix = (int*)x;
	int* iy = (int*)y;
	return ix < iy ? 1 : 0;
}
//学生结构体的交换规则
int cmpStudent(void* x,void* y)      //得分比较
{
	Student* sx = (Student*)x;
	Student* sy = (Student*)y;
	return sx->score < sy->score ? 1 : 0;   //字符串排序和字符排序不再列举
}

int main()
{
	int arr[] = { 8, 3, 9, 5, 7, 4, 6 };
	int len = sizeof(arr) / sizeof(arr[0]);
	bubbleSortGeneral(arr, len, sizeof(arr[0]),cmpint);


	Student students[] = {
		{ 1, "张三", 96 },
		{ 2, "李四", 90 },
		{ 3, "王五", 94 },
		{ 4, "赵六", 98 },
		{ 5, "陈七", 94 },
	};
	int n = sizeof(students) / sizeof(students[0]);
	bubbleSortGeneral(students->score, n, sizeof(students->score), cmpStudent);
	return 0;
}

上述代码中,在交换过程中我们对数组进行强制类型转换用到的算法:char* p1 = carr + (cur - 1)*unitlen;是怎么回事呢?来看下图:
在这里插入图片描述

当类型为int时,想要取到数组第n个数只要数组名+n即可,但转为char后,数组中每个元素大小只为1个字节,而整数之间地址相差4个字节,迫于无奈,我们只好用数组名+(n每个元素大小)了

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值