函数指针的使用精髓 -- 回调函数+qsort的内部实现

前言:

我们有普通指针(存放普通变量的地址)、数组指针(存放数组的地址)... ... 那函数的地址可以存起来吗?可以的话该怎么用函数指针呢?这是本篇博客探讨的问题... 


目录:​​​​​​​

一、函数指针的定义+简单使用

二、函数指针的使用场景  --  回调函数的定义引入

1.函数指针的使用 + 回调函数引入

2.回调函数定义

三、回调函数qsort的使用

1.qsort的使用

2. 使用原理 -- 了解qsort函数

2.1 qsort函数的参数 

2.2 qsort对compare函数返回值的要求

3. 测试题

四、qsort函数的内部实现

1. 分析

1.1 找差异化部分 

1.2 调用差异化部分的函数

2. 源码


 一、函数指针的定义+简单使用

函数指针 -- 指针,存放函数的地址。或者说,指针指向一个函数。 

函数指针这样用可就太别扭啦~打开方式不对,重来🤪

我们接下来用一个例子,来引入回调函数的定义,并在其中探讨函数指针的用法!

二、函数指针的使用场景  --  回调函数的定义引入

1.函数指针的使用 + 回调函数引入

为了方便理解并使用函数指针,我们先引入一个编程题:写一个计算器,能够根据用户输入case情况,完成加减乘除四种算法。(提示:可以用switch case语句)

代码示例:

#include<stdio.h>
void menu()
{
	printf("**********************************\n");
	printf("*****  1. add     2. sub     *****\n");
	printf("*****  3. mul     4. div     *****\n");
	printf("*****  0. exit               *****\n");
	printf("**********************************\n");
}
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;
}
int main()
{
	int input = 0;
	int x = 0;
	int y = 0;
	int ret = 0;
	do
	{
		menu();
		printf("请选择:>");
		scanf("%d", &input);
		switch (input)
		{
		case 1:
			printf("输入2个操作数:>");
			scanf("%d %d", &x, &y);
			ret = Add(x, y);
			printf("ret = %d\n", ret);
			break;
		case 2:
			printf("输入2个操作数:>");
			scanf("%d %d", &x, &y);
			ret = Sub(x, y);
			printf("ret = %d\n", ret);
			break;
		case 3:
			printf("输入2个操作数:>");
			scanf("%d %d", &x, &y);
			ret = Mul(x, y);
			printf("ret = %d\n", ret);
			break;
		case 4:
			printf("输入2个操作数:>");
			scanf("%d %d", &x, &y);
			ret = Div(x, y);
			printf("ret = %d\n", ret);
			break;
		case 0:
			printf("退出计算器\n");
			break;
		default:
			printf("选择错误\n");
			break;
		}
	} while (input);

	return 0;
}

分析过程: 

提问: 我们会发现:每次完成一个算法,我们就要有输入整数部分(图中红色部分所示),这使得代码非常冗余。有没有好的方式可以解决冗余问题?

思考:可否将重复代码放在一个新函数里面?但是加减乘除的算法函数并不相同啊,如果把代码红色部分放在一起,那俺们怎么进行不同的计算呢?💢

解决:首先我们将冗余部分包装成一个新的函数(叫他F吧),现在这个函数的返回值和参数待定。那既然计算函数不同,那我们就把不同的算法函数地址传给F函数,在F函数里,解引用函数指针就可以找到计算函数,继而完成不同计算...  F函数没有输出值,所以返回类型是void;传入了函数指针,所以参数就是函数指针。void F( int (*p)(int x,int y) )

看!函数指针就派上用场了,我们来改装一下代码...

改装代码示例: 

#include<stdio.h>
void menu()
{
	printf("**********************************\n");
	printf("*****  1. add     2. sub     *****\n");
	printf("*****  3. mul     4. div     *****\n");
	printf("*****  0. exit               *****\n");
	printf("**********************************\n");
}
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 F(int(*p)(int,int))
{
	int x = 0;
	int y = 0;
	int ret = 0;
	printf("输入2个操作数:>");
	scanf("%d %d", &x, &y);
	ret = p(x, y);
	printf("ret = %d\n", ret);
}
int main()
{
	int input = 0;
	do
	{
		menu();
		printf("请选择:>");
		scanf("%d", &input);
		switch (input)
		{
		case 1:
			F(Add);
			break;
		case 2:
			F(Sub);
			break;
		case 3:
			F(Mul);
			break;
		case 4:
			F(Div);
			break;
		case 0:
			printf("退出计算器\n");
			break;
		default:
			printf("选择错误\n");
			break;
		}
	} while (input);

	return 0;
}

 将Add函数的地址传给新函数F, F通过解引用地址来调用Add算法函数。F就叫做回调函数。

这样回调函数可以实现多个计算,帮助解决冗余代码~

2.回调函数定义

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

三、回调函数qsort的使用

1.qsort的使用

