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

学习目录 

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

回调函数 

回调函数是一种机制

计算器的代码举例

有问题:过于冗余

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

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

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();
}

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

WPF Button去除按钮边框,添加下划线

<Button Width"45" Height"25" FontSize"20" Background"Transparent" BorderBrush"Transparent" Foreground"#FFC9A322" Click"Btn_Retry_Click" ><TextBlock><Underline>重试</…

Jenkins自动构建 CI/CD流水线学习笔记(从入门到入土,理论+示例)

文章目录 1、什么是Jenkins的流水线?2、流水线语法2.1、声明式流水线2.2、脚本化流水线 3、流水线示例3.1、使用声明式流水线的语法编写的 Jenkinsfile 文件3.2、Pipeline 各种语言示例3.2.1 Java&#xff1a;3.2.2 Node.js / JavaScript3.2.3 Python 4、一套完整的Devops Jen…

[云原生] K8s之ingress

1.Ingress的相关知识 1.1 Ingress的简介 service的作用体现在两个方面&#xff0c;对集群内部&#xff0c;它不断跟踪pod的变化&#xff0c;更新endpoint中对应pod的对象&#xff0c;提供了ip不断变化的pod的服务发现机制&#xff1b;对集群外部&#xff0c;他类似负载均衡器…

09-设计模式 面试题

你之前项目中用过设计模式吗? 工厂方法模式分类 简单工厂模式工厂方法模式抽象工厂模式工厂模式 需求:设计一个咖啡店点餐系统。 设计一个咖啡类(Coffee),并定义其两个子类(美式咖啡【AmericanCoffee】和拿铁咖啡【LatteCoffee】);再设计一个咖啡店类(CoffeeStore)…

机器学习-04-分类算法-01决策树案例

总结 本系列是机器学习课程的系列课程&#xff0c;主要介绍机器学习中分类算法&#xff0c;本篇为分类算法开篇与决策树部分。 本门课程的目标 完成一个特定行业的算法应用全过程&#xff1a; 懂业务会选择合适的算法数据处理算法训练算法调优算法融合 算法评估持续调优工程…

20240308-使用VS2022编译VLD-v2.5.4内存泄漏工具

20240308-使用VS2022编译VLD-v2.5.4内存泄漏工具 一、软件环境 Win10 x64 22h2 JuneVS2022 v17.9.0GIT v2.29.2标签&#xff1a;win10 22h2 vs2022分栏&#xff1a;C 二、硬件环境 Win10 x64的PC台式机 三、获取源码 方法一 git clone https://gitee.com/gdnh22/vld254.…

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

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

Jmeter+Ant+Git/SVN+Jenkins实现持续集成接口测试,一文精通(二)

前言 上篇内容已经介绍接口测试流程以及了解如何用jmeter接口测试&#xff0c;本篇将介绍如何在实战中应用 一、Jmeter接口关联 1.使用正则表达式实现接口关联&#xff08;可以作用于任意值&#xff09; 如果说一个请求里面有多次请求服务器。 2.使用Jsonpath表达式实现接口关…

Python教程-SchemDraw绘制电路图

电路图是电子工程师和电子爱好者的重要工具&#xff0c;用于图形化表示电子元件之间的连接关系。在Python中&#xff0c;有许多库可以用于绘制电路图&#xff0c;其中之一就是SchemDraw。本文将介绍如何使用SchemDraw库&#xff0c;通过简单的Python代码绘制出清晰、美观的电路…

电子价签前景璀璨,汉朔科技革新零售行业的数字化新篇章

新型商超模式数字化“秘密武器”——电子价签 传统纸质价签&#xff0c;只要商品价格、日期等信息发生变化&#xff0c;就必须重新打印进行手动替换。电子价签的应用使传统的人工申请、调价、打印、营业员去货架前端更换等变价流程均可省略&#xff0c;所有门店的价格由后台统…

2024 年系统架构设计师(全套资料)

