qsort函数的模拟和使用(两万字详解)

news2024/10/7 3:29:27

 qsort 的使用(回调函数结构体指针的总和运用)

qsort的作用 

qsort--用来排序的

库函数,直接可是用来排序数据

底层使用的是快速排序的方式 

—————————————————————————————————————————————————————————————————————————————————————— 

复习冒泡排序

排序分为几种方式

复习冒泡排序

冒泡排序的思想

冒泡排序

每次循环的次数减少

打印

图解

这个代码存在问题 因为只能排序整形 字符等等是不能排序是 也就是说

有局限性 所以有点问题

指针篇章-(冒泡排序详解)-CSDN博客icon-default.png?t=N7T8https://blog.csdn.net/Jason_from_China/article/details/136581549 

这里是写过的冒泡排序的详解 想要详细代码的同学可以去看一下

—————————————————————————————————————————————————————————————————————————————————————— 

qsort存在的意义

qsort是C语言中的一个标准库函数,用于对数组进行快速排序。

它的存在有以下几个意义:

1. 提供高效的排序算法:qsort使用快速排序算法,这是一种高效的排序算法,平均时间复杂度为O(nlogn),能够在大多数情况下快速地对数组进行排序。

2. 通用性:qsort是一个通用的排序函数,可以用于对各种类型的数组进行排序,只需要提供相应的比较函数即可。这使得它在处理不同类型的数据时非常方便。

3. 灵活性:通过提供自定义的比较函数,可以根据不同的需求对数组进行排序。比如可以按照升序或降序排列,也可以按照自定义的规则进行排序。

4. 标准化:qsort是C语言标准库中的函数之一,它的存在使得程序员可以直接使用标准库提供的排序功能,而无需自己实现排序算法。这样可以提高代码的可读性和可维护性。

——————————————————————————————————————————————————————————————————————————————————————

qsort的语法格式

void qsort(void *base, size_t num, size_t width, int (*compar)(const void *, const void *));

void *base:指向要排序的数组的指针。数组的元素类型必须是可以通过指针进行比较的。
size_t num:要排序的元素数量。
size_t width:每个元素的大小(以字节为单位)。
int (*compar)(const void *, const void *):比较函数,用于指定如何比较两个元素。这个函数应该返回以下值之一:
如果第一个参数应该排在第二个参数前面,则返回一个小于零的值。
如果两个参数相等,则返回零。
如果第一个参数应该排在第二个参数后面,则返回一个大于零的值。
比较函数的类型是 int (*)(const void *, const void *),这意味着它是一个接受两个指向要比较的元素的指针,并返回整数的函数。
下面是一个使用 qsort 的简单例子,展示了如何对一个整数数组进行排序:

#include <stdio.h>
#include <stdlib.h>

// 比较函数
int compare(const void *a, const void *b) {
    return (*(int *)a - *(int *)b); // 升序排序
}

int main() {
    int arr[] = {3, 1, 4, 1, 5, 9};
    int n = sizeof(arr) / sizeof(arr[0]);

    qsort(arr, n, sizeof(int), compare);

    for (int i = 0; i < n; i++) {
        printf("%d ", arr[i]);
    }

    return 0;
}

 在上述代码中,我们定义了一个比较函数 compare,它根据整数值进行升序排序。然后我们调用 qsort 函数,使用我们的比较函数对数组 arr 进行排序。最后,我们遍历排序后的数组并打印出来。

 —————————————————————————————————————————————————————————————————————————————————————

qsort函数的原型 (语法格式详解)

需要查找函数的,这两个网站很好用

 C 标准库头文件 - cppreference.com

C library - C++ Reference (cplusplus.com) 

四个参数 base num size compar 所以这个是一个函数指针

四个参数

base(基础(基础地质))是一个指针 指向的是待排序的数组的第一个元素

num(元素的个数)base指向数组待排序的个数

size(大小)base指向元素待排序的大小

compar()计算函数->计算函数里面 还有一个交换函数

如果要进行排序优化那什么是修改的什么不是修改的

也就是 还是冒泡排序的话 比较方式会进行改变

所以由此得知qsort就是两个函数的比较函数

我们是qsor的使用者

——————————————————————————————————————————————————————————————————————————————————————

qsort的使用(整形的使用)

e1 e2 分别是 第一个需要比较元素的地址和第二个需要比较元素的地址

之前讲到过 void*类型的指针是没有类型的指针 这种类型的指针是不能直接解引用 也不能进行加减整数的运算

所以是不对的

但是我们可以强制类型转化为整形

因为我们明确的知道是两个整形

完整代码

计算的逻辑 

    //这里首先进行了强制类型转换,将传入的void指针转换为int指针。
    // 因为在C语言中,函数的参数是通过值传递的方式传递的,
    // 这里传递的是指针的值(即指针的地址),而不是指针所指向的数据。
    // 因此,需要对传入的指针进行解引用(通过类型转换为int*),以获取它们所指向的实际整数值。

The element pointed to by p1 goes before the element pointed to by p2
The element pointed to by p1 is equivalent to the element pointed to by p2
The element pointed to by p1 goes after the element pointed to by p2
p1所指向的元素在p2所指向的元素之前
p1所指向的元素等价于p2所指向的元素
p1所指向的元素在p2所指向的元素之后 

qsort

包含的头文件

代码简化

这里采取指针-指针的运算 之前讲过指针-指针 在指针(1)里面

这里是在同一个地址空间

如果是倒着排序

就反过来

所以回调函数再次产生

 整形函数的代码

