【C语言】指针收尾,函数指针的应用,新指针类型void*------回调函数和qsort函数的模拟实现【附源码,图文讲解】

欢迎来CILMY23的博客喔,本期系列为【C语言】指针的收尾篇,新指针类型void*------回调函数和qsort函数的模拟实现【附源码,图文讲解】,图文讲解qsort函数,带大家更深刻理解指针,感谢观看,支持的可以给个赞哇。

前言

在上一篇指针的进阶篇博客中,我们了解了其他类型的指针变量,并且彻底了解了指针数组和数组指针的区别,以及函数指针和函数指针数组的出现,让我们了解了简化代码的另一种方式,本期博客将用两个有趣的代码来开篇,并学习回调函数和qsort函数的相关内容。 

目录

一、有趣的代码

二、回调函数

三、qsort函数

四、qsort模拟实现


一、有趣的代码

(*(void (*)())0)();

void (*signal(int , void(*)(int)))(int);

首先第一段代码我们看里面void (*)(),这是一个函数指针类型,指针所指向的函数类型是void,参数是(),    在函数指针的外围我们看到一个0,左边有一对括号,这个(),是代表强制类型转换的意思,我们把0这个数值,强制类型转换成函数指针类型,就表示0地址放了一个void (*)()的函数,然后对其解引用,进行调用。 

调用0地址处的函数,调用的函数参数是无参,返回类型是void【C语言】指针收尾,函数指针的应用,新指针类型void*------回调函数和qsort函数的模拟实现【附源码,图文讲解】_第1张图片

第二段代码:

 首先在第二段代码里,我们看到有个signal函数,它的两个参数类型一个是整型,一个是函数指针类型,这个函数指针类型的返回类型是void,形参中有int整型。所以这一段代码说的是signal的函数声明,它的返回类型是函数指针类型。

signal是一个函数的函数名

它有两个参数,第一个参数是int类型,第二个参数是函数指针类型,该函数指针指向的函数参数是int类型,返回类型是void。

signal函数的返回类型也是函数指针类型,该函数指针指向的函数参数是int类型,返回类型是void。

【C语言】指针收尾,函数指针的应用,新指针类型void*------回调函数和qsort函数的模拟实现【附源码,图文讲解】_第2张图片

二、回调函数

 回调函数就是⼀个通过函数指针调用的函数。 在进阶篇我们使用了转移表来实现普通的四则计算器,现在我们对转移表进行改装一下,

#include 

int Add(int x, int y)
{
	return x + y;
}

int Sub(int x, int y)
{
	return x - y;
}

int Mul(int x, int y)
{
	return x * y;
}

int Div(int x, int y)
{
	return x / y;
}


void menu()
{
	printf("***************************\n");
	printf("*****  1. add  2.sub  *****\n");
	printf("*****  3. mul  4.div  *****\n");
	printf("*****  0. exit        *****\n");
	printf("***************************\n");
}

void calc(int(*pf)(int, int))
{
	int x = 0;
	int y = 0;
	int ret = 0;

	printf("请输入2个操作数:>");
	scanf("%d %d", &x, &y);
	ret = pf(x, y);
	printf("%d\n", ret);
}

int main()
{
	int input = 0;

	do
	{
		menu();
		printf("请选择:>");
		scanf("%d", &input);
		switch (input)
		{
		case 1:
			calc(Add);
			break;
		case 2:
			calc(Sub);
			break;
		case 3:
			calc(Mul);
			break;
		case 4:
			calc(Div);
			break;
		case 0:
			printf("退出计算器\n");
			break;
		default:
			printf("选择错误, 重新选择\n");
			break;
		}
	} while (input);

	return 0;
}

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

就像上述代码中,函数我们把它作为参数传递给cal函数,这个指针被用来调用四则运算的函数,那么四则运算的函数就是回调函数。

三、qsort函数

