C语言指针进阶之四-函数指针的使用之回调函数及库函数qsort函数详解(详解)

目录

1.回调函数

1.1引入

1.2回调函数的使用,加减乘除计算器改造

2.回调函数的应用-qsort函数

实例1:整型数组排序

实例2:结构体排序 

①按照整型数据来排序,这里是按照年龄来排序

②按照名字来排序,也就是比较的是字符串

③strcmp函数的补充

3.qsort库函数的模仿实现

3.1实现问题分析

3.2元素比较分析及实现

3.3排序分析 

3.4实现及源代码

3.4.1实现1整型排序 

3.4.2.1结构体排序实现1-按照整型数据排序

3.4.2.2结构体排序实现1-按照字符串数据排序

4.结语


 

1.回调函数

1.1引入

通过前几天的分享,大家应该对函数指针等有了一定的认识,今天我们要分享的知识就是函数指针的一个经典应用——回调函数。

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

回调函数依赖于函数指针。 

C语言指针进阶之四-函数指针的使用之回调函数及库函数qsort函数详解(详解)_第1张图片

1.2回调函数的使用,加减乘除计算器改造

C语言指针进阶之四-函数指针的使用之回调函数及库函数qsort函数详解(详解)_第2张图片

 在上期的文章中,我们说计算器不用函数指针来实现的时候,代码冗余,除了红色部分,绿色部分的内容几乎是一样的。上期我们使用了回调函数的方法来GANKd掉了Switch语句,现在介绍了回调函数的概念,我们就想能不能这样,我们直接写一个调用函数,将加减乘除函数的地址传给调用函数,冗余的部分就只用在调用函数里写一次就可以了,我们来看一下:

C语言指针进阶之四-函数指针的使用之回调函数及库函数qsort函数详解(详解)_第3张图片

我们的switch部分就可以这样:

 C语言指针进阶之四-函数指针的使用之回调函数及库函数qsort函数详解(详解)_第4张图片

就少了很多的代码冗余。

2.回调函数的应用-qsort函数

用于排序

回顾一下冒泡排序:C语言详解冒泡排序

我们写的这个冒泡排序只适合于我们的整型数据,如果我要排序结构体类型或者其他类型就做不到,所以这种排序是存在缺陷的。 

接下来我们来看一下库函数qsort

qsort函数的特点:

1.采用的快速排序的方法

2.适合于任意类型的数据的排序

 让我们具体来学习一下这个函数:

C语言指针进阶之四-函数指针的使用之回调函数及库函数qsort函数详解(详解)_第5张图片

 Sort elements of array:排序数组中的元素

翻译:排序由base指向的num个元素,每个元素是size个字节长度,使用comper指针指向的这个函数去比较

 C语言指针进阶之四-函数指针的使用之回调函数及库函数qsort函数详解(详解)_第6张图片

函数的头文件为: 

#include    

函数的参数为:(size_t为无符号整型的意思)

void qsort (
void* base, 指向要被排序这个数组的第一个元素的地址。
size_t num, 要被排序的元素的数目
size_t size, 每个元素的大小
int (*compar)(const void*,const void*):函数指针类型,一个指针指向的比较函数,能够比较base指向数组中的两个元素

两个指针参数,p1指向的元素的值小于P2指向的元素的值返回一个小于0的数字,p1指向的元素的值等于P2指向的元素的值返回一个0,p1指向的元素的值大于P2指向的元素的值返回一个大于0的数字
(这个比较函数要自己写)
);

接下来我们就来使用一下这个排序函数:

实例1:整型数组排序

C语言指针进阶之四-函数指针的使用之回调函数及库函数qsort函数详解(详解)_第7张图片

我们对第一个参数进行解引用,我们想的是直接通过解引用来操作比较,但是却报错了,这是为什么呢?

补充:
1.void * 类型的指针是不能直接解引用操作的

2.也不能直接进行指针运算

3.void* 就是一种通用类型的指针,无具体类型的指针

这怎么理解呢,像我们平时使用这种

int  a = 10;

int *pa = &a;

这种就是平时的使用

但是如果我这样写:

char * pc = &a;这样写是可以但是会有woring,类型不兼容

但是如果写出

void *  pe = &a就没有问题,

所以void* 类型的指针可以接受任意类型的地址

在Qsort函数中,编者也不知道未来会传什么样的地址进来,或者数组或者结构体,所以就写成了void*

4.需要用的时候要强制类型转换