#define _CRT_SECURE_NO_WARNINGS 1
#include<stdio.h>
#include<assert.h>
#include<string.h>
#include<stdlib.h>
//打印函数
//打印整形函数
void Printint(int arr[], size_t sz)
{
	for (int i = 0; i < sz; i++)
	{
		printf("%d ", arr[i]);
	}
}
//1 整形数组的使用  qsort函数的使用,整形的计算(回调函数的使用)
int compute1(const void* e1, const void* e2)
{
	return *(int*)e1 - *(int*)e2;
	//这里解释一下 首先这里是强制类型转化成指针类型
	// 
	//这里首先进行了强制类型转换,将传入的void指针转换为int指针。
    // 因为在C语言中,函数的参数是通过值传递的方式传递的,
    // 这里传递的是指针的值(即指针的地址),而不是指针所指向的数据。
    // 因此,需要对传入的指针进行解引用(通过类型转换为int*),以获取它们所指向的实际整数值。
	// 
	// 对于计算方式是有隐含的计算方式的 图片已经进行说明,这里不进行多余的解释
	//如果返回值小于0,则表示e1应该排在e2之前。
	//如果返回值等于0,则表示e1和e2的顺序不变。
	//如果返回值大于0,则表示e1应该排在e2之后。
}
void test1()
{
	printf("qsort函数的使用,整形的计算(回调函数的使用)>\n");
	int arrint[] = { 1,3,7,3,5,9,0,2,5,7,0,1,4,6 };
	int sz = sizeof(arrint) / sizeof(arrint[0]);
	qsort(arrint, sz, sizeof(arrint[0]), compute1);
	Printint(arrint, sz);
	printf("\n\n");
}
//主函数 负责函数的调用
int main()
{
	test1();
}

 

————————————————————————————————————————————————————————————————————————————————————— 

qsort的使用 (字符)

字符的使用和整形的大致是一样的 这里不做图解了

直接代码解释 

#define _CRT_SECURE_NO_WARNINGS 1
#include<stdio.h>
#include<assert.h>
#include<string.h>
#include<stdlib.h>
//打印字符函数
void Printchar(char *arr, size_t sz)
{
	for (int i = 0; i < sz; i++)
	{
		printf("%c ", arr[i]);
	}
}
2 字符数组的使用  qsort函数的使用,字符的计算(回调函数的使用)
char compute2(const void* e1, const void* e2)
{
	return *(char*)e1 - *(char*)e2;//和整形同理
	//这里解释一下 首先这里是强制类型转化成指针类型
    // 
    //这里首先进行了强制类型转换,将传入的void指针转换为char指针。
    // 因为在C语言中,函数的参数是通过值传递的方式传递的,
    // 这里传递的是指针的值(即指针的地址),而不是指针所指向的数据。
    // 因此,需要对传入的指针进行解引用(通过类型转换为char*),以获取它们所指向的实际整数值。
    // 
    // 对于计算方式是有隐含的计算方式的 图片已经进行说明,这里不进行多余的解释
    //如果返回值小于0,则表示e1应该排在e2之前。
    //如果返回值等于0,则表示e1和e2的顺序不变。
    //如果返回值大于0,则表示e1应该排在e2之后。
}
void test2()
{
	printf("qsort函数的使用,字符的计算(回调函数的使用)>\n");
	char arrchar[] = "sdhngkfuhsah";
	size_t sz = strlen(arrchar);
	qsort(arrchar, sz, sizeof(arrchar[0]), compute2);
	Printchar(arrchar, sz);
	printf("\n\n");
}
//主函数 负责函数的调用
int main()
{
	test1();
	test2();

}

——————————————————————————————————————————————————————————————————————————————————————

qsort的使用(结构体)

结构体不明白的同学,可以看一下这一篇文章 

结构体详解-CSDN博客icon-default.png?t=N7T8https://blog.csdn.net/Jason_from_China/article/details/136477956

20数组二十个元素

这里的问题是怎么样进行排序

是年龄还是名字 肯定是年龄

那么继续是qsort 和回调函数

但是进行计算的时候他不知道这个是结构体

所以需要强制类型转化

结构体里面 需要知道不管是名字 还是字母,本质就是字符串

两个字符串的比较需要strcmp

strcmp需要头文件 string.h

强制类型转换之后 再进行这个strcmp进行比较

 这个强制类型转化是临时的

不理解强制类型转化的 可以去看看这个文章C语言——强制类型转化-CSDN博客icon-default.png?t=N7T8https://blog.csdn.net/Jason_from_China/article/details/136583214

strcmp的使用规则

需要知道

首先我们创建结构体

这里需要注意 这个结构体 要么进行声明 要么放在使用函数的的上面 不然这个是会显示找不到变量

3 结构体的使用  qsort函数的使用,结构体的计算(回调函数的使用)
struct MyStruct
{
	char name[100];
	int age;
	//这里的结构体需要放在使用函数的上面 算是进行函数声明 只有进行函数的声明之后 才可以进行打印 才可以进行计算
};

 其次创建调用的函数

void test3()
{
	struct MyStruct arrstu[5] = { {"张三",12},{"李四",1} ,{"王五",2} ,{"二狗",90}, {"大炮",5} };//结构体初始化
	int sz = sizeof(arrstu) / sizeof(arrstu[0]);
	qsort(arrstu, sz, sizeof(arrstu[0]), compute_nmae);
}

然后是计算函数 当然需要知道 

这里的计算方式有两种 一种是按照名字进行计算 一种是按照年龄进行计算

//结构体函数的计算 年龄的计算排序
int compute_age(const void* e1, const void* e2)
{
	return ((struct MyStruct*)e1)->age - ((struct MyStruct*)e2)->age;
	//这里是计算年龄的结构体进行打印的 
	//年龄的计算方式还是采取-+的方式进行对比 不采取库函数的方式进行对比
}
//结构体函数的计算 名字的计算排序
int compute_nmae(const void* e1, const void* e2)
{
	return strcmp(((struct MyStruct*)e1)->name , ((struct MyStruct*)e2)->name);
	//这里是计算名字的结构体进行打印的 也就是名字字符的字母转换成ASCII码值进行一对一对照 从而进行排序
	//这里的计算方式是采取strcmp的一对一对照的方式进行计算的 
}

 写出打印函数

//打印结构体函数
void Printstu(struct MyStruct* arr)
{
	printf("%s %d\n", arr->name, arr->age);
	//这里是打印结构体的函数 打印结构体的函数 需要用的是指针接收的 所以test3函数里面 需要取地址 传地址过来

}

 调用函数的完成

