C语言指针 下篇

92 阅读5分钟

持续创作,加速成长!这是我参与「掘金日新计划 · 10 月更文挑战」的第20天,点击查看活动详情

一、回调函数

1、什么是回调函数

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

简单来说:就是有一个A函数,这里不是直接去调用A函数,而是把A函数的地址传给B函数(这里的B函数的参数就是一个函数指针),这时通过B函数去调用A函数时就称为回调函数 在这里插入图片描述

2、实例:

/*********************************************************************** 目的:继续使用switch语句来实现并简化【实例1(实现计算器) - 纠正2】中的代码 在这里插入图片描述 想法:这里将会把红色标识区域封装成一个函数,调用即可。但这里有个问题是printf和scanf两条语句是相同的,但是每次调用这个函数时传的参数可不一样 分析:这里将封装一个函数Calc来帮我们分别计算加减乘除,而这个函数的参数将是一个函数指针pf,来接收不同模块的代码。然后再通过pf所指向的这个函数的地址去间接调用那个函数。这里就用到了回调函数 平台:Visual studio 2017 && windows *************************************************************************/

#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>
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");
}
int Calc(int (*pf)(int, int))//这里使用函数指针来做为Calc的参数
{
	int x = 0;
	int y = 0;
	printf("请输入2个操作数\n");
	scanf("%d %d", &x, &y);
	return pf(x, y);//这里通过pf指针来调用所对应传过来的函数
}
int main()
{
	int input = 0;
	do
	{
		menu();
		int ret = 0;
		printf("请选择:>");
		scanf("%d", &input);
		switch(input)
		{
		case 1:
			ret = Calc(Add);//这里应把Add函数的地址传给Calc,且在Calc中通过指针调用Add
			printf("ret = %d\n", ret);
			break;
		case 2:
			ret = Calc(Sub);
			printf("ret = %d\n", ret);
			break;
		case 3:
			ret = Calc(Mul);
			printf("ret = %d\n", ret);
			break;
		case 4:
			ret = Calc(Div);
			printf("ret = %d\n", ret);
			break;
		case 0:
			printf("退出程序\n");
			break;
		default:
			printf("输入错误,请重新输入!\n");
			break;	
		}
	}while(input);
	return 0;
}

3、qsort函数

1、bubble_sort

💨 之前有学习过冒泡排序,再来回顾一下