qsort函数可以在cplusplus网站查询:cplusplus.com - The C++ Resources Network

 【C语言】指针收尾,函数指针的应用,新指针类型void*------回调函数和qsort函数的模拟实现【附源码,图文讲解】_第3张图片

底下一部分是参数解释: 

【C语言】指针收尾,函数指针的应用,新指针类型void*------回调函数和qsort函数的模拟实现【附源码,图文讲解】_第4张图片

qsort函数是一个库函数,可以对数据进行排序,它可以排序任意类型的数据! 

四个参数解析如下: 

【C语言】指针收尾,函数指针的应用,新指针类型void*------回调函数和qsort函数的模拟实现【附源码,图文讲解】_第5张图片

首先解释一个指针类型:void*是什么?

#include

int main()
{
	int a = 5;
	int* pa = &a;
	char* pc = &a;
	void* p = &a;

	return 0;
}

我们先来运行以下代码,我们发现,编译器会给我们一个警告:

而void * 不报错。这是因为void* 是一种指针类型,这种类型是通用的。可以用来接收任意数据类型的地址。  针对void * 类型,它只是用来存放地址的,不能拿来解引用。

其次是中间三个参数,一个是待排序,一个是元素个数,一个是大小。

最后我们看第四个参数 ,

【C语言】指针收尾,函数指针的应用,新指针类型void*------回调函数和qsort函数的模拟实现【附源码,图文讲解】_第6张图片

它的使用是,比较前面一个参数和后面一个参数的大小,如果前面小于后面,就返回一个比0小的值,如果前面一个参数和后面一个参数的大小相等,就返回0,如果前面一个参数比后面一个参数大的话,就返回一个比0大的值。 

 qsort的使用如下:

#include