void test3()
{
	struct MyStruct arrstu[5] = { {"张三",12},{"李四",1} ,{"王五",2} ,{"二狗",90}, {"大炮",5} };//结构体初始化
	int sz = sizeof(arrstu) / sizeof(arrstu[0]);
	qsort(arrstu, sz, sizeof(arrstu[0]), compute_nmae);
	printf("结构体按照名字顺序进行排序>\n");
	for (int i = 0; i < sz; i++)
	{
		Printstu(&arrstu[i]);
		//打印结构体的函数 需要用的是指针接收的 所以test3函数里面 需要取地址 传地址过来
		//这里是【i】是外部数组的循环
	}
	printf("结构体按照年龄顺序进行排序>\n");
	qsort(arrstu, sz, sizeof(arrstu[0]), compute_age);
	for (int i = 0; i < sz; i++)
	{
		Printstu(&arrstu[i]);//打印结构体的函数 需要用的是指针接收的 所以test3函数里面 需要取地址 传地址过来
	}
	printf("\n\n");

}

图解 

按照年龄来排序

两个整形排序进行做差就好 年龄的排序和字符的排序逻辑是一样的 只是写的东西不一样

代码总结

3 结构体的使用  qsort函数的使用,结构体的计算(回调函数的使用)
struct MyStruct
{
	char name[100];
	int age;
	//这里的结构体需要放在使用函数的上面 算是进行函数声明 只有进行函数的声明之后 才可以进行打印 才可以进行计算
};
//打印结构体函数
void Printstu(struct MyStruct* arr)
{
	printf("%s %d\n", arr->name, arr->age);
	//这里是打印结构体的函数 打印结构体的函数 需要用的是指针接收的 所以test3函数里面 需要取地址 传地址过来

}
//结构体函数的计算 名字的计算排序
int compute_nmae(const void* e1, const void* e2)
{
	return strcmp(((struct MyStruct*)e1)->name , ((struct MyStruct*)e2)->name);
	//这里是计算名字的结构体进行打印的 也就是名字字符的字母转换成ASCII码值进行一对一对照 从而进行排序
	//这里的计算方式是采取strcmp的一对一对照的方式进行计算的 
}
//结构体函数的计算 年龄的计算排序
int compute_age(const void* e1, const void* e2)
{
	return ((struct MyStruct*)e1)->age - ((struct MyStruct*)e2)->age;
	//这里是计算年龄的结构体进行打印的 
	//年龄的计算方式还是采取-+的方式进行对比 不采取库函数的方式进行对比
}
void test3()
{
	struct MyStruct arrstu[5] = { {"张三",12},{"李四",1} ,{"王五",2} ,{"二狗",90}, {"大炮",5} };//结构体初始化
	int sz = sizeof(arrstu) / sizeof(arrstu[0]);
	qsort(arrstu, sz, sizeof(arrstu[0]), compute_nmae);
	printf("结构体按照名字顺序进行排序>\n");
	for (int i = 0; i < sz; i++)
	{
		Printstu(&arrstu[i]);
		//打印结构体的函数 需要用的是指针接收的 所以test3函数里面 需要取地址 传地址过来
		//这里是【i】是外部数组的循环
	}
	printf("结构体按照年龄顺序进行排序>\n");
	qsort(arrstu, sz, sizeof(arrstu[0]), compute_age);
	for (int i = 0; i < sz; i++)
	{
		Printstu(&arrstu[i]);//打印结构体的函数 需要用的是指针接收的 所以test3函数里面 需要取地址 传地址过来
	}
	printf("\n\n");

}
//主函数 负责函数的调用
int main()
{
	test1();
	test2();
	test3();
}

 ————————————————————————————————————————————————————————————————————————————————————— —————————————————————————————————————————————————————————————————————————————————————

模拟qsort函数(冒泡排序)

模拟的逻辑和图片详解

 前面讲解了冒泡排序 这里不过多赘述

此时就不能写成整形数组了

需要用void进行参数的接收 因为既然是模仿

要是有人用你的函数,那么不一定只是整形类型的

这里是冒泡排序的逻辑代码

函数指针指向两个参数

因为不知道指向什么元素 所以只能const

最后通过返回值告诉我

怎么算出arr的地址

base不能+1 因为是void类型

如果不是整形 那就不具备通用性 int类型

因为void是不能进行计算 并且让代码具备通用性

所以只能使用void

所以进行强制类型转换 转化成char*类型

那就是短整型首元素地址+循环的次数*宽度也就是字节的长度

也就是传参的时候还需要吧宽度再次传过去

这里图解一下交换函数的逻辑

还是那句话 你不确定用你函数的人是用int类型 还是char类型

但是char类型是 最小的 可以最大限度的满足所有的需求

如果没有那么大的空间 那就用小空间进行交换

也就是说 这里是int类型 int类型是实际是4个char

那么也就是这4个char进行交换 

交换完成后然后进行数值的返回

这里是一对字节进行交换

大块空间不方便进行交换 就小块空间进行交换

数据梳理1 

数据梳理

运行的步骤 

宽度就是字节长度

不满足返回2

此时进行交换

这里进行一个字节一个字节进行交换 交换四次

函数指针这里是非常重要的 不同类型的比较方式不一样 所以根据不同的类型 进行比较

每次交换一个字节 如果是整形此时交换四次 也就是交换四次 然后每次左边和右边进行缩小

从而完成数值的交换

———————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————— 

qsort函数模拟的具体代码分析

首先就是函数的构建

void test4()//模拟qsort函数的主函数
{
	int arrqsort[] = { 1,3,7,3,5,9,0,2,5,7,0,1,4,6 };
	int sz = sizeof(arrqsort) / sizeof(arrqsort[0]);
	my_qsort(arrqsort, sz, sizeof(arrqsort[0]), cmp);//这里的意思是让mt_qsort函数充当qsort函数 进行模拟
	//arrqsort函数充当的是数组首元素地址
	//sz代表的是整个数组的长度
	//sizeof(arrqsort[0])这里充当的是数组里面一个元素的大小 占据的是多大的宽度
	//my_qsort进行这个函数的调用 当然 这也是一个回调函数 间接调用cnp
	//cmp这里的函数的计算函数 也就是两个字符进行计算的函数

}

