《C语言》深入理解指针(4)

1 回调函数是什么?

回调函数就是一个通过函数指针调用的函数。

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

深入理解指针(3)中我们写的计算机的实现的代码中,switch语句中的代码是重复出现的,虽然其中的执行计算的逻辑是有区别的,但是输入输出操作是冗余的,有没有办法简化一些呢?

因为switch语句2中的代码,只有调用函数的逻辑是有差异的,我们可以把调用的函数的地址以参数的形式传递过去,使用函数指针接收,函数指针指向沙漠函数就调用什么函数,这里其实使用的就是回调函数的功能。

使用回调函数改造前

cpp 复制代码
#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("-------   计算器   -------\n");
	printf("--  1.add       2.sub  --\n");
	printf("--  3.mul       4.di  ---\n");
	printf("--------  0.exit  -------\n");
	printf("-------------------------\n");
}
 
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("请输入两个操作数:");
			scanf("%d %d", &x, &y);
			ret = Add(x, y);
			printf("%d\n", ret);
			break;
		case 2:
			printf("请输入两个操作数:");
			scanf("%d %d", &x, &y);
			ret = Sub(x, y);
			printf("%d\n", ret);
			break;
		case 3:
			printf("请输入两个操作数:");
			scanf("%d %d", &x, &y);
			ret = Mul(x, y);
			printf("%d\n", ret);
			break;
		case 4:
			printf("请输入两个操作数:");
			scanf("%d %d", &x, &y);
			ret = Div(x, y);
			printf("%d\n", ret);
			break;
		case 0:
			printf("退出计算器\n");
			break;
		default:
			printf("输入错误,请重新输入!\n");
			break;
		}
	} while (input);
	return 0;
}

使用回调函数改造后

cpp 复制代码
#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 calc(int(*pf)(int, int))    //函数指针作为参数
{
	int x = 0;
	int y = 0;
	int ret = 0;
	printf("请输入两个操作数:");
	scanf("%d %d", &x, &y);
	ret = pf(x, y);
	printf("%d\n", ret);
}

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

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

2 qsort使用举例

qsort 是库函数,这个函数可以完成任意类型数据的排序。使用qsort 要包含头文件 stdlib.h

cpp 复制代码
void qsort (void* base,  //base指向了要排序的数组的第一个元素
            size_t num,  //base指向的数组中的元素个数(待排序的数组的元素的个数)
            size_t size, //base指向的数组中元素的大小(单位是字节)
            int (*compar)(const void* p1,const void* p2));
            //函数指针 - 指针指向的函数是用来比较数组中的2个元素的    
            //当p1指向的元素小于p2指向的元素,返回值<0
            //当p1指向的元素等于p2指向的元素,返回值=0
            //当p1指向的元素大于p2指向的元素,返回值>0

2.1 使用qsort函数排序整型数据

cpp 复制代码
#include<stdio.h>
#include<stdlib.h>

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

int cmp_int(const void* p1, const void* p2)
{
	return *(int*)p1 - *(int*)p2; //升序
	//return *(int*)p2 - *(int*)p1;  //降序
	//void*的指针是不能直接解引用的,我们知道要对比的数据类型是int型
    //所以直接进行强制类型转换后再解引用
}

void test1()
{
	int arr[10] = { 1,5,9,6,3,7,8,4,2 };
	int sz = sizeof(arr) / sizeof(arr[0]);
	print_arr(arr, sz);
	qsort(arr, sz, sizeof(arr[0]), cmp_int);
	print_arr(arr, sz);
}

int main()
{
	//将一个整型数组排序为升序
	test1();
	return 0;
}

2.2 使用qsort排序结构数据

2.2.1 按照年龄来比较

cpp 复制代码
#include<stdlib.h>

struct Stu
{
	char name[20]; //名字
	int age;  //年龄
};

//按年龄来排序
int cmp_stu_by_age(const void* p1, const void* p2)
{
	return ((struct Stu*)p1)->age - ((struct Stu*)p2)->age;  //正序
	//return ((struct Stu*)p2)->age - ((struct Stu*)p1)->age;//逆序
	//void*的指针是不能直接解引用的
	//我们知道要对比的数据类型是结构体类型
	//所以直接进行强制类型转换后再通过->访问结构体成员
}
void test2()
{
	struct Stu arr[] = { {"shubao", 22},{"piku", 20},{"cs",76} };
	int sz = sizeof(arr) / sizeof(arr[0]);
	qsort(arr, sz, sizeof(arr[0]), cmp_stu_by_age);
}

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

