一.回调函数:

在介绍qsort函之前,先简单说明回调函数的概念;

回调函数是什么呢?通俗来讲,就是通过函数指针调用函数

=>如果你把函数的地址作为参数传递给另一个函数,当这个指针被回来调用其所指向的函数时,被调用的函数就是回调函数。

举一个例子来解释概念,仍然是之前提到的简易计算器:

int Add(int x, int y)
{
	return x + y;
}
int Sub(int x, int y)
{
	return x - y;
}
int Mul(int x, int y)
{
	return x * y;
}
int Div(int x, int y)
{
	return x / y;
}
void menu()
{
	printf("********************\n");
	printf("*** 1.add  2.sub ***\n");
	printf("*** 3.mul  4.div ***\n");
	printf("*** 0.exit       ***\n");
	printf("********************\n");
}

int main()
{
	int input = 0;
	int x = 0;
	int y = 0;
	int ret = 0;
	//函数指针数组 -- 转移表
	int (*pfArr[5])(int, int) = { 0,Add,Sub,Mul,Div };
	do
	{
		menu();
		printf("请选择:>");
		scanf("%d", &input);
		if (input > 0 && input <= 4)
		{
			printf("请输入两个操作数:>");
			scanf("%d %d", &x, &y);
			ret = pfArr[input](x, y);
			printf("%d\n", ret);
		}
		else if (input == 0)
			printf("退出计算器\n");
		else
			printf("输入错误\n");
	} while (input);
	return 0;
}


//实现计算器
int Add(int x, int y)
{
	return x + y;
}
int Sub(int x, int y)
{
	return x - y;
}
int Mul(int x, int y)
{
	return x * y;
}
int Div(int x, int y)
{
	return x / y;
}
void menu()
{
	printf("********************\n");
	printf("*** 1.add  2.sub ***\n");
	printf("*** 3.mul  4.div ***\n");
	printf("*** 0.exit       ***\n");
	printf("********************\n");
}

void Calc(int(*pf)(int, int))  
{
	int x = 0;
	int y = 0;
	int ret = 0;
	printf("请输入两个操作数:>");
	scanf("%d %d", &x, &y);
	ret =pf(x, y);    
	printf("%d\n", ret);
}

int main()
{
	int input = 0;
	
	//函数指针数组 -- 转移表
	int (*pfArr[5])(int, int) = { 0,Add,Sub,Mul,Div };
	do
	{
		menu();
		printf("请选择:>");
		scanf("%d", &input);
		switch (input)
		{
		case 1:
			Calc(Add);//pf中间商 
			break;
		case 2:
			Calc(Sub);
			break;
		case 3:
			Calc(Mul);
			break;
		case 4:
			Calc(Div);
			break;
		case 0:
			printf("退出计算器\n");
			break;
		default:
			printf("输入错误\n");
			break;
		}
	} while (input);
	return 0;
}
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.
  • 21.
  • 22.
  • 23.
  • 24.
  • 25.
  • 26.
  • 27.
  • 28.
  • 29.
  • 30.
  • 31.
  • 32.
  • 33.
  • 34.
  • 35.
  • 36.
  • 37.
  • 38.
  • 39.
  • 40.
  • 41.
  • 42.
  • 43.
  • 44.
  • 45.
  • 46.
  • 47.
  • 48.
  • 49.
  • 50.
  • 51.
  • 52.
  • 53.
  • 54.
  • 55.
  • 56.
  • 57.
  • 58.
  • 59.
  • 60.
  • 61.
  • 62.
  • 63.
  • 64.
  • 65.
  • 66.
  • 67.
  • 68.
  • 69.
  • 70.
  • 71.
  • 72.
  • 73.
  • 74.
  • 75.
  • 76.
  • 77.
  • 78.
  • 79.
  • 80.
  • 81.
  • 82.
  • 83.
  • 84.
  • 85.
  • 86.
  • 87.
  • 88.
  • 89.
  • 90.
  • 91.
  • 92.
  • 93.
  • 94.
  • 95.
  • 96.
  • 97.
  • 98.
  • 99.
  • 100.
  • 101.
  • 102.
  • 103.
  • 104.
  • 105.
  • 106.
  • 107.
  • 108.
  • 109.
  • 110.
  • 111.
  • 112.
  • 113.
  • 114.
  • 115.
  • 116.
  • 117.
  • 118.
  • 119.
  • 120.
  • 121.
  • 122.
  • 123.
  • 124.
  • 125.
  • 126.