模拟函数的构建 

void my_qsort(void* base, size_t sz, size_t width, int(*cmp)(const void* e1, const void* e2))//模拟qsort的库函数
{
	//这里需要知道 这里需要用void类型 不能用int类型的 因为我们是进行qsort的模拟 并且我们并不知道传参过来的时候是int类型还是char类型的 要模拟的全面
	//void* base,
	//size_t sz,这里可以是int类型的 但是根据C语言的库函数我们可以得知 qsort函数里面 数组长度是size_t类型的
	//size_t width, 这里可以是int类型的 但是根据C语言的库函数我们可以得知 qsort函数里面 数组元素单个的宽度是size_t类型的
	//int(*cmp)(const void* e1, const void* e2)这里是一个函数指针变量 这里是int类型的原因是 需要接受返回值进行计算 大于1进行交换 等于0 不交换 小于0
	//这里补充一下 void类型虽然是可以接受任何的参数,但是void类型是不能参与计算的 所以在计算的时候需要进行强制类型转化,这也就是为什么在之前进行计算的时候 是需要进行强制类型转化的


	//这里采取的是冒泡排序的方式进行举例 所以 依旧是两个循环遍历数组 sz-1 的目的是 防止最后一次交换的时候发生越界行为 
	for (int i = 0; i < sz - 1; i++)
	{
		for (int j = 0; j < sz - i - 1; j++)//这里sz-i-1的目的是 减少循环的次数 如果不理解 可以理解为sz-1也是没问题的 只是那样遍历的次数多了
		{

			//这里是进行一个数值的对比 也就是qsort函数里面的运算逻辑
			//首先我们知道,这里的接受参数是void类型的 void的类型是无法参与计算的 所以在这里需要进行强制类型的转化 来确保传参过去后可以进行计算
			// 其次关于这个的计算问题j * width,(j + 1) * width 
			// 首先我们需要知道 这里我们为了让不仅int类型的可以计算 字符也是可以计算的 我们采取的是void类型的 可以接受任何的参数
			// 其次才是在内部需要进行计算 的时候再强制类型转化为char类型 这里的char类型一次是有一个字节 也就是sizeof的大小是一个字节
			// 但是这里我们具体问题具体分析一下就是 如果是int类型的情况下 那就是四个字节 所以数组元素每个元素占据的是四个字节
			// 那么 这里我们采取的是char一次跳过一个字节 如果是int类型的情况下 如何才能进行两个地址的对比
			// 那么 就是   首元素的地址+当前字符所在位置*字符的字节大小 和  首元素的地址+(当前字符所在位置+1)*字符的字节大小 
			//进行对比  所以此时也就是 前后两个元素进行对比 不会出现说 对比半天还是这个元素进行对比 或者 这个元素第一个字节和第二个字节对比的尴尬情况
			if (cmp((char*)base + j * width, (char*)base + (j + 1) * width) > 0)
			{
				Swap((char*)base + j * width, (char*)base + (j + 1) * width, width);
				//这里是交换函数 也就是如果满足
				//如果返回值小于0,则表示e1应该排在e2之前。
	            //如果返回值等于0,则表示e1和e2的顺序不变。
	            //如果返回值大于0,则表示e1应该排在e2之后。
				//则进行交换
				//不满足不进行交换 因为这里是指针 是直接对地址进行操作的 所以这里是只要操作 也就是会直接地址就改变排序的顺序
				// 满足交换的条件之后 传递参数到swap交换函数里面
				// 把满足条件的参数直接拿过来传过去就可以
				// 这里再次说明一下 void强制转化为char的原因
				// 因为 void虽然是很好的 可以接受各种类型 但是是不参与计算的
				// 所以需要强制转化为其他类型
				// 为了让代码的通用性更强 转化为char类型是最好的选择
				// 但是这里是指针 
				// 所以理所应当也就是char*类型了

			}
		}
	}

}

计算函数的构建

//qsort函数的模拟
int cmp(const void* e1, const void* e2)//模拟qsort函数的计算函数
{
	return *(int*)e1 - *(int*)e2;//这里没有什么好说的 这里之前讲很多次了 一笔带过
}

交换函数的构建

void Swap(char* e1, char* e2, size_t width)//模拟qsort函数的交换函数
{
	for (int i = 0; i < width; i++)
	{
		char tmp = *e1;//这里注意需要是char类型 千万要写成int类型 因为你传参过来的是char* 所以 你就算创建一个空的 也得类型一样
		*e1 = *e2;//这里就是进行交换
		*e2 = tmp;//进行交换
		e1++; //这里是每次进行交换完毕之后 左边指针指向的1字节进行++,不然会有很多重复的运算 或者错误的运算
		e2++;//这里是每次进行交换完毕之后 右边指针指向的1字节进行++
	}
}

 模拟函数的总结代码

