指针篇章-(4)+qsort函数的模拟

news2024/10/6 0:35:33

学习目录 

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

回调函数 

回调函数是一种机制

计算器的代码举例

有问题:过于冗余

对代码进行简化 就是吧代码抽象成函数

所以就变成通过参数的不同进行指针的调用

calc函数

对比和详解

通过函数指针调用函数 也就是通过clac调用函数

这里插入回调函数的篇章

忘记的同学可以再去看一眼

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

 补充 

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

之前在指针(2)篇章系统的说过结构体,这里补充复习一下,结构体在指针里的使用

在结构体中使用指针可以通过以下两种方式找到结构体的成员:
1. 使用箭头运算符(->):当结构体是通过指针进行引用时,可以使用箭头运算符来访问结构体的成员。例如,如果有一个指向结构体的指针ptr,可以使用ptr->member来访问结构体的成员。
2. 使用间接引用运算符(*):如果有一个指向结构体的指针ptr,可以使用*ptr来获取指针所指向的结构体对象,然后再使用点运算符(.)来访问结构体的成员。例如,(*ptr).member。

*(指针).+结构体

也可以是

指针名称->结构体名称

需要用指针进行接收,所以传参的时候需要取地址,因为指针指向的是地址 

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

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/1510542.html

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

相关文章

第二十一天-NumPy

目录 什么是NumPy NumPy使用 1.数组的创建 2.类型转换 3.赠删改查 4.数组运算 5.矩阵运算 什么是NumPy 1.NumPy操作的是多维数组&#xff0c;什么是纬度&#xff1f; NumPy使用 1. 安装 pip install numpy import numpy as np 2.官网&#xff1a; 中文官网&#xff1a…

Vue3案例——通过指令实现下拉菜单效果

2.6 Vue3案例——通过指令实现下拉菜单效果 使用v-for指令可以对数组、对象进行循环&#xff0c;来获取其中的每一个值。 1. v-for指令遍历数组 使用v-for指令时&#xff0c;必须使用特定语法alias in expression&#xff0c;其中items是源数据数组&#xff0c;而item则是被…

java集合题库详解

1. Arraylist与LinkedList区别 可以从它们的底层数据结构、效率、开销进行阐述哈 ArrayList是数组的数据结构&#xff0c;LinkedList是链表的数据结构。 随机访问的时候&#xff0c;ArrayList的效率比较高&#xff0c;因为LinkedList要移动指针&#xff0c;而ArrayList是基于索…

宏工科技数智方案现先进陶瓷展,VR体验数字工厂引关注

3月6-8日&#xff0c;第十六届中国国际粉末冶金、硬质合金与先进陶瓷展览会在上海举行。本届展会&#xff0c;宏工科技股份有限公司携VR体验数字工厂和正负压气力输送系统惊艳亮相&#xff0c;“现实虚拟”的呈现方式收获众多行业客户及专业观众高度关注。 展会汇聚了来自粉末冶…

第十三届蓝桥杯嵌入式省赛程序设计详细题解

第十三届蓝桥杯嵌入式省赛题目相对于第十二届较为简单&#xff0c;没有那么多串口的数据处理以及判断&#xff01; 第十三届省赛主要是制作一个可由串口设置密码的密码锁。本实验中&#xff0c;我们将用到LED模块、按键模块、串口模块、定时器的PWM模块以及官方会提供源码的LC…

[Linux_IMX6ULL应用开发]-hello程序的交叉编译

目录 【开发板、虚拟机和PC的三者联通】 使用串口连接到开发板 连接Ubuntu虚拟机 互ping测试 【交叉编译hello.c文件】 Ubuntu编译无法在板子运行问题 使用交叉编译链编译hello.c 【开发板、虚拟机和PC的三者联通】 在这里我们使用IMX6ULL-PRO开发板进行学习&#xff0c;…

python+requests接口自动化框架的实现

为什么要做接口自动化框架 1、业务与配置的分离 2、数据与程序的分离&#xff1b;数据的变更不影响程序 3、有日志功能&#xff0c;实现无人值守 4、自动发送测试报告 5、不懂编程的测试人员也可以进行测试 正常接口测试的流程是什么&#xff1f; 确定接口测试使用的工具…

内部应用解耦神器-Spring事件

大家好&#xff0c;我是程序员牛牛&#xff0c;《AI超级个体: ChatGPT与AIGC实战指南》的参与人&#xff0c;10年Java编程程序员。 1. 概述 在做业务开发过程中&#xff0c;有些复杂点的逻辑&#xff0c;可能代码逻辑会很冗长&#xff0c;举一个很简单的例子&#xff0c;如&am…

windows解决nodejs版本冲突:安装版本管理器nvm,可根据不同项目一键切换适配版本