2.2.2 按照名字来比较

两个字符串不能直接使用 <>== 等进行比较,而是需要用到库函数strcmp ,用于比较2个字符串,其实就是按照对应位置上字符的ASCII码值的大小来比较的。使用 strcmp 要包含头文件 string.h

cpp 复制代码
abcdef
abz
//这2个字符串前两个字符相等,但是字符串2中的z的ASCII码值比字符串1中的c大
//所以判定字符串2比字符串1大
cpp 复制代码
#include<stdlib.h>
#include<string.h>

struct Stu
{
	char name[20]; //名字
	int age;  //年龄
};

//按名字来排序
int cmp_stu_by_name(const void* p1, const void* p2)
{
	return strcmp(((struct Stu*)p1)->name, ((struct Stu*)p2)->name);  //正序
	//return strcmp(((struct Stu*)p2)->name, ((struct Stu*)p1)->name);  //逆序
	//void*的指针是不能直接解引用的
	//我们知道要对比的数据类型是结构体类型
	//所以直接进行强制类型转换后再通过->访问结构体成员
	//再用strcmp比较字符串,strcmp的返回值逻辑与要求的返回值逻辑一致
}
void test3()
{
	struct Stu arr[] = { {"shubao", 22},{"piku", 20},{"cs",76} };
	int sz = sizeof(arr) / sizeof(arr[0]);
	qsort(arr, sz, sizeof(arr[0]), cmp_stu_by_name);
}

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

3 qsort函数的模拟实现

使用回调函数模拟实现qsort(在冒泡排序的基础上进行修改)。

冒泡排序如下:

cpp 复制代码
void Bubble_Sort(int arr[], int sz)  //函数参数
{
	int i = 0;
    //趟数
	for (i = 0; i < sz - 1; i++)
	{
        //每一趟冒泡排序的过程
		int j = 0;
		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;     //
			}
		}
	}
}

我们要对冒泡排序的3个地方进行改造:

  1. 数据比较的代码需要改造,需要用到回调函数
  2. 交换的代码需要改造,需要适配每一种数据类型的交换
  3. 函数参数需要改造,要排序的数据类型可能是任意的,还要加上一些排序时需要的参数。参数的修改可以参考qsort函数的参数

qsort函数:

cpp 复制代码
void qsort (void* base,  //base指向了要排序的数组的第一个元素
            size_t num,  //base指向的数组中的元素个数(待排序的数组的元素的个数)
            size_t size, //base指向的数组中元素的大小(单位是字节)
            int (*compar)(const void* p1,const void* p2));
            //函数指针 - 指针指向的函数是用来比较数组中的2个元素的    
            //当p1指向的元素小于p2指向的元素,返回值<0
            //当p1指向的元素等于p2指向的元素,返回值=0
            //当p1指向的元素大于p2指向的元素,返回值>0

3.1 函数的参数

cpp 复制代码
void Bubble_Sort2(void* base, 
                    size_t sz, 
                    size_t width, 
                    int(*cmp)(const void* p1, const void* p2));
  1. base:因为qsort可能排序任意类型的数据,为了能够接收任意可能的指针类型,我们就设计成void*
  2. sz:数组的元素个数是未知的,元素个数不可能会是负数,所以使用size_t类型
  3. width:元素的类型是未知的,一个元素到底有多长是不清楚的,元素的长度不可能是负数,所以依然使用size_t类型
  4. cmp:针对不同类型的比较方法是有所差异的,所以需要提供一个比较函数,需要比较的数据的类型是未知的,并且我们不想要去修改数据,函数的参数就使用const void*,我们需要一个明确的比较结果,返回值使用int

3.2 对比的条件

cpp 复制代码
if(cmp((char*)base + j * width, (char*)base + (j + 1) * width) > 0)

我们之前的《C语言》深入理解指针(1)-CSDN博客中的介绍到,char*类型的指针变量+1就是跳过1个字节,我们知道一个元素的宽度是width个字节,那么我们就可以使用char*指针+3*width来找到第3个元素,第四个元素就可以使用char*指针+4*width找到。比较出来的值大于0,我们就交换。

3.3 交换的过程