#define _CRT_SECURE_NO_WARNINGS 1
#include<stdio.h>
#include<assert.h>
#include<string.h>
#include<stdlib.h>
//打印函数
//打印整形函数
void Printint(int arr[], size_t sz)
{
	for (int i = 0; i < sz; i++)
	{
		printf("%d ", arr[i]);
	}
}
//qsort函数的模拟
int cmp(const void* e1, const void* e2)//模拟qsort函数的计算函数
{
	return *(int*)e1 - *(int*)e2;//这里没有什么好说的 这里之前讲很多次了 一笔带过
}
void Swap(char* e1, char* e2, size_t width)//模拟qsort函数的交换函数
{
	for (int i = 0; i < width; i++)
	{
		char tmp = *e1;//这里注意需要是char类型 千万要写成int类型 因为你传参过来的是char* 所以 你就算创建一个空的 也得类型一样
		*e1 = *e2;//这里就是进行交换
		*e2 = tmp;//进行交换
		e1++; //这里是每次进行交换完毕之后 左边指针指向的1字节进行++,不然会有很多重复的运算 或者错误的运算
		e2++;//这里是每次进行交换完毕之后 右边指针指向的1字节进行++
	}
}
void my_qsort(void* base, size_t sz, size_t width, int(*cmp)(const void* e1, const void* e2))//模拟qsort的库函数
{
	//这里需要知道 这里需要用void类型 不能用int类型的 因为我们是进行qsort的模拟 并且我们并不知道传参过来的时候是int类型还是char类型的 要模拟的全面
	//void* base,
	//size_t sz,这里可以是int类型的 但是根据C语言的库函数我们可以得知 qsort函数里面 数组长度是size_t类型的
	//size_t width, 这里可以是int类型的 但是根据C语言的库函数我们可以得知 qsort函数里面 数组元素单个的宽度是size_t类型的
	//int(*cmp)(const void* e1, const void* e2)这里是一个函数指针变量 这里是int类型的原因是 需要接受返回值进行计算 大于1进行交换 等于0 不交换 小于0
	//这里补充一下 void类型虽然是可以接受任何的参数,但是void类型是不能参与计算的 所以在计算的时候需要进行强制类型转化,这也就是为什么在之前进行计算的时候 是需要进行强制类型转化的


	//这里采取的是冒泡排序的方式进行举例 所以 依旧是两个循环遍历数组 sz-1 的目的是 防止最后一次交换的时候发生越界行为 
	for (int i = 0; i < sz - 1; i++)
	{
		for (int j = 0; j < sz - i - 1; j++)//这里sz-i-1的目的是 减少循环的次数 如果不理解 可以理解为sz-1也是没问题的 只是那样遍历的次数多了
		{

			//这里是进行一个数值的对比 也就是qsort函数里面的运算逻辑
			//首先我们知道,这里的接受参数是void类型的 void的类型是无法参与计算的 所以在这里需要进行强制类型的转化 来确保传参过去后可以进行计算
			// 其次关于这个的计算问题j * width,(j + 1) * width 
			// 首先我们需要知道 这里我们为了让不仅int类型的可以计算 字符也是可以计算的 我们采取的是void类型的 可以接受任何的参数
			// 其次才是在内部需要进行计算 的时候再强制类型转化为char类型 这里的char类型一次是有一个字节 也就是sizeof的大小是一个字节
			// 但是这里我们具体问题具体分析一下就是 如果是int类型的情况下 那就是四个字节 所以数组元素每个元素占据的是四个字节
			// 那么 这里我们采取的是char一次跳过一个字节 如果是int类型的情况下 如何才能进行两个地址的对比
			// 那么 就是   首元素的地址+当前字符所在位置*字符的字节大小 和  首元素的地址+(当前字符所在位置+1)*字符的字节大小 
			//进行对比  所以此时也就是 前后两个元素进行对比 不会出现说 对比半天还是这个元素进行对比 或者 这个元素第一个字节和第二个字节对比的尴尬情况
			if (cmp((char*)base + j * width, (char*)base + (j + 1) * width) > 0)
			{
				Swap((char*)base + j * width, (char*)base + (j + 1) * width, width);
				//这里是交换函数 也就是如果满足
				//如果返回值小于0,则表示e1应该排在e2之前。
	            //如果返回值等于0,则表示e1和e2的顺序不变。
	            //如果返回值大于0,则表示e1应该排在e2之后。
				//则进行交换
				//不满足不进行交换 因为这里是指针 是直接对地址进行操作的 所以这里是只要操作 也就是会直接地址就改变排序的顺序
				// 满足交换的条件之后 传递参数到swap交换函数里面
				// 把满足条件的参数直接拿过来传过去就可以
				// 这里再次说明一下 void强制转化为char的原因
				// 因为 void虽然是很好的 可以接受各种类型 但是是不参与计算的
				// 所以需要强制转化为其他类型
				// 为了让代码的通用性更强 转化为char类型是最好的选择
				// 但是这里是指针 
				// 所以理所应当也就是char*类型了

			}
		}
	}

}
void test4()//模拟qsort函数的主函数
{
	int arrqsort[] = { 1,3,7,3,5,9,0,2,5,7,0,1,4,6 };
	int sz = sizeof(arrqsort) / sizeof(arrqsort[0]);
	my_qsort(arrqsort, sz, sizeof(arrqsort[0]), cmp);//这里的意思是让mt_qsort函数充当qsort函数 进行模拟
	//arrqsort函数充当的是数组首元素地址
	//sz代表的是整个数组的长度
	//sizeof(arrqsort[0])这里充当的是数组里面一个元素的大小 占据的是多大的宽度
	//my_qsort进行这个函数的调用 当然 这也是一个回调函数 间接调用cnp
	//cmp这里的函数的计算函数 也就是两个字符进行计算的函数

	Printint(arrqsort,sz);
}


//主函数 负责函数的调用
int main()
{
	test1();
	test2();
	test3();
	test4();
}

 ————————————————————————————————————————————————————————————————————————————————————— ———————————————————————————————————————————————————————————————————————————————————— 

总的代码

这里强调一下

结构体方面 需要把struct放到调用函数的上面,防止找不到变量 

从而产生错误 

#define _CRT_SECURE_NO_WARNINGS 1
#include<stdio.h>
#include<assert.h>
#include<string.h>
#include<stdlib.h>
//打印函数
//打印整形函数
void Printint(int arr[], size_t sz)
{
	for (int i = 0; i < sz; i++)
	{
		printf("%d ", arr[i]);
	}
}
//打印字符函数
void Printchar(char *arr, size_t sz)
{
	for (int i = 0; i < sz; i++)
	{
		printf("%c ", arr[i]);
	}
}