windows解决nodejs版本冲突&#xff1a;安装版本管理器nvm&#xff0c;可根据不同项目一键切换适配版本 参考来源&#xff1a;在本机 Windows 上设置 NodeJS | Microsoft Learn 建议安装版本管理器 nvm-windows&#xff0c;再用它来安装 Node.js 和 npm&#xff0c;这样可以根据…

C++学习笔记:红黑树

红黑树 什么是红黑树红黑树的规则红黑树节点的定义红黑树的插入空树插入非空插入条件判断新插入的节点 cur 不为 root 且 parent->_col 为红就需要调整父节点为左 grandf->left parent当uncle节点为红色时,只需要进行颜色调整,即可当uncle为空 或 者存在但是为黑parent …

Midjourney能让角色保持一致了

Midjourney发布新功能&#xff0c;网友直呼“不可思议”&#xff01; 现在你可以让生成的图像几乎保持角色一致&#xff0c;belike&#xff1a; 所有超级英雄长一个模样盯着你。 甚至动漫风、写实风等跨风格生成也同样适用&#xff1a; 保持同一风格&#xff0c;感jio配上文字…

【FPGA】DDR3学习笔记(一)丨SDRAM原理详解

本篇文章包含的内容 一、DDR3简介1.1 DDR3 SDRAM概述1.2 SDRAM的基础结构 二、 SDRAM操作时序2.1 SDRAM操作指令2.2 模式寄存器&#xff08;LOAD MODE REGISTER&#xff09;2.3 SDRAM操作时序示例2.3.1 SDRAM初始化时序2.3.2 突发读时序2.3.3 随机读时序2.3.4 突发写时序2.3.5 …

Java基础-接口

文章目录 1.快速入门代码&#xff1a;结果&#xff1a; 2.接口基本介绍1.语法注意&#xff1a;在jdk1.8以后接口才可以有静态方法&#xff0c;默认方法 2.代码实例 3.接口的应用场景1.场景介绍2.代码实例4.接口使用细节 5.接口课堂练习题目&#xff1a;答案&#xff1a;注意&am…

深入理解,java标识符?类型转换?

1、标识符 下面这张图是中国的一些姓氏 中国人起名字的规则都是以姓开头&#xff0c;名结尾。通过这个规则可以起&#xff1a;刘爱国、张三等&#xff0c;都是以汉字起的。但是不会起李ad、王123等名字&#xff0c;因为不符合规则。 所以&#xff0c;java在给变量、方法、类等…

【C++进阶】C++继承概念详解

C继承详解 一&#xff0c;继承的概念和定义1.1 继承的概念1.2 继承的定义1.3 继承关系和访问限定符 二&#xff0c;基类和派生类的对象赋值转移三&#xff0c;继承的作用域四&#xff0c;派生类的默认成员函数五&#xff0c;继承和友元&静态成员和继承六&#xff0c;菱形继…

Ansys Lumerical | 激光雷达天线仿真

附件下载 联系工作人员获取附件 在本文中&#xff0c;我们将了解如何根据激光雷达应用需求设计和优化相控阵光栅天线。 概述 激光雷达&#xff08;LIDAR&#xff09;是“light detection and ranging”的简称&#xff0c;近年来由于在机器人、自动驾驶汽车、高精度测绘等领域…

【AcWing】蓝桥杯集训每日一题Day2|前缀和|562.壁画(C++)

562. 壁画 562. 壁画 - AcWing题库难度&#xff1a;中等时/空限制&#xff1a;1s / 64MB总通过数&#xff1a;4154总尝试数&#xff1a;10197来源&#xff1a;Google Kickstart2018 Round H Problem B算法标签 思维题枚举前缀和 题目内容 Thanh 想在一面被均分为 N 段的墙上画…

[java——基础] 双亲委派机制

目录 核心思想&#xff1a; 双亲委派机制的好处&#xff1a; 三种类加载器 解析源代码 双亲委派思想面试总结&#xff1a; 核心思想&#xff1a; 向上搜索&#xff0c;向下加载。 双亲委派机制的好处&#xff1a; 防止Java核心类被篡改&#xff0c;防止类的重复加载。 三…

哈希表|15.三数之和

力扣题目链接 int cmp(const void *a,const void *b) {return *(int*)a - *(int*)b;} int** threeSum(int* nums, int numsSize, int* returnSize, int** returnColumnSizes) {*returnSize 0;if(numsSize < 3)return NULL;qsort(nums, numsSize, sizeof(int),cmp);int **…

C++ Qt开发:QNetworkAccessManager网络接口组件

Qt 是一个跨平台C图形界面开发库&#xff0c;利用Qt可以快速开发跨平台窗体应用程序&#xff0c;在Qt中我们可以通过拖拽的方式将不同组件放到指定的位置&#xff0c;实现图形化开发极大的方便了开发效率&#xff0c;本章将重点介绍如何运用QNetworkAccessManager组件实现Web网…