解决了void * 的问题过后,我们现在来看一下实例1的实现

#include
#include
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]);
	}
}
void 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);
	print(arr, sz);
}
int main()
{
	test1();
	return 0;
}

C语言指针进阶之四-函数指针的使用之回调函数及库函数qsort函数详解(详解)_第8张图片

关于排序结果是升序还是降序,我们自己通过这个函数11能够操作的部分只有这里

C语言指针进阶之四-函数指针的使用之回调函数及库函数qsort函数详解(详解)_第9张图片

因为内部是如何实现的我们不知道。

实例2:结构体排序 

①按照整型数据来排序,这里是按照年龄来排序
struct Stu
{
	char name[20];
	int age;
};
void print2(struct Stu* p1, int sz)
{
	int i = 0;
	for (i = 0; i < sz; i++)
	{

		printf("%s,%d \n ", (p1+i)->name, (p1+i)->age);
		
	}
}
int  cmp_byage(const void* p1, const void* p2)
{
	return ((struct Stu*)p1)->age - ((struct Stu*)p2)->age;
}
void test2()
{
	struct Stu arr[] = { {"zhangsan",20},{"lisi",27},{"wangwu",8} };
	int sz = sizeof(arr) / sizeof(arr[0]);
	qsort(arr, sz, sizeof(arr[0]), cmp_byage);
	print2(arr, sz);


}

int main()
{
	//test1();
	test2();
	
	return 0;
}

C语言指针进阶之四-函数指针的使用之回调函数及库函数qsort函数详解(详解)_第10张图片 

这是升序比较结果。 

注意一点:当我们使用年龄比较的时候我们就可以使用算术运算来作为比较返回值,但是如果我们使用字符串来比较,比如使用结构体当中的名字作为比较标准,在比较函数中就需要实用strcmp来比较两个字符串。 

②按照名字来排序,也就是比较的是字符串
struct Stu
{
	char name[20];
	int age;
};
void print2(struct Stu* p1, int sz)
{
	int i = 0;
	for (i = 0; i < sz; i++)
	{

		printf("%s,%d \n ", (p1+i)->name, (p1+i)->age);
		
	}
}
int  cmp_byage(const void* p1, const void* p2)
{
	return ((struct Stu*)p1)->age - ((struct Stu*)p2)->age;
}
int  cmp_byname(const void* p1, const void* p2)
{
	return strcmp(((struct Stu*)p1)->name ,((struct Stu*)p2)->name);


}
void test2()
{
	struct Stu arr[] = { {"zhangsan",20},{"lisi",27},{"wangwu",8} };
	int sz = sizeof(arr) / sizeof(arr[0]);
	//qsort(arr, sz, sizeof(arr[0]), cmp_byage);
	qsort(arr, sz, sizeof(arr[0]), cmp_byname);
	print2(arr, sz);


}

int main()
{
	//test1();
	test2();
	
	return 0;
}

排序结果为:

C语言指针进阶之四-函数指针的使用之回调函数及库函数qsort函数详解(详解)_第11张图片

③strcmp函数的补充

这里的字符串使用strcmp函数来比较,因为这个函数返回值和qsort函数要我们自己实现的比较函数的返回值期望是一模一样的。其引用头文件为:string.h

C语言指针进阶之四-函数指针的使用之回调函数及库函数qsort函数详解(详解)_第12张图片

3.qsort库函数的模仿实现

3.1实现问题分析

因为目前还没有接触到快速排序的算法,所以我们这里使用冒泡排序的思想,实现一个功能类似Qsort的函数。

函数功能分析:

1.使用冒泡排序思想

2.适用于任意类型数据的排序。

3.要仿照库函数改造冒泡排序的参数:使用void*base来知道要排序数据的起始位置,num来告诉元素个数,size来知道一个元素有多大。这是参数部分

4.进入实现部分我们想一下,冒泡排序对于不同的数据类型,比如结构体类型或者整型,我们排序的次数和数据之间比较的次数在元素个数相同的情况下是不变的,唯一不同的就是比较方式:整型使用大于、小于、等于比较,但是两个结构体比较的话有可能比较整型又可以比较浮点型,就不能简单用大于小于来比较。那这个比较实现,我们的解决办法是:

比较的方式方法,由外部传入,在我们的函数里面定义一个函数指针来接收一个比较函数的地址,如何比较两个数据的方式方法由用户传入,用户需要怎么比较我们就调用这个比较例子方式来用。 由于我们也不知道要比较的数据是什么类型所以就使用void *类型作为参数,然后两个指针指向的参数只用于比较,不需要修改所以可以用const来修饰

   有了这些分析,我们来实现一下看,图中遇到问题我们一起分析:

 C语言指针进阶之四-函数指针的使用之回调函数及库函数qsort函数详解(详解)_第13张图片

3.2元素比较分析及实现

按照上述的分析,我们来到了比较元素的这一步,我们来想一下是如何进行比较的。

在对数组元素进行比较的时候,我们知道两个元素两两相比较,是不是比较的是两个地址指向的内容,那么我们这里并不是数组,我们如何根据已知条件比较第j个元素和第J+1个元素呢? 

C语言指针进阶之四-函数指针的使用之回调函数及库函数qsort函数详解(详解)_第14张图片 

那么第j个元素和第j+1个元素的地址一定通过这个base来获得,可以使用base+吗?,显然不行,因为base的类型是void*,不支持算数运算,那只有强制类型转换,转换为那个类型呢,结构体指针还是整型指针?我们发现将它转换为字符类型指针是最为方便的,算数运算可以访问一个自己的元素,当我们要访问第j个元素的时候就可以这样来写:

(char*)base+j*size 

访问第j+1个元素可以这样实现

(char*)base+(j+1)*size 

比较方式由用户传入就可以了。

3.3排序分析 

假设要升序排序,cmp返回大于0,前面大于后面就需要交换
                //如何实现交换呢?
  并不能直接交换因为未来可能传入的数据类型不一样,这里没有办法定义中间的交换变量的类型,那么要交换的数据的地址和数据大小都知道,交换下标为j和j+1的元素,size是以字节为单位,我们可以以字节为单位来交换地址中的内容
                //因为指针变量被强制类型转换了,也是char,解引用访问是1个字节。 我们可以定义char 类型的中间变量来实现交换每个字节的内容就好了,实现如下:

void Swap(char* buf1, char* buf2, int size)
{
	int i = 0;
	char temp = 0;
	for (i = 0; i < size; i++)
	{
		temp = *buf1;
		*buf1 = *buf2;
		*buf2 = temp;
		buf1++;
		buf2++;
	}
}

3.4实现及源代码

3.4.1实现1整型排序 

#include

int  cmp_int(const void* p1, const void* p2)
{
	return *(int*)p1 - *(int*)p2;
}
void Swap(char* buf1, char* buf2, int size)
{
	int i = 0;
	char temp = 0;
	for (i = 0; i < size; i++)
	{
		temp = *buf1;
		*buf1 = *buf2;
		*buf2 = temp;
		buf1++;
		buf2++;
	}
}

void bubble_sort(void* base,int num,int size,int (*cmp)(const void*,const void*))
{
	int i = 0;//趟数
	for (i = 0; i < num-1; i++)
	{
		int j = 0;
		for (j = 0; j < num-1-i; j++)
		{
			if (cmp((char*)base + j*size,(char*)base + (j + 1) * size)>0)
			{
				//假设要升序排序,cmp返回大于0,前面大于后面就需要交换
				//如何实现交换呢?
				//地址和数据大小都需要知道,交换下标为j和j+1的元素,size是以字节为单位,我们以字节为单位来交换地址中的内容
				//因为指针变量也是char,解引用访问是1个字节。
				Swap((char*)base + j * size, (char*)base + (j + 1) * size, size);
			}
		}
	}

}


//测试bubble_sort 排序整型数据
void test1()
{
	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);
	int i = 0;
	for (i = 0; i < sz; i++)
	{
		printf("%d ", arr[i]);
	}
}
int main()
{
	test1();
	return 0;
}

 C语言指针进阶之四-函数指针的使用之回调函数及库函数qsort函数详解(详解)_第15张图片

3.4.2.1结构体排序实现1-按照整型数据排序

 

void Swap(char* buf1, char* buf2, int size)
{
	int i = 0;
	char temp = 0;
	for (i = 0; i < size; i++)
	{
		temp = *buf1;
		*buf1 = *buf2;
		*buf2 = temp;
		buf1++;
		buf2++;
	}
}

