• C语言进阶指针(3) ——qsort的实现


    大家好,我们今天来学习回调函数qsort的实现。

    在这里插入图片描述
    首先让我们打开cplusplus.com找到qsort函数。

    在这里插入图片描述
    我们看到这个函数就可以看到它的头文件和参数信息。

    #include
    void qsort (void* base, size_t num, size_t size, int (*compar)(const void*,const void*));
    
    • 1
    • 2

    让我们看看它的具体参数信息:
    void* base:待排序数组第一个元素的地址
    size_t num:待排序数组的元素个数
    size_t size:待排序数组中一个元素的大小单位是字节
    int (compar)(const void,const void*)):这里是一个函数指针compar,指向的是一个比较的函数,是用来比较两个元素的,里面要放置的是要比较的两个元素的地址

    我们要学习这个函数的话,我们首先就得来复习下冒泡排序法,这里大家可以参考我之前解析冒泡排序法的博客,我这里就不给大家一 一介绍了。https://editor.csdn.net/md/?articleId=132266676访问这个网址就可以看到我之前写的关于冒泡排序法的博客了。

    我们为什么学会了冒泡排序法依然qsort排序函数呢,那是因为这个函数的优点,它既可以给数组排序也可以给结构体进行排序,但是排序时要注意:

    1. 排序整型数组, 两个整型可以直接使用>比较
    2. 排序结构体数组,两个结构体的数据可能不能直接使用>比较也就是不同类型的数据,比较出大小,方法是有差异的

    我们看到代码—>:

    #include 
    //qosrt函数的使用者得实现一个比较函数
    int int_cmp(const void * p1, const void * p2)
    {
    return (*( int *)p1 - *(int *) p2);
    }
    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]);
    }
    printf("\n");
    return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18

    我们在这里定义了数组arr,里面我们要给它排序成升序,我们只要用引用qsort函数就可以了,首先我们给qsort函数传参,第一个传的就是数组中第一个元素的地址,第二个就是数组中的元素多少,我们用sizeof就可以计算出来sizeof(arr)计算的是整个数组的大小,而sizeof(arr[0])计算的是每个元素的大小,两个值相除那就是这个数组元素的个数,因为这是一个整形数组,所以我们第三个传参传每个元素的大小,只要传sizeof(int)就可以了,最后传的就是一个函数指针的地址了,而函数指针中第一个指针存放的地址当然就是数组首元素的地址,第二个指针存放的就是要和第一个相比较的元素的地址了,我们都了解了,那就来看看程序运行的结果吧。

    在这里插入图片描述
    我们现在已经学会使用了这个函数,那么我们该怎么进行模拟这个函数的使用呢,这就是我们今天要学习的重点了,我们今天就来分享如何用qsort函数对整型数组和结构体变量进行排序。

    首先我们先来模拟实现整形数组升序的实现:

    void print_arr(int arr[], int sz)
    {
    	int i = 0;
    	for (i = 0; i < sz; i++)
    	{
    		printf("%d ", arr[i]);
    	}
    	printf("\n");
    }
    void bubble_sort(void* base, size_t num, size_t size, int (*cmp)(const void* e1, const void*e2))
    {
    	//冒泡排序的趟数
    	int i = 0;
    	for (i = 0; i < num - 1; i++)
    	{
    		//一趟冒泡排序
    		int j = 0;
    		for (j = 0; j < num - 1 - i; j++)
    		{
    			if (arr[j] > arr[j + 1])
    			
    			{
    				//交换
    				int tmp=arr[j];
    				arr[j]=arr[j+1];
    				arr[j+1]=tmp;
    			}
    		}
    	}
    }
    
    void test1()
    {
    	int arr[] = { 0,1,2,3,4,5,6,7,8,9 };//升序
    	//排序为降序
    	int sz = sizeof(arr) / sizeof(arr[0]);
    	print_arr(arr, sz);
    	bubble_sort(arr, sz, sizeof(arr[0]), cmp_int);
    	print_arr(arr, sz);}
    	
    int main()
    {
    test();
    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

    这里我们要注意的是: int (cmp)(const void e1, const void* e2)
    e1是一个指针,存放了一个要比较的元素的地址 e2是一个指针,存放了一个要比较的元素的地址 e1指向的元素>e2指向的元素,返回>0的数字
    e1指向的元素==e2指向的元素,返回0 e1指向的元素

    我们首先定义数组,调用函数test(),就会跳转到函数test中,在这个函数中我们用sz表示数组元素的个数,求解的方法还是sizeof的老方法,我们在将它调用到函数print_arr中对原数组进行打印,再调用函数bubble_sort,在里面我们用循环嵌套进行冒泡排序进行打印就行了。

    那我们怎么来实现结构体的排序呢,因为我们int (cmp)(const void e1, const void*
    e2)这个函数指针中所有的指针都是void* 型,这就相当于一个垃圾桶,可以储存任意类型变量的地址,这对我们实现结构体的排序密不可分。

    我们先定义个结构体变量:

    struct Stu
    {
    	char name[20];//20
    	int age;//4
    };
    void test2()
    {
    	struct Stu arr[] = { {"zhangsan", 20}, {"lisi", 30}, {"wangwu", 15}};
    	int sz = sizeof(arr) / sizeof(arr[0]);//3
    	bubble_sort(arr, sz, sizeof(arr[0]), cmp_stu_by_name);
    }
    int main()
    {
    	//整型数据/字符数据/结构体数据...
    	//可以使用qsort函数对数据进行排序
    	//测试bubble_sort,排序整型数据
    	//test1();
    
    	//测试bubble_sort,排序结构体的数据
    	test2();
    	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

    看到代码块我们发现结构体里面定义了两个类型的变量,一个是名字char型,一个是年龄int型,那么我们对它进行排序的方法就有两种,一种是按照名字排序,一种按照年龄排序。

    int cmp_stu_by_age(const void* e1, const void*e2)
    {
    	return ((struct Stu*)e1)->age - ((struct Stu*)e2)->age;
    }
    
    
    int cmp_stu_by_name(const void* e1, const void* e2)
    {
    	return strcmp(((struct Stu*)e1)->name, ((struct Stu*)e2)->name);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    要注意的是,我们这里指针的类型是void* 类型要强制转换。这是我们两种方法的比较函数,那我们如何进行交换呢,我们就看到我们的交换函数

    void swap(char* buf1, char* buf2, size_t size)
    {
    	int i = 0;
    	for (i = 0; i < size; i++)
    	{
    		char tmp = *buf1;
    		*buf1 = *buf2;
    		*buf2 = tmp;
    		buf1++;
    		buf2++;
    	}
    }
    
    swap((char*)base + j * size, (char*)base + (j + 1) * size, size)
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    我们看到这里的交换函数:

    (char*)base + j * size :确定要交换的第一个数据的地址
    (char*)base + (j + 1) * size :确定要交换的第二个数据的地址
    size :确定这俩个数据直接隔了多少个字节,方便逐字节进行操作

    因为我们的base是char*的指针里面存放的是结构体变量里首元素的地址,size表示的是每个元素的大小,j * size表示的就是跳过多少个元素,而两个元素之间的交换就是字节之间的交换,所以我们算出每个每个元素的大小,指针代表的char * 型的,是一个字节,所以两个元素进行一次交换是进行一个字节之间的交换,所以我们这里只需要用一个循环就可以实现两个元素的交换了。

    接下来我们就来看看完整的代码:

    #include 
    
    void print_arr(int arr[], int sz)
    {
    	int i = 0;
    	for (i = 0; i < sz; i++)
    	{
    		printf("%d ", arr[i]);
    	}
    	printf("\n");
    }
    
    //int (*cmp)(const void* e1, const void* e2)
    //e1是一个指针,存放了一个要比较的元素的地址
    //e2是一个指针,存放了一个要比较的元素的地址
    //e1指向的元素>e2指向的元素,返回>0的数字
    //e1指向的元素==e2指向的元素,返回0
    //e1指向的元素
    //
    
    void swap(char* buf1, char* buf2, size_t size)
    {
    	int i = 0;
    	for (i = 0; i < size; i++)
    	{
    		char tmp = *buf1;
    		*buf1 = *buf2;
    		*buf2 = tmp;
    		buf1++;
    		buf2++;
    	}
    }
    
    
    //泛型编程
    void bubble_sort(void* base, size_t num, size_t size, int (*cmp)(const void* e1, const void*e2))
    {
    	//冒泡排序的趟数
    	int i = 0;
    	for (i = 0; i < num - 1; i++)
    	{
    		//一趟冒泡排序
    		int j = 0;
    		for (j = 0; j < num - 1 - i; j++)
    		{
    			//if (arr[j] > arr[j + 1])
    			if(cmp((char*)base + j * size, (char*)base + (j + 1) * size)>0)
    			{
    				//交换
    				swap((char*)base + j * size, (char*)base + (j + 1) * size, size);
    			}
    		}
    	}
    }
    
    
    int cmp_int(const void*e1, const void*e2)
    {
    	return *(int*)e1 - *(int*)e2;
    }
    
    void test1()
    {
    	int arr[] = { 0,1,2,3,4,5,6,7,8,9 };//升序
    	//排序为降序
    	int sz = sizeof(arr) / sizeof(arr[0]);
    	print_arr(arr, sz);
    	bubble_sort(arr, sz, sizeof(arr[0]), cmp_int);
    	print_arr(arr, sz);
    }	
    
    struct Stu
    {
    	char name[20];//20
    	int age;//4
    };
    
    int cmp_stu_by_age(const void* e1, const void*e2)
    {
    	return ((struct Stu*)e1)->age - ((struct Stu*)e2)->age;
    }
    
    
    int cmp_stu_by_name(const void* e1, const void* e2)
    {
    	return strcmp(((struct Stu*)e1)->name, ((struct Stu*)e2)->name);
    }
    
    void test2()
    {
    	struct Stu arr[] = { {"zhangsan", 20}, {"lisi", 30}, {"wangwu", 15}};
    	int sz = sizeof(arr) / sizeof(arr[0]);//3
    	bubble_sort(arr, sz, sizeof(arr[0]), cmp_stu_by_name);
    }
    
    int main()
    {
    	//整型数据/字符数据/结构体数据...
    	//可以使用qsort函数对数据进行排序
    	//测试bubble_sort,排序整型数据
    	test1();
    
    	//测试bubble_sort,排序结构体的数据
    	test2();
    	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

    这里我们就只需调用函数test2就可以访问了,在test2中我们将所需的参数上传到函数bubble_sort中,这里面我们对两个元素进行比较,调用到函数int cmp_stu_by_age和int cmp_stu_by_name中,在循环里进行比较,传参到函数int cmp_stu_by_age(const void* e1, const voide2)和int cmp_stu_by_name(const void e1, const void* e2)中,如果返回的值大于0就传参到交换函数中进行交换,如果小于0就无需交换,顺着循环和下一个元素进行比较。

    我们知道年龄是整形的比较大小非常的简单,那我们的姓名是char型的,那这个怎么比较大小并且排序呢,因为我们的名字是字符串,所以比较名字之间的大小就是比较字符串之间的大小,那么怎么比较字符串的大小呢?那就是字符串对应位置的Ascll值的大小,如果相等的话就跳到下一个字符相比较,以此类推,直到比较出大小为止。

    让我们看到下面的代码来验证字符串大小的比较:

    #include
    #include
    int main()
    {
    	char arr1[20] = "abc";
    	char arr2[20] = "abe";
    	if (strcmp(arr1, arr2) > 0)
    		printf(">\n");
    	else
    		printf("<=\n");
    
    	return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    在这里插入图片描述
    我们看到图片第一个和第二个字符的Ascll值的大小相同,而第三个位置e>c,所以打印的结果就是<=。
    在这里插入图片描述
    那么大家看到都应该更加深刻的了解了qsort函数的实现吧,今天的分享就到这里,谢谢大家。

  • 相关阅读:
    IPv6 PIRng 和路由手工汇总【下一代互联网04】
    Spring Boot 整合 MyBatis Plus实现多数据源的两种方式
    web前端期末大作业——基于HTML+CSS+JavaScript蓝色的远程监控设备系统后台管理界面模板
    前端性能优化——启用文本压缩
    交叉编译opencv时,无法安装cmake-gui
    【flink-sql实战】flink 主键声明与upsert功能实战
    【Django 05】Django-DRF(ModelViewSet)、路由组件、自定义函数
    租服务器训练深度学习模型
    【答读者问57】backtrader回测的时候出现nan值的时候如何解决
    EasyCode整合mybatis-plus的配置
  • 原文地址:https://blog.csdn.net/Lehjy/article/details/132925423