在使用之前,我们要先了解它。一个函数,参数是什么,返回类型是什么?完成的是什么操作?qsort是库函数,它是一个回调函数。Performs a quick sort. -- 是干嘛的?是完成快速排序的。

前面提到计算器中回调函数的使用和qsort的使用一样:qsort函数也是接收一个差异化函数并调用

我们先来试着,用qsort函数编写一个对整形数组排序的算法... 代码如图所示: 

2. 使用原理 -- 了解qsort函数

2.1 qsort函数的参数 

看一看它的声明: 

返回类型是void , 有四个参数。

我们先要弄明白,每个参数的含义。既然是排序,那应该是对数组里面的元素排序。

void *base  --  数组地址

 size_t num --  数组长度大小

size_t width --  数组每个元素的字节大小

int (__cdecl *compare )(const void *elem1, const void *elem2 -- 函数指针,指向一个比较函数。比较函数的的返回类型是int,参数是参与比较的数组元素地址

参数的大概意思我们已经知道了,但是对于void *base 以及int (__cdecl *compare )(const void *elem1, const void *elem2有需要注意的地方!一个个参数来分析:

提问1:void *base为什么指针类型是void* ?

思考思考:比较两个数的大小时,变量可能是整型、浮点型、甚至是结构体。为什么对于的参数不是int*,float*...  ?我们在引入部分也提到了,回调函数实现多个计算。既然需要进行不同的计算,如果传入的是指定的类型,比如是int *,那就不能用回调函数排序浮点型和其他的数据类型了。void* 可以接收不同类型的参数。(第四个参数也是一样的原理)

提问2:为什么有const修饰?

因为void*eum1是数组元素的指针,这个值是不能被随意改变的,否则比较的就不是我们想要的两个元素的值了。用const修饰可以很好的避免被修改

2.2 qsort对compare函数返回值的要求

所以在比较函数中我们用的是return>0或者<0或者=0的数字 

提示:

因为前面提到,void*是不能直接被解引用的,也是不能+-整数的。所以比较两个元素时,需要将void*类型的指针转化为元素原本的类型,才能够实现数据的比较。例如:比较整型数组的元素,数组元素的类型本来是int整型的,所以指针类型也应该转成整型。然后再解引用。

3. 测试题

学会了上面对整型数组排序之后,再来做一做结构体数组的排序吧~

案例:用qsort函数,编程实现对结构体数组元素的排序 

代码示例:

#include<stdio.h>
#include<stdlib.h>
//创建结构体类型
struct Stu
{
	char name[20];
	int age;
	double score;
};
//比较函数 -- 比较字符串
int cmp_by_name(const void* num1, const void* num2)
{
	return(  strcmp ( ((struct Stu*)num1)->name , ((struct Stu*)num2)->name  )  );
}
//比较函数 -- 比较age
int cmp_by_age (const void*num1, const void* num2)
{
	return ( ((struct Stu*)num1)->age - ((struct Stu*)num2)->age );
}
int cmp_by_score(const void* num1, const void* num2)
{
	if (((struct Stu*)num1)->score > ((struct Stu*)num2)->score)
	{
		return 1;
	}
	else if (((struct Stu*)num1)->score < ((struct Stu*)num2)->score)
	{
		return -1;
	}
	else
	{
		return 0;
	}
}
//打印排序后结构体的值
void print_(struct Stu arr[],int sz)
{
	for (int i = 0; i < sz; i++)
	{
		printf("%9s %5d %9.2f", arr[i].name, arr[i].age, arr[i].score);
		printf("\n");
	}
	printf("\n");
}
int main()
{
	struct Stu arr[] = { {"Dongxin",19,97.63},{"Lihua",18,89.77},{"Xiaohong",20,100} };
	int sz = sizeof(arr) / sizeof(arr[0]);
	qsort(arr, sz, sizeof(arr[0]), cmp_by_name);//排序name
	print_(arr, sz);
	qsort(arr, sz, sizeof(arr[0]), cmp_by_age);//排序age
	print_(arr, sz);
	qsort(arr, sz, sizeof(arr[0]), cmp_by_score);//排序score
	print_(arr, sz);
	return 0;
}

代码讲解: 

注意事项:

-- 比较name(字符串)

   name是字符串,字符串比较要用不能直接相减!需要用库函数strcmp,记得引头文件 

-- 比较age(int整型)

   首先要明确,我们最终要比较的是结构体的元素里面的age
   (struct Stu*)就是是将进行比较的void指针强制转换成结构体类型指针
   (struct Stu*)num1结构体数组中的第num1个结构体元素,但我们要比较的是结构体里面的      age。 

   (struct Stu*)num1)->age是对结构体的解引用,这样就可以拿到里面的age进行比较

-- 比较浮点数 (浮点型)

   比较两个浮点数,由于浮点数在内存中的存储比较特殊,不能直接相减返回。

四、qsort函数的内部实现

既然会用了,那再走远一点...qsort函数内部是怎么实现的呢?

编程:模仿qsort的功能实现一个通用的冒泡排序

1. 分析

1.1 找差异化部分 

前面计算器的案例提到过:我们需要找差异化的部分

 先来看看冒泡排序差异化部分在哪里...

#include<stdio.h>
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_(int arr[],int sz)
{
	for (int i = 0; i < sz; i++)
	{
		printf("%d ", arr[i]);
	}
	printf("\n");
}
int main()
{
	int arr[10] = {1,4,5,3,2,7,6,8,0,10 };
	int sz = sizeof(arr) / sizeof(arr[0]);
	bubble_sort(arr,sz);
	print_(arr, sz);
	return 0;
}

排序过程中对传入的数据大小比较是不同的,如红色框框部分。如果是字符串进行比较,要用strcmp;如果是浮点型、结构体,也要根据相应的类型做不同的计算。

1.2 调用差异化部分的函数

                                                        ⬇⬇⬇

 重点来了:怎么在qsort里面用cmp函数?

函数名作为函数地址的时候可以直接调用。所以直接cmp_int()

⬇⬇⬇

cmp函数接受的是地址,所以调用cmp-int,()传的是地址。要想保证一个函数实现多个计算,不能强制转化成int,double等特定的类型。由于qsort接收了width参数,我们可以把元素指针转换成char*类型。由(char*)ch+width决定走一步的步长,即确定指针类型。

i,j++,步长需要变化。(char*)th + j * width可以解决问题。

⬇⬇⬇

接下来就是交换值,char*指针每次只能访问一个字节。但是目标元素的单位可不止一个字节,所以需要一个字节一个字节去改变值。

2. 源码

#include<stdio.h>
int cmp_int(const void* e1, const void* e2)
{
	return *(int*)e1 - *(int*)e2;
}
void swap(char* element1, char* element2, int width)
{
	for (int i = 0; i < width; i++)
	{
		char tmp = *element1;
		*element1 = *element2;
		*element2 = tmp;
		element1++; element2++;
	}
}
void my_qsort(int arr1[], int sz, int 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_int((char*)arr1 + j * width, (char*)arr1 + (j + 1) * width) > 0)
			{
				swap((char*)arr1 + j * width, (char*)arr1 + (j + 1) * width, width);
			}
		}
	}
}
void print_(int arr2[],int sz)
{
	for (int i = 0; i < sz; i++)
	{
		printf("%d ", arr2[i]);
	}
	printf("\n");
}
int main()
{
	int arr[10] = { 9,8,7,6,5,4,3,2,1,0 };
	int sz = sizeof(arr) / sizeof(arr[0]);
	my_qsort(arr,sz,sizeof(arr[0]),cmp_int);
	print_(arr,sz);
	return 0;
}