void bubble_sort(void* base,int num,int size,int (*cmp)(const void*,const void*))
{
	int i = 0;//趟数
	for (i = 0; i < num-1; i++)
	{
		int j = 0;
		for (j = 0; j < num-1-i; j++)
		{
			if (cmp((char*)base + j*size,(char*)base + (j + 1) * size)>0)
			{
				//假设要升序排序,cmp返回大于0,前面大于后面就需要交换
				//如何实现交换呢?
				//地址和数据大小都需要知道,交换下标为j和j+1的元素,size是以字节为单位,我们以字节为单位来交换地址中的内容
				//因为指针变量也是char,解引用访问是1个字节。
				Swap((char*)base + j * size, (char*)base + (j + 1) * size, size);
			}
		}
	}

}


//测试bubble_sort 排序整型数据
//void test1()
//{
//	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);
//	int i = 0;
//	for (i = 0; i < sz; i++)
//	{
//		printf("%d ", arr[i]);
//	}
//}
//排序结构体数据
struct Stu
{
	char name[20];
	int age;
};
int cmp_byage(const void* p1, const void* p2)
{
	return ((struct Stu*)p1)->age - ((struct Stu*)p2)->age;


}
void test2()
{
	struct Stu arr1[] = { {"zhangsan",20},{"lisi",27},{"wangwu",8} };
	int sz = sizeof(arr1) / sizeof(arr1[0]);
	
	bubble_sort(arr1,sz,sizeof(arr1[0]),cmp_byage);
	int i = 0;
	for (i = 0; i < sz; i++)
	{

		printf("%s,%d \n ",arr1[i].name,arr1[i].age);

	}
}

int main()
{
	//test1();
	test2();
	return 0;
}

排序结果为:

C语言指针进阶之四-函数指针的使用之回调函数及库函数qsort函数详解(详解)_第16张图片 

3.4.2.2结构体排序实现1-按照字符串数据排序

void Swap(char* buf1, char* buf2, int size)
{
	int i = 0;
	char temp = 0;
	for (i = 0; i < size; i++)
	{
		temp = *buf1;
		*buf1 = *buf2;
		*buf2 = temp;
		buf1++;
		buf2++;
	}
}

void bubble_sort(void* base,int num,int size,int (*cmp)(const void*,const void*))
{
	int i = 0;//趟数
	for (i = 0; i < num-1; i++)
	{
		int j = 0;
		for (j = 0; j < num-1-i; j++)
		{
			if (cmp((char*)base + j*size,(char*)base + (j + 1) * size)>0)
			{
				//假设要升序排序,cmp返回大于0,前面大于后面就需要交换
				//如何实现交换呢?
				//地址和数据大小都需要知道,交换下标为j和j+1的元素,size是以字节为单位,我们以字节为单位来交换地址中的内容
				//因为指针变量也是char,解引用访问是1个字节。
				Swap((char*)base + j * size, (char*)base + (j + 1) * size, size);
			}
		}
	}

}


//测试bubble_sort 排序整型数据
//void test1()
//{
//	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);
//	int i = 0;
//	for (i = 0; i < sz; i++)
//	{
//		printf("%d ", arr[i]);
//	}
//}
//排序结构体数据
struct Stu
{
	char name[20];
	int age;
};
int cmp_byage(const void* p1, const void* p2)
{
	return ((struct Stu*)p1)->age - ((struct Stu*)p2)->age;


}
int  cmp_byname(const void* p1, const void* p2)
{
	return strcmp(((struct Stu*)p1)->name, ((struct Stu*)p2)->name);
}
void test2()
{
	struct Stu arr1[] = { {"zhangsan",20},{"lisi",27},{"wangwu",8} };
	int sz = sizeof(arr1) / sizeof(arr1[0]);
	
	//bubble_sort(arr1,sz,sizeof(arr1[0]),cmp_byage);
	qsort(arr1, sz, sizeof(arr1[0]), cmp_byname);
	int i = 0;
	for (i = 0; i < sz; i++)
	{

		printf("%s,%d \n ",arr1[i].name,arr1[i].age);

	}
}

int main()
{
	//test1();
	test2();
	return 0;
}

 排序结果为

C语言指针进阶之四-函数指针的使用之回调函数及库函数qsort函数详解(详解)_第17张图片

4.结语

以上就是本期的所有内容,知识含量蛮多,大家可以配合解释和原码运行理解。创作不易,大家如果觉得还可以的话,欢迎大家三连,有问题的地方欢迎大家指正,一起交流学习,一起成长,我是nicn,正在c++方向前行的新手,感谢大家的关注与喜欢。

你可能感兴趣的:(c语言,开发语言,数据结构,算法,c++,排序算法)