2024年5月系统架构设计师最新第2版教材对应的全套视频教程、历年真题及解析、章节分类真题及解析、论文写作及范文、教材、讲义、模拟题、答题卡等资料 1、2023年11月最新第2版本教材对应全套教程视频&#xff0c;2022年、2021年、2020年、2018年、2016年五套基础知识精讲视频、…

YOLOv5改进 | 独家创新篇 | 利用DCNv3结合DLKA形成全新的注意力机制(全网独家创新)

一、本文介绍 本文给大家带来的机制是由我独家创新结合Deformable Large Kernel Attention (D-LKA) 注意力机制和DCNv3可变形卷积的全新注意力机制模块&#xff08;算是二次创新&#xff09;&#xff0c;D-LKA的基本原理是结合了大卷积核和可变形卷积的注意力机制&#xff0c;…

python基础及网络爬虫

网络爬虫(Web crawler)&#xff0c;有时候也叫网络蜘蛛(Web spider)&#xff0c;是指这样一类程序——它们可以自动连接到互联网站点&#xff0c;并读取网页中的内容或者存放在网络上的各种信息&#xff0c;并按照某种策略对目标信息进行采集&#xff08;如对某个网站的全部页面…

案例分析篇08:Web架构设计相关20个考点(1~6)(2024年软考高级系统架构设计师冲刺知识点总结系列文章)

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

Acer宏碁非凡Swift SFG16-71工厂模式原厂Win11系统,预装OEM系统恢复开箱状态

宏基笔记本电脑SFG16-71原装出厂Windows11系统安装工厂包下载&#xff0c;带恢复重置功能 链接&#xff1a;https://pan.baidu.com/s/1JK02kBbwKG_cIBNlEOzrOw?pwdzdfm 提取码&#xff1a;zdfm 原装工厂包系统自带所有驱动、Office办公软件、出厂时自带主题壁纸图片、系统…

汽车软件市场迅猛扩张,Perforce Helix Core与Helix IPLM助力汽车软件开发的版本控制及IP生命周期管理

汽车软件世界正处于持续变革和转型之中。从自动驾驶汽车到电动汽车和先进的驾驶辅助系统&#xff0c;汽车软件的集成度和复杂性不断提升。 据美国电气与电子工程师协会的研究&#xff0c;如今大多数汽车都集成了超过1亿行代码&#xff0c;而仅仅十年前&#xff0c;这种水平的汽…

Go语言简介

一.Go语言简介 1.1 优点 自带gc静态编译&#xff0c;编译好后&#xff0c;扔服务器直接运行简单思想&#xff0c;没有继承&#xff0c;多态和类等丰富的库和详细开发文档语法层支持并发&#xff0c;和拥有同步并发的channel类型&#xff0c;使并发开发变得非常方便简洁语法&am…

网站实现HTTPS必须要使用SSL证书吗?

网站实现https必须要使用https协议&#xff0c;而要使用https协议就必须要安装SSL证书来实现。https协议可以通过SSL证书来实现加密传输数据&#xff0c;从而保证访客的隐私信息不回被窃取到。SSL证书就是浏览器跟服务器之间建立起来的安全通信的重要组成部分。 当访客在访问一…

跨城容灾与异地多活常见的架构设计

跨城容灾与异地多活常见的架构设计 1. 同城IDC与跨城IDC2. 几种不同的部署方式1.1 无复制的异地部署&#xff08;单地存储&#xff09;1.2 无复制的异地部署&#xff08;异地存储&#xff09;1.3 两地三中心部署&#xff08;同城同步复制&#xff0c;跨城异步复制&#xff09;1…

几何变换 - 图像的缩放、翻转、仿射变换、透视等

1、前言 图像的几何变换是指改变图像的几何结构,大小、形状等等,让图像呈现出具备缩放、翻转、映射和透视的效果 图像的几何变换都比较复杂,计算也很复杂。 例如仿射变换,像素点的位置和灰度值都需要变换。 数字图像处理中利用后向传播的方法,将像素点变换后的位置通过…