//两个元素比较
int int_com(const void* e1, const void* e2)
{
	return *(int*)e1 - *(int*)e2;
}
//打印数组
void print_arr(int* p, size_t sz)
{
	size_t i = 0;
	for (i = 0; i 

在上述代码中,我们在test1函数里测试qsort排序一个整型数组。 我们自己写了一个函数,int

_com来完成第四个参数。所以这个int_com就是我们前面所提到的回调函数。

接下来是qsort测试排序结构体:

#include
#include
struct Stu
{
	char name[20];
	int age;
};
//结构体年龄数据比较
int Stu_age_com(const void* e1,const void* e2)
{
	return ((struct Stu*)e1)->age - ((struct Stu*)e2)->age;
}
//结构体名字数据比较
int Stu_name_com(const void* e1, const void* e2)
{
	return strcmp(((struct Stu*)e1)->name,((struct Stu*)e2)->name);
}
//打印结构体
void print_Stu(struct Stu* p,int sz)
{
	for (size_t i = 0; i < sz; i++)
	{
		printf("%s %d\n", (p + i)->name, (p + i)->age);
	}
}
//测试qsort排序结构体数据
void test2()
{
	struct Stu s[] = { {"zhangsan",30},{"lisi",20},{"wangwu",35} };
	int sz = sizeof(s) / sizeof(s[0]);
	qsort(s, sz, sizeof(s[0]), Stu_age_com);
	print_Stu(s, sz);
	qsort(s, sz, sizeof(s[0]), Stu_name_com);
	print_Stu(s, sz);
}
int main()
{
	test2();
	return 0;
}

结果如下:

【C语言】指针收尾,函数指针的应用,新指针类型void*------回调函数和qsort函数的模拟实现【附源码,图文讲解】_第7张图片

在上述代码中,我们要比较两个结构体并不是简单用关系运算来比较,而是根据结构体成员的不同来具体比较,比如我们按照年龄来,姓名来等等…… 

qsort的排序是用的快速排序算法,排序算法有很多,希尔排序,快速排序,冒泡排序等等……

四、qsort模拟实现

我们先来写出先前讲述的冒泡排序,它存在的一些缺点是什么?

#include

void Bubble_sort(int* arr, int sz)
{
	int i = 0;
	int j = 0;
	for (i = 0; i < sz - 1; i++)
	{
		for (j = 0; j < sz - 1 - i; j++)
		{
			if (arr[j] > arr[j + 1])
			{
				int tmp = arr[j];
				arr[j] = arr[j+1];
				arr[j+1] = tmp;
			}
		}
	}
}

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

int main()
{
	int arr[] = { 1,5,2,3,7,4,6,0,8,9 };
	int sz = sizeof(arr) / sizeof(arr[0]);

	Bubble_sort(arr, sz);
	print_arr(arr, sz);
	return 0;
}

 它一个是只能用来排序整型数据,第二个我们是其实把它写死,比较上也只能有简单的关系比较,那如果我们想扩展一下排序任意数据呢?那我们就需要用到void * 这个类型来接收任意数据类型的地址。

改造的三个点: 

一个是比较方法上要更改,能够比较任意数据

一个是数据类型要更改,也就是改造参数

一个是交换的代码不在是只有整型数据了

 首先第一个在排序算法上,我们要对原先的bubble_sort进行参数增加,首先是起始位置的地址,我们要能接收任意数据类型的地址,就要采用void * 的数据类型,其次是长度我们用sz来接收,大小我们写成width,然后是比较的函数,写成函数指针类型。【C语言】指针收尾,函数指针的应用,新指针类型void*------回调函数和qsort函数的模拟实现【附源码,图文讲解】_第8张图片

其次是交换的地址 

【C语言】指针收尾,函数指针的应用,新指针类型void*------回调函数和qsort函数的模拟实现【附源码,图文讲解】_第9张图片

为了获取地址,我们需要将地址转换成char*一个字节,为什么用char*,因为内存中的内存单元就是一个字节大小,char*刚好是一个字节,这样获得最小单位,然后用width*我所对应的第几个元素,就能得到对应的数据元素地址。

 【C语言】指针收尾,函数指针的应用,新指针类型void*------回调函数和qsort函数的模拟实现【附源码,图文讲解】_第10张图片

然后我们把地址传给我们需要比较的函数,最后是交换的内容。因为数据是二进制存入内存,一个内存单元存一个字节的数据,我们只需要把每个数据的地址传进Swap,然后根据width决定我一次要交换几个内存单元,最后就是完成交换的内容,这样一个Bubble_sort的模拟qsort实现就结束了。

【C语言】指针收尾,函数指针的应用,新指针类型void*------回调函数和qsort函数的模拟实现【附源码,图文讲解】_第11张图片


#include

void Swap(char* buf1,char* buf2,size_t width)
{
	int i = 0;
	for (i = 0; i < width; i++)
	{
		char tmp = *buf1;
		*buf1 = *buf2;
		*buf2 = tmp;
		buf1++;
		buf2++;
	}
}

int int_com(const void* e1, const void* e2)
{
	return *(int*)e1 - *(int*)e2;
}

void Bubble_sort(void* base,size_t sz,
size_t width ,int(*cmp)(const void*e1,const void*e2))
{
	int i = 0;
	int j = 0;
	for (i = 0; i < sz - 1; i++)
	{
		for (j = 0; j < sz - 1 - i; j++)
		{
			if (cmp((char*)base+j*width,(char*)base+(j+1)*width) > 0)
			{
				Swap((char*)base + j * width, (char*)base + (j + 1) * width, width);
			}
		}
	}
}

 测试环节

利用新写的Bubble_sort来排序整型数据

void test1()
{
	int arr[] = { 1,5,2,3,7,4,6,0,8,9 };
	int sz = sizeof(arr) / sizeof(arr[0]);
	Bubble_sort(arr, sz,sizeof(arr[0]),int_com);
	printf_arr(arr, sz);
}

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

 结果如下:

利用Bubble_sort测试结构体数据,我们把先前写的代码给它扣下来,拿来测试

struct Stu
{
	char name[20];
	int age;
};
//结构体年龄数据比较
int Stu_age_com(const void* e1, const void* e2)
{
	return ((struct Stu*)e1)->age - ((struct Stu*)e2)->age;
}
//结构体名字数据比较
int Stu_name_com(const void* e1, const void* e2)
{
	return strcmp(((struct Stu*)e1)->name, ((struct Stu*)e2)->name);
}
//打印结构体
void print_Stu(struct Stu* p, int sz)
{
	for (size_t i = 0; i < sz; i++)
	{
		printf("%s %d\n", (p + i)->name, (p + i)->age);
	}
}
//测试Bubble_sort排序结构体数据
void test2()
{
	struct Stu s[] = { {"zhangsan",30},{"lisi",20},{"wangwu",35} };
	int sz = sizeof(s) / sizeof(s[0]);
	Bubble_sort(s, sz, sizeof(s[0]), Stu_age_com);
	print_Stu(s, sz);
	Bubble_sort(s, sz, sizeof(s[0]), Stu_name_com);
	print_Stu(s, sz);
}

结果如下:

【C语言】指针收尾,函数指针的应用,新指针类型void*------回调函数和qsort函数的模拟实现【附源码,图文讲解】_第12张图片

源码如下: 

#include
#include
//交换函数
void Swap(char* buf1,char* buf2,size_t width)
{
	int i = 0;
	for (i = 0; i < width; i++)
	{
		char tmp = *buf1;
		*buf1 = *buf2;
		*buf2 = tmp;
		buf1++;
		buf2++;
	}
}
//整型数据的比较函数
int int_com(const void* e1, const void* e2)
{
	return *(int*)e1 - *(int*)e2;
}
//冒泡排序
void Bubble_sort(void* base,size_t sz,size_t width ,
				int(*cmp)(const void*e1,const void*e2))
{
	int i = 0;
	int j = 0;
	for (i = 0; i < sz - 1; i++)
	{
		for (j = 0; j < sz - 1 - i; j++)
		{
			if (cmp((char*)base+j*width,(char*)base+(j+1)*width) > 0)
			{
				Swap((char*)base + j * width, 
					(char*)base + (j + 1) * width,
					width);
			}
		}
	}
}
//打印数组
void print_arr(int* arr, int sz)
{
	int i = 0;
	for (i = 0; i < sz; i++)
	{
		printf("%d ", arr[i]);
	}
}
//定义结构体
struct Stu
{
	char name[20];
	int age;
};
//结构体年龄数据比较
int Stu_age_com(const void* e1, const void* e2)
{
	return ((struct Stu*)e1)->age - ((struct Stu*)e2)->age;
}
//结构体名字数据比较
int Stu_name_com(const void* e1, const void* e2)
{
	return strcmp(((struct Stu*)e1)->name, ((struct Stu*)e2)->name);
}
//打印结构体
void print_Stu(struct Stu* p, int sz)
{
	for (size_t i = 0; i < sz; i++)
	{
		printf("%s %d\n", (p + i)->name, (p + i)->age);
	}
}
//测试Bubble_sort排序结构体数据
void test2()
{
	struct Stu s[] = { {"zhangsan",30},{"lisi",20},{"wangwu",35} };
	int sz = sizeof(s) / sizeof(s[0]);
	Bubble_sort(s, sz, sizeof(s[0]), Stu_age_com);
	print_Stu(s, sz);
	Bubble_sort(s, sz, sizeof(s[0]), Stu_name_com);
	print_Stu(s, sz);
}
//Bubble_sort测试整型数组
void test1()
{
	int arr[] = { 1,5,2,3,7,4,6,0,8,9 };
	int sz = sizeof(arr) / sizeof(arr[0]);
	Bubble_sort(arr, sz,sizeof(arr[0]),int_com);
	print_arr(arr, sz);
}

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

感谢各位同伴的支持,本期指针收尾篇就讲解到这啦,接下来指针还会有一个番外练习,如果你觉得写的不错的话,可以给个赞,若有不足,欢迎各位在评论区讨论。   

你可能感兴趣的:(C语言,算法,c语言,开发语言)