ICode9

精准搜索请尝试: 精确搜索
首页 > 其他分享> 文章详细

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

2021-06-15 14:32:44  阅读:117  来源: 互联网

标签:arr cur int void 冒泡排序 函数 Student 库函数 进阶


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每个元素大小)了

标签:arr,cur,int,void,冒泡排序,函数,Student,库函数,进阶
来源: https://blog.csdn.net/Diligent_wu/article/details/117694454

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有