这段代码中:

##指针-qsort函数_函数指针

将Add的地址作为参数传给Calc函数,pf为函数指针,此时存放Add的地址##指针-qsort函数_函数指针_02

用地址pf 来调用函数Add,完成加法计算,此时Add函数就被称作回调函数

##指针-qsort函数_函数指针_03

注:回调函数不是由该函数的实现方直接调用的,而是在特定的事件或条件发生时由另一方调用的,用于对该事件或条件进行响应。(如果不理解可以看看上面已经以Add函数的解释,就是说我们再进行加法运算时,没有直接调用Add函数,而是用了一个中间商pf来间接调用Add达到我们想要的效果)

二.qsort函数

1.引入:我们大概都了解过一种排序方法”冒泡排序”,它可以对整型数据进行排序,但如果我们想对字符进行排序?想对结构体进行排序呢?

2.介绍:qsort是一个卡库函数,它可以对任何数据进行排序。

我们先来看qsort的使用:

##指针-qsort函数_回调函数_04

这里:base 指向待排序数据的第一个数据;

         num是base中的第一个元素;

         size指向数组中每个元素的大小;

         compar是一个函数指针 - 传递函数地址(compar函数由自己实现)

通过compar函数的返回值记录此元素和下一个元素的大小关系:

##指针-qsort函数_数据排序_05

看cplusplus.com给出的示例,通过qsort函数对数组中的数据进行了排序

##指针-qsort函数_函数指针_06

扩展:泛型编程:编程范式,允许编写可以处理多种的数据类型的代码。无需知道具体的数据类型。

三.qsort的模拟实现

为了更好地了解qsort函数,让我们通过上述的使用方法介绍尝试模拟写出qsort函数。

1.首先写排序部分也就是模拟qsort函数部分的代码

需要考虑这几个问题:

(1) 假如是int,在比较时怎么知道四个字节里存的是一个变量?

(2)指向变量起始地址,计算机是怎么确定需要的内容占了几个字节?

(3)假如说int类型,起始地址只有一个字节,计算机怎么知道存在这四个字节里的东西是一个变量的?

//交换元素部分
void Swap(char* buf1, char* buf2, size_t width,size_t sz)
{
	int i = 0;
	for (i = 0; i < width; i++)//一个字节一个字节交换
	{
		int tem = *buf1;
		*buf1 = *buf2;
		*buf2 = tem;

		buf1++;
		buf2++;
	}
}
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
//由于对排序的数据类型是未知的,使用void* 来接受数据的地址,函数cmp的两个参数同样是void*类型
//模拟实现
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, sz);
			}
		}
	}
}
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.
  • 21.
  • 22.

上面提到的三个问题:

如果我们将每个void*类型的地址都强制类型转换成char* ,而我们又知道每个元素的大小,再用大小限制数据的大小 ,这也是参数width存在的意义,

·- 我们在交换元素时,一个字节一个字节的交换,交换width次

·- 寻找下一个元素时,跳过1*width(或者2*width...)个字节

这样就可以达到对不同类型元素排序的效果。

2.有数据的完整代码:

#include<stdio.h> 
#include<string.h>
//定义结构体
struct stu
{
	char name[20];
	int age;
};
//对排序好的数据进行打印
void print_name(struct stu arr[])
{
	int i = 0;
	for (i = 0; i < 3; i++)
	{
		printf("%s ", (arr[i].name));
	}
}
void print_age(struct stu arr[])
{
	int i = 0;
	for (i = 0; i < 3; i++)
	{
		printf("%d ", (arr[i].age));
	}
}
void print_arr(int* arr, int sz)
{
	int i = 0;
	for (i = 0; i < sz; i++)
	{
		printf("%d ", *(arr + i));
	}
}

//交换部分代码
void Swap(char* buf1, char* buf2, size_t width,size_t sz)
{
	int i = 0;
	for (i = 0; i < width; i++)//一个字节一个字节交换
	{
		int tem = *buf1;
		*buf1 = *buf2;
		*buf2 = tem;

		buf1++;
		buf2++;
	}
}
//字符串 结构体test2
int  cmp_stu_by_name(void* p1, void* p2)
{
	return strcmp(((struct stu*)p1)->name, ((struct stu*)p2)->name);
}
//整型
int cmp_int(const void* p1, const void* p2)
{
	return (*(int*)p1) - (*(int*)p2);
}
//
int cmp_age(const void* p1, const void* p2)
{
	return (((struct stu*)p1)->age - ((struct stu*)p2)->age);
}
//模拟qsort部分
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, sz);
			}
		}
	}
}
//需要排序的数据
void test4()
{
	int arr[] = { 3,1,2,7,8,9,0,6,4,5 };
	int sz = sizeof(arr) / sizeof(arr[0]);
	bubble_sort(arr, sz, sizeof(arr[0]), cmp_int);
	print_arr(arr, sz);
}
void test3()
{
	struct stu arr[] = { {"zhangsan",20},{"lisi",30},{"wangwu",18} };
	int sz = sizeof(arr) / sizeof(arr[0]);
	bubble_sort(arr, sz, sizeof(arr[0]), cmp_age);
	print_age(arr);
}
void test2()
{
	struct stu arr[] = { {"zhangsan",20},{"lisi",30},{"wangwu",18} };
	int sz = sizeof(arr) / sizeof(arr[0]);
	bubble_sort(arr, sz, sizeof(arr[0]), cmp_stu_by_name);	
	print_name(arr);
}

int main()
{
	//qsort(arr, sz, sizeof(arr[0]), cmp_stu_by_age);
	//排序-->正序
	test4();//整型
	printf("\n");
	test3();//结构体_按年龄排序
	printf("\n");
	test2();//字符串_按名字首字母排序
	printf("\n");
	
	return 0;
}
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.
  • 21.
  • 22.
  • 23.
  • 24.
  • 25.
  • 26.
  • 27.
  • 28.
  • 29.
  • 30.
  • 31.
  • 32.
  • 33.
  • 34.
  • 35.
  • 36.
  • 37.
  • 38.
  • 39.
  • 40.
  • 41.
  • 42.
  • 43.
  • 44.
  • 45.
  • 46.
  • 47.
  • 48.
  • 49.
  • 50.
  • 51.
  • 52.
  • 53.
  • 54.
  • 55.
  • 56.
  • 57.
  • 58.
  • 59.
  • 60.
  • 61.
  • 62.
  • 63.
  • 64.
  • 65.
  • 66.
  • 67.
  • 68.
  • 69.
  • 70.
  • 71.
  • 72.
  • 73.
  • 74.
  • 75.
  • 76.
  • 77.
  • 78.
  • 79.
  • 80.
  • 81.
  • 82.
  • 83.
  • 84.
  • 85.
  • 86.
  • 87.
  • 88.
  • 89.
  • 90.
  • 91.
  • 92.
  • 93.
  • 94.
  • 95.
  • 96.
  • 97.
  • 98.
  • 99.
  • 100.
  • 101.
  • 102.
  • 103.
  • 104.
  • 105.
  • 106.
  • 107.
  • 108.
  • 109.
  • 110.
  • 111.
  • 112.
  • 113.
  • 114.
  • 115.
  • 116.
  • 117.