cpp 复制代码
void Swap(char* buf1, char* buf2, int width)
{
    int i = 0;
    for(i = 0; i < width; i++)
    {
        char tmp = *buf1;
        *buf1 = *buf2;
        *buf2 = tmp;
        buf1++;
        buf2++;
    }
}

交换,我们需要知道需要交换的元素的地址,以及元素的宽度。

一个元素的宽度为width字节,那么我们就将这一个元素分为width份,每一份进行交换,交换width次,就可以实现交换的这一过程。假如说元素的类型是int型,那么它的宽度就是4个字节,就给它拆分成4份,对应的每一份进行交换,交换四次即可完成交换。
一次整型数据的交换

3.4 模拟实现的qsort

cpp 复制代码
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++;
	}
}

void Bubble_Sort2(void* base, size_t sz, size_t width, int(*cmp)(const void* p1, const void* p2))
{
	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);
			}
		}
	}
}

3.5 模拟实现出的qsort排序整型数据

cpp 复制代码
#include<stdio.h>

int cmp_int(const void* p1, const void* p2)
{
	return *(int*)p1 - *(int*)p2;
}

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++;
	}
}

void Bubble_Sort2(void* base, size_t sz, size_t width, int(*cmp)(const void* p1, const void* p2))
{
	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_arr(int arr[], int sz)
{
	int i = 0;
	for (i = 0; i < sz; i++)
	{
		printf("%d ", arr[i]);
	}
	printf("\n");
}

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

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

3.6 模拟实现出的qsort排序结构数据

3.6.1 按年龄排序

cpp 复制代码
#include<stdio.h>

struct Stu
{
	char name[20]; //名字
	int age;  //年龄
};

//按年龄来排序
int cmp_stu_by_age(const void* p1, const void* p2)
{
	return ((struct Stu*)p1)->age - ((struct Stu*)p2)->age;  
}

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++;
	}
}

void Bubble_Sort2(void* base, size_t sz, size_t width, int(*cmp)(const void* p1, const void* p2))
{
	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 test2()
{
	struct Stu arr[] = { {"shubao", 22},{"piku", 20},{"cs",76} };
	int sz = sizeof(arr) / sizeof(arr[0]);
	Bubble_Sort2(arr, sz, sizeof(arr[0]), cmp_stu_by_age);
	int i = 0;
	for (i = 0; i < sz; i++)
	{
		printf("%s %d\n", arr[i].name, arr[i].age);
	}
}

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

输出结果:

3.6.2 按名字排序

cpp 复制代码
#include<stdio.h>

struct Stu
{
	char name[20]; //名字
	int age;  //年龄
};

//按名字来排序
int cmp_stu_by_name(const void* p1, const void* p2)
{
	return strcmp(((struct Stu*)p1)->name, ((struct Stu*)p2)->name);
}

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++;
	}
}

void Bubble_Sort2(void* base, size_t sz, size_t width, int(*cmp)(const void* p1, const void* p2))
{
	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 test3()
{
	struct Stu arr[] = { {"shubao", 22},{"piku", 20},{"cs",76} };
	int sz = sizeof(arr) / sizeof(arr[0]);
	Bubble_Sort2(arr, sz, sizeof(arr[0]), cmp_stu_by_name);
	int i = 0;
	for (i = 0; i < sz; i++)
	{
		printf("%s %d\n", arr[i].name, arr[i].age);
	}
}

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

输出结果:

相关推荐
J-TS2 小时前
线性自抗扰控制LADRC
c语言·人工智能·stm32·单片机·算法
A9better2 小时前
C++——指针与内存
c语言·开发语言·c++·学习
坚持编程的菜鸟2 小时前
互质数的个数
c语言·算法
浅念-3 小时前
C++ 模板初阶:从泛型编程到函数模板与类模板
c语言·开发语言·数据结构·c++·笔记·学习
DevilSeagull4 小时前
C语言: C语言内存函数详解
c语言·开发语言·算法
你怎么知道我是队长5 小时前
C语言---排序算法8---递归快速排序法
c语言·算法·排序算法
白太岁5 小时前
操作系统开发:(8) 任务/线程的创建、调度与管理(实现 tasks.h 与 tasks.c)
c语言·开发语言·bash
cameron_tt6 小时前
定时器中断应用 HC-SR04超声波测距模块、定时器输出PWM应用 控制SG90舵机
c语言·嵌入式硬件
神明不懂浪漫7 小时前
【第十三章】操作符详解,预处理指令详解
c语言·开发语言·经验分享·笔记