3 结构体的使用  qsort函数的使用,结构体的计算(回调函数的使用)
struct MyStruct
{
	char name[100];
	int age;
	//这里的结构体需要放在使用函数的上面 算是进行函数声明 只有进行函数的声明之后 才可以进行打印 才可以进行计算
};
//打印结构体函数
void Printstu(struct MyStruct* arr)
{
	printf("%s %d\n", arr->name, arr->age);
	//这里是打印结构体的函数 打印结构体的函数 需要用的是指针接收的 所以test3函数里面 需要取地址 传地址过来

}
//结构体函数的计算 名字的计算排序
int compute_nmae(const void* e1, const void* e2)
{
	return strcmp(((struct MyStruct*)e1)->name , ((struct MyStruct*)e2)->name);
	//这里是计算名字的结构体进行打印的 也就是名字字符的字母转换成ASCII码值进行一对一对照 从而进行排序
	//这里的计算方式是采取strcmp的一对一对照的方式进行计算的 
}
//结构体函数的计算 年龄的计算排序
int compute_age(const void* e1, const void* e2)
{
	return ((struct MyStruct*)e1)->age - ((struct MyStruct*)e2)->age;
	//这里是计算年龄的结构体进行打印的 
	//年龄的计算方式还是采取-+的方式进行对比 不采取库函数的方式进行对比
}
void test3()
{
	struct MyStruct arrstu[5] = { {"张三",12},{"李四",1} ,{"王五",2} ,{"二狗",90}, {"大炮",5} };//结构体初始化
	int sz = sizeof(arrstu) / sizeof(arrstu[0]);
	qsort(arrstu, sz, sizeof(arrstu[0]), compute_nmae);
	printf("结构体按照名字顺序进行排序>\n");
	for (int i = 0; i < sz; i++)
	{
		Printstu(&arrstu[i]);
		//打印结构体的函数 需要用的是指针接收的 所以test3函数里面 需要取地址 传地址过来
		//这里是【i】是外部数组的循环
	}
	printf("结构体按照年龄顺序进行排序>\n");
	qsort(arrstu, sz, sizeof(arrstu[0]), compute_age);
	for (int i = 0; i < sz; i++)
	{
		Printstu(&arrstu[i]);//打印结构体的函数 需要用的是指针接收的 所以test3函数里面 需要取地址 传地址过来
	}
	printf("\n\n");

}




2 字符数组的使用  qsort函数的使用,字符的计算(回调函数的使用)
char compute2(const void* e1, const void* e2)
{
	return *(char*)e1 - *(char*)e2;//和整形同理
	//这里解释一下 首先这里是强制类型转化成指针类型
    // 
    //这里首先进行了强制类型转换,将传入的void指针转换为char指针。
    // 因为在C语言中,函数的参数是通过值传递的方式传递的,
    // 这里传递的是指针的值(即指针的地址),而不是指针所指向的数据。
    // 因此,需要对传入的指针进行解引用(通过类型转换为char*),以获取它们所指向的实际整数值。
    // 
    // 对于计算方式是有隐含的计算方式的 图片已经进行说明,这里不进行多余的解释
    //如果返回值小于0,则表示e1应该排在e2之前。
    //如果返回值等于0,则表示e1和e2的顺序不变。
    //如果返回值大于0,则表示e1应该排在e2之后。
}
void test2()
{
	printf("qsort函数的使用,字符的计算(回调函数的使用)>\n");
	char arrchar[] = "sdhngkfuhsah";
	size_t sz = strlen(arrchar);
	qsort(arrchar, sz, sizeof(arrchar[0]), compute2);
	Printchar(arrchar, sz);
	printf("\n\n");
}










//1 整形数组的使用  qsort函数的使用,整形的计算(回调函数的使用)
int compute1(const void* e1, const void* e2)
{
	return *(int*)e1 - *(int*)e2;
	//这里解释一下 首先这里是强制类型转化成指针类型
	// 
	//这里首先进行了强制类型转换,将传入的void指针转换为int指针。
    // 因为在C语言中,函数的参数是通过值传递的方式传递的,
    // 这里传递的是指针的值(即指针的地址),而不是指针所指向的数据。
    // 因此,需要对传入的指针进行解引用(通过类型转换为int*),以获取它们所指向的实际整数值。
	// 
	// 对于计算方式是有隐含的计算方式的 图片已经进行说明,这里不进行多余的解释
	//如果返回值小于0,则表示e1应该排在e2之前。
	//如果返回值等于0,则表示e1和e2的顺序不变。
	//如果返回值大于0,则表示e1应该排在e2之后。
}
void test1()
{
	printf("qsort函数的使用,整形的计算(回调函数的使用)>\n");
	int arrint[] = { 1,3,7,3,5,9,0,2,5,7,0,1,4,6 };
	int sz = sizeof(arrint) / sizeof(arrint[0]);
	qsort(arrint, sz, sizeof(arrint[0]), compute1);
	Printint(arrint, sz);
	printf("\n\n");
}