#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 temp = arr[j];
				arr[j] = arr[j + 1];
				arr[j + 1] = temp;
			}
		}
	}
}  
void print_arr(int arr[], int sz)
{
	int i = 0;
	for(i = 0; i < sz; i++)
	{
		printf("%d ", arr[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]);
	//升序
	bubble_sort(arr, sz);
	print_arr(arr, sz);
	return 0;
}

2、解读qsort函数

💨 bulbble_sort是我们自己实现的函数,缺点是只能排序一种类型,如果还想排序其它类型的数组,就需要自己设计。 qsort ():快速排序,可以排序整型、字符型、结构体等等。接下来我们就来了解一下qsort函数:

qsort函数头:<stdlib.h> qsort函数原型: 在这里插入图片描述 函数参数(我们来看一下文档): 在这里插入图片描述 全英文的文档可能对大家还是有点不友好,这里把大概意思翻译一下 void* base:base中存放的是待排序数据中第一个对象的地址 size_t num:num是排序数组元素的个数 size_t size:size是排序数据中一个元素的大小,单位是字节 int (* cmp) (const void**, const void*) :cmp是用来比较待排序数据中的2个元素的函数。 返回的是>0的数字:第1个元素 > 第2个元素 返回的是=0的数字:第1个元素 = 第2个元素 返回的是<0的数字:第1个元素 < 第2个元素 ==注:我们不防可以推理一下,当开发人员在设计这个函数时可能会想到的场景== ==1、这个函数是实现不同类型的数组排序,所以base必需有能力指向不同的数据类型,所以base这个指针的类型就是void类型== ==2、同样的这个函数必需知道你待排序的数组里有几个元素,所以这里又设计了num来接收== ==3、如果void base指向数组时,它并不知道一个元素有多大(因为这是void类型),所以这里还需要一个参数size来接收一个数组元素有多大== ==4、因为不同的类型比较,比较的方法也有所差异(比如int类型使用>、<比较;而char类型使用strcmp比较),所以这里就使用函数指针来接收,且所指向的参数部分也是void类型, 所以开发人员就把两个元素比较的这个函数交给使用者来确定==

💨 使用qsort函数来排序整型数组

	#include<stdlib.h>
	#include<stdio.h>
	int cmp_int(const void* e1, const void* e2)
	{
		//return *(int*)e1 - *(int*)e2;//升序
		return *(int*)e2 - *(int*)e1;//降序
	}
	void print_arr(int arr[], int sz)
	{
		int i = 0;
		for(i = 0; i < sz; i++)
		{
			printf("%d ", arr[i]);
		}
		printf("\n");
	}
	int main()
	{
		int arr[10] = {8,9,0,6,5,1,3,2,4,7};
		int sz = sizeof(arr)/sizeof(arr[0]);
		qsort(arr, sz, sizeof(arr[0]), cmp_int);
		print_arr(arr, sz);
		return 0;
	}

💨 使用qsort函数来排序结构体


```c
#include<stdlib.h>
#include<string.h>
#include<stdio.h>
struct Stu
{
	char name[20];
	int age;
};
int sort_by_age(const void* e1, const void* e2)
{
	//return ((struct Stu*)e1) -> age - ((struct Stu*)e2) -> age;//升序
	return ((struct Stu*)e2) -> age - ((struct Stu*)e1) -> age;//降序
}
int sort_by_name(const void* e1, const void* e2)
{
	//return strcmp(((struct Stu*)e1) -> name, ((struct Stu*)e2) -> name);//升序
	return strcmp(((struct Stu*)e2) -> name, ((struct Stu*)e1) -> name);
}

int main()
{
	struct Stu s[] = {{"zhangshan", 30}, {"lisi", 34}, {"wangwu", 20}};
	//按照年龄来升序
	qsort(s, sizeof(s)/sizeof(s[0]), sizeof(s[0]), sort_by_age);
	//按照名字来排序
	qsort(s, sizeof(s)/sizeof(s[0]), sizeof(s[0]), sort_by_name);
	return 0;
}

3、模仿qsort函数实现冒泡排序的通用算法

#include<stdio.h>
void Swap(char* buf1, char* buf2, int width)
{
	int i = 0;
	for(i = 0; i < width; i++)
	{
		char temp = *buf1;
		*buf1 = *buf2;
		*buf2 = temp;
		buf1++;
		buf2++;
	}
}
void bubble_sort(void* base, int sz, int width, int (*cmp)(const void* e1, const void* e2))
{
	int i = 0;
	for(i = 0; i < sz - 1; i++)
	{
		int j = 0;
		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(int arr[], int sz)
{
	int i = 0;
	for(i = 0; i < sz; i++)
	{
		printf("%d ", arr[i]);
	}
}
int cmp_int(const void* e1, const void* e2)
{
	//return *(int*)e1 - *(int*)e2;//升序
	return *(int*)e2 - *(int*)e1;//降序
}
int main()
{
	int arr[] = {8,9,0,6,5,1,3,2,4,7};
	int sz = sizeof(arr)/sizeof(arr[0]);
	bubble_sort(arr, sz, sizeof(arr[0]), cmp_int);
	print(arr, sz);
	return 0;
}

分析: bubble_sort这个函数要模仿qsort函数,这是待排序的数组: 在这里插入图片描述

1、调用bubble_sort函数:首先参数部分一定是按照qsort函数参数来模拟的 (数组首元素,数组元素个数,数组元素大小,自定义的比较函数) 在这里插入图片描述 2、定义cmp_int函数 这里e1和e2接收了2个要比较的参数,如果要把它们的结果返回去,就需要将void* 类型强转为int* 类型才可以在这里插入图片描述

3、定义bubble_sort函数: 在这里插入图片描述 3.1、确定好循环次数 在这里插入图片描述 3.2、接着就是应用回调用函数机制(通过bubble_sort函数的参数 -> cmp函数指针来调用cmp_int函数并把两两元素传参) ==确定要传参的元素:== base + j, base + j + 1 -> 逻辑上是可行的,但是base的类型是void*,语法不支持 所以: 这里不管是什么类型都将base转换为char*类型(因为它是类型大小的最小单位),再加上 j 乘上1个元素的大小就可以确定元素地址 ==确定函数的返回值:== 如果cmp这个函数的返回值>0则交换,刚好对应qsort函数 在这里插入图片描述 在这里插入图片描述 3.3、再定义并调用一个交换的函数 把两元素的地址传给Swap函数,因为类型的缘故,我们并不知道交换多大空间,所以这里把wide也传参的原因是让这个数据一个字节一个字节的进行交换 在这里插入图片描述