学自己不会的知识才能慢慢成长~

  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
qsort函数是C标准库中的一个快速排序函数,用于对数组进行排序。下面是一个简单的示例,展示如何使用C语言实现qsort函数: ```c #include <stdio.h> // 比较函数,用于指定排序规则 int compare(const void *a, const void *b) { // 转换为指向int类型的指针 int *x = (int *)a; int *y = (int *)b; if (*x < *y) { return -1; } else if (*x > *y) { return 1; } else { return 0; } } // 快速排序函数 void quicksort(int arr[], int low, int high) { if (low < high) { int pivot = arr[low]; int i = low + 1; int j = high; while (i <= j) { while (i <= j && arr[i] <= pivot) { i++; } while (i <= j && arr[j] > pivot) { j--; } if (i < j) { int temp = arr[i]; arr[i] = arr[j]; arr[j] = temp; } } int temp = arr[low]; arr[low] = arr[j]; arr[j] = temp; quicksort(arr, low, j - 1); quicksort(arr, j + 1, high); } } int main() { int arr[] = {5, 2, 8, 9, 1}; int n = sizeof(arr) / sizeof(arr[0]); // 使用自定义的比较函数进行排序 qsort(arr, n, sizeof(int), compare); printf("Sorted array: "); for (int i = 0; i < n; i++) { printf("%d ", arr[i]); } printf("\n"); return 0; } ``` 在这个示例中,我们定义了一个compare函数作为qsort函数的比较函数。这个比较函数接收两个指针参数,指向要比较的元素。函数内部将这些指针转换为int类型的指针,并根据需要的排序规则返回-1、0或1。 然后,我们使用自定义的quicksort函数来实现快速排序。这个函数使用了经典的快速排序算法来对数组进行排序。 最后,在main函数中,我们声明一个整型数组arr,并使用qsort函数对其进行排序。排序完成后,我们打印出排序后的数组。 请注意,这只是一个简单的示例,用于演示如何使用C语言实现一个类似于qsort函数的功能。在实际使用中,建议使用C标准库提供的qsort函数,因为它经过了优化,并且适用于各种数据类型。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值