//qsort函数的模拟
int cmp(const void* e1, const void* e2)//模拟qsort函数的计算函数
{
	return *(int*)e1 - *(int*)e2;//这里没有什么好说的 这里之前讲很多次了 一笔带过
}
void Swap(char* e1, char* e2, size_t width)//模拟qsort函数的交换函数
{
	for (int i = 0; i < width; i++)
	{
		char tmp = *e1;//这里注意需要是char类型 千万要写成int类型 因为你传参过来的是char* 所以 你就算创建一个空的 也得类型一样
		*e1 = *e2;//这里就是进行交换
		*e2 = tmp;//进行交换
		e1++; //这里是每次进行交换完毕之后 左边指针指向的1字节进行++,不然会有很多重复的运算 或者错误的运算
		e2++;//这里是每次进行交换完毕之后 右边指针指向的1字节进行++
	}
}
void my_qsort(void* base, size_t sz, size_t width, int(*cmp)(const void* e1, const void* e2))//模拟qsort的库函数
{
	//这里需要知道 这里需要用void类型 不能用int类型的 因为我们是进行qsort的模拟 并且我们并不知道传参过来的时候是int类型还是char类型的 要模拟的全面
	//void* base,
	//size_t sz,这里可以是int类型的 但是根据C语言的库函数我们可以得知 qsort函数里面 数组长度是size_t类型的
	//size_t width, 这里可以是int类型的 但是根据C语言的库函数我们可以得知 qsort函数里面 数组元素单个的宽度是size_t类型的
	//int(*cmp)(const void* e1, const void* e2)这里是一个函数指针变量 这里是int类型的原因是 需要接受返回值进行计算 大于1进行交换 等于0 不交换 小于0
	//这里补充一下 void类型虽然是可以接受任何的参数,但是void类型是不能参与计算的 所以在计算的时候需要进行强制类型转化,这也就是为什么在之前进行计算的时候 是需要进行强制类型转化的


	//这里采取的是冒泡排序的方式进行举例 所以 依旧是两个循环遍历数组 sz-1 的目的是 防止最后一次交换的时候发生越界行为 
	for (int i = 0; i < sz - 1; i++)
	{
		for (int j = 0; j < sz - i - 1; j++)//这里sz-i-1的目的是 减少循环的次数 如果不理解 可以理解为sz-1也是没问题的 只是那样遍历的次数多了
		{

			//这里是进行一个数值的对比 也就是qsort函数里面的运算逻辑
			//首先我们知道,这里的接受参数是void类型的 void的类型是无法参与计算的 所以在这里需要进行强制类型的转化 来确保传参过去后可以进行计算
			// 其次关于这个的计算问题j * width,(j + 1) * width 
			// 首先我们需要知道 这里我们为了让不仅int类型的可以计算 字符也是可以计算的 我们采取的是void类型的 可以接受任何的参数
			// 其次才是在内部需要进行计算 的时候再强制类型转化为char类型 这里的char类型一次是有一个字节 也就是sizeof的大小是一个字节
			// 但是这里我们具体问题具体分析一下就是 如果是int类型的情况下 那就是四个字节 所以数组元素每个元素占据的是四个字节
			// 那么 这里我们采取的是char一次跳过一个字节 如果是int类型的情况下 如何才能进行两个地址的对比
			// 那么 就是   首元素的地址+当前字符所在位置*字符的字节大小 和  首元素的地址+(当前字符所在位置+1)*字符的字节大小 
			//进行对比  所以此时也就是 前后两个元素进行对比 不会出现说 对比半天还是这个元素进行对比 或者 这个元素第一个字节和第二个字节对比的尴尬情况
			if (cmp((char*)base + j * width, (char*)base + (j + 1) * width) > 0)
			{
				Swap((char*)base + j * width, (char*)base + (j + 1) * width, width);
				//这里是交换函数 也就是如果满足
				//如果返回值小于0,则表示e1应该排在e2之前。
	            //如果返回值等于0,则表示e1和e2的顺序不变。
	            //如果返回值大于0,则表示e1应该排在e2之后。
				//则进行交换
				//不满足不进行交换 因为这里是指针 是直接对地址进行操作的 所以这里是只要操作 也就是会直接地址就改变排序的顺序
				// 满足交换的条件之后 传递参数到swap交换函数里面
				// 把满足条件的参数直接拿过来传过去就可以
				// 这里再次说明一下 void强制转化为char的原因
				// 因为 void虽然是很好的 可以接受各种类型 但是是不参与计算的
				// 所以需要强制转化为其他类型
				// 为了让代码的通用性更强 转化为char类型是最好的选择
				// 但是这里是指针 
				// 所以理所应当也就是char*类型了

			}
		}
	}

}
void test4()//模拟qsort函数的主函数
{
	int arrqsort[] = { 1,3,7,3,5,9,0,2,5,7,0,1,4,6 };
	int sz = sizeof(arrqsort) / sizeof(arrqsort[0]);
	my_qsort(arrqsort, sz, sizeof(arrqsort[0]), cmp);//这里的意思是让mt_qsort函数充当qsort函数 进行模拟
	//arrqsort函数充当的是数组首元素地址
	//sz代表的是整个数组的长度
	//sizeof(arrqsort[0])这里充当的是数组里面一个元素的大小 占据的是多大的宽度
	//my_qsort进行这个函数的调用 当然 这也是一个回调函数 间接调用cnp
	//cmp这里的函数的计算函数 也就是两个字符进行计算的函数

	Printint(arrqsort,sz);
}


//主函数 负责函数的调用
int main()
{
	test1();
	test2();
	test3();
	test4();
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1509504.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【绿电监测 碳排放分析 新建5G基站】基站能效管理解决方案

背景及需求 中国基站相关政策 需求 01用电监管&#xff0c;偷电窃电监测 对基站进线回路和出线回路进行监测对比&#xff0c;实时监测线路的使用功率&#xff0c;通过最大功率判断是否有其他设备接入而产生偷电行为。 02节能控制 通过控制空调启停、调整通讯设备工作模式或…

http升级https需要做什么

背景&#xff1a;随着现代网络时代的高速发展&#xff0c;网络安全方面的日益更新&#xff0c;实现网站https协议的数量也在不断增多&#xff0c;完善安全方面的因素也在逐步增加。 下面从最基础的网站http协议全面升级为https协议的流程做出说明。 目录 首先带大家一起先了解…

Python 导入Excel三维坐标数据 生成三维曲面地形图(面) 4-3、线条平滑曲面(原始颜色)去除无效点

环境和包: 环境 python:python-3.12.0-amd64包: matplotlib 3.8.2 pandas 2.1.4 openpyxl 3.1.2 scipy 1.12.0 代码: import pandas as pd import matplotlib.pyplot as plt from mpl_toolkits.mplot3d import Axes3D from scipy.interpolate import griddata fr…

HTML5七天学会基础动画网页10(2)

制作立方体 学完前面的基础内容&#xff0c;制作立方体是个不错的练习方法&#xff0c;先看成品 再分析一下&#xff0c;六个面让每个面旋转平移就可以实现一个立方体&#xff0c;来看代码: <title> 制作立方体</title> <style> *{ margin: 0; padding: 0; …

Springboot+vue的政府管理的系统设计(有报告)。Javaee项目,springboot vue前后端分离项目。

演示视频&#xff1a; Springbootvue的政府管理的系统设计&#xff08;有报告&#xff09;。Javaee项目&#xff0c;springboot vue前后端分离项目。 项目介绍&#xff1a; 采用M&#xff08;model&#xff09;V&#xff08;view&#xff09;C&#xff08;controller&#xff…

案例分析篇01:软件架构设计考点架构风格及质量属性(2024年软考高级系统架构设计师冲刺知识点总结系列文章)

专栏系列文章推荐: 2024高级系统架构设计师备考资料(高频考点&真题&经验)https://blog.csdn.net/seeker1994/category_12601310.html 【历年案例分析真题考点汇总】与【专栏文章案例分析高频考点目录】(2024年软考高级系统架构设计师冲刺知识点总结-案例分析篇-…

【大厂AI课学习笔记NO.79】机器学习行业人才能力图谱

有从事机器学习行业的小伙伴&#xff0c;人才岗位如上了。 同样的&#xff0c;也是分为领军人才&#xff08;略&#xff09;、产业研发人才、应用开发人才和实用技能人才了。 机器学习领域的就业岗位分析 随着科技的飞速发展&#xff0c;人工智能已成为当今时代最热门的领域…

Purple Pi OH鸿蒙开发板7天入门OpenHarmony开源鸿蒙教程【五】

在完成了Purple Pi OH大部分的接口测试之后&#xff0c;紧接着就是一个充满挑战的任务——利用SDK来编译生成我们自己的镜像文件。通过这一过程&#xff0c;不仅能够让你获得一个可在真实硬件上运行的系统镜像&#xff0c;更重要的是&#xff0c;它让你对OpenHarmony系统的构建…

分享个好用的GPT网站

目录 一、背景 二、功能描述 1、写代码 2、联网查询 3、AI绘图 一、背景 我现在的开发工作都依靠ChatGPT&#xff0c;效率提升了好几倍。这样一来&#xff0c;我有更多时间来摸鱼&#xff0c;真是嘎嘎香~ ⭐⭐⭐点击直达 ⭐⭐⭐ 二、功能描述 1、写代码 import java.ut…

Hyperopt自动化调参工具实践-1

hyperopt Hyperopt的任务是在一组可能的参数上找到标量值的最佳值&#xff0c;该标量值可能是随机的。 与许多优化包假定这些输入来自向量空间不同&#xff0c;Hyperopt是不同的&#xff0c;因为它鼓励使用者更详细地描述搜索空间。通过提供关于函数定义在哪里以及认为最佳值…

AI新工具(20240311) 国内免费使用Claude 3 Sonnet;Pika推出视频加音效功能

1: 国内免费使用Claude 3 Sonnet Claude 3现已登陆Amazon Bedrock&#xff0c;国内就能够免费使用&#xff0c;以下是网友整理的使用流程。 地址&#xff1a;https://lab.amazoncloud.cn/ 2: Pika Sound Effects Pika推出视频加音效功能&#xff0c;为视频创作带来声音定制…

举牌小人图生成小程序源码(修复版)

源码介绍&#xff1a; 举牌小人图生成小程序源码&#xff08;修复版&#xff09;无需服务器导入开发者工具即可运行&#xff0c;无需绑定合法域名&#xff0c;仅供学习交流 建议&#xff1a; 有能力者接入安全过滤机制&#xff0c;更完美&#xff0c;可以联系客服免费指导~ 源…

深度学习_VGG_3

目标 知道VGG网络结构的特点能够利用VGG完成图像分类 2014年&#xff0c;牛津大学计算机视觉组&#xff08;Visual Geometry Group&#xff09;和Google DeepMind公司的研究员一起研发出了新的深度卷积神经网络&#xff1a;VGGNet&#xff0c;并取得了ILSVRC2014比赛分类项目…

OKHttpRetrofit

完成一个get请求 1.导入依赖 implementation("com.squareup.okhttp3:okhttp:3.14.")2.开启viewBinding android.buildFeatures.viewBinding true 3.加网络权限 和 http明文请求允许配置文件 <?xml version"1.0" encoding"utf-8"?> &l…

【精选】30+Redis面试题整理(2024)附答案

目录 前言Redis基础项目有用到redis吗&#xff1f;你们项目为什么用redis?redis为什么这么快&#xff1f;了解Redis的线程模型吗&#xff1f;Redis优缺点?redis如何实现持久化&#xff1f;RDB持久化过程&#xff1f;AOF持久化过程&#xff1f;AOF持久化会出现阻塞吗&#xff…

[Angular 基础] - 表单:响应式表单

[Angular 基础] - 表单&#xff1a;响应式表单 之前的笔记&#xff1a; [Angular 基础] - routing 路由(下) [Angular 基础] - Observable [Angular 基础] - 表单&#xff1a;模板驱动表单 开始 其实这里的表单和之前 Template-Driven Forms 没差很多&#xff0c;不过 Tem…

vue-创建vue项目记录

安装node.js 先安装node.js的运行环境node.js的下载地址 安装后就可以使用npm命令 1、清除npm缓存&#xff1a;npm cache clean --force 2、禁用SSL&#xff1a;npm config set strict-ssl false 3、手动设置npm镜像源&#xff1a;npm config set registry https://registry.…

Python AI 之Stable-Diffusion-WebUI

Stable-Diffusion-WebUI简介 通过Gradio库&#xff0c;实现Stable Diffusion web 管理接口 Windows 11 安装Stable-Diffusion-WebUI 个人认为Stable-Diffusion-WebUI 官网提供的代码安装手册/自动安装不适合新手安装&#xff0c;我这边将一步步讲述我是如何搭建Python Conda…

centos 系统 yum 无法安装(换国内镜像地下)

centos 系统 yum 因为无法连接到国外的官网而无法安装&#xff0c;问题如下图&#xff1a; 更换阿里镜像&#xff0c;配置文件路径&#xff1a;/etc/yum.repos.d/CentOS-Base.repo&#xff08;如果目录有多余的文件可以移动到子目录&#xff0c;以免造成影响&#xff09; bas…

php CI框架异常报错通过钉钉自定义机器人发送

php CI框架异常报错通过钉钉自定义机器人发送 文章目录 php CI框架异常报错通过钉钉自定义机器人发送前言一、封装一个异常监测二、封装好钉钉信息发送总结 前言 我们在项目开发中&#xff0c;经常会遇到自己测试接口没问题&#xff0c;上线之后就会测出各种问题&#xff0c;主…