C语言:大战指针“哥斯拉”(3)

目录

一、前言

二、函数指针数组

三、回调函数

四、qsort函数

(一)排序整型数据 

 (二)排序结构数据

(三)模拟实现qsort函数 

 五、总结


一、前言

大家新年好,小欣在这里祝大家新年快乐,身体健康,学业进步,拿到心仪的offer!!!在新的一年里,小欣将继续和大家一起学习并分享知识点,让我们一起携手并进,书写2024年的精彩画卷!!!今天我们一起学习打败指针“哥斯拉”的最后一招。

二、函数指针数组

所谓函数指针数组,就是一个存放函数指针的数组。我们以一个计算器程序为例子。

代码如下所示:

#define _CRT_SECURE_NO_WARNINGS
#include 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 a, int b)
{
	return a + b;
}
//减法运算
int sub(int a, int b)
{
	return a - b;
}
//乘法运算
int mul(int a, int b)
{
	return a * b;
}
//除法运算
int div(int a, int b)
{
	return a / b;
}
int main()
{
	int x, y;
	int input = 1;
	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;
}

我们可以发现,实现这样一个简单的计算器程序代码比较多也有些冗余,维护起来也比较麻烦。

现在,我们用函数指针数组来修改上面的代码,让代码变简洁,进而更好维护。

修改后的代码如下:

#define _CRT_SECURE_NO_WARNINGS
#include //计算器(转移表)
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 a = 0;
	int b = 0;
	int ret = 0;
	do
	{
		menu();
		//使用函数指针数组的方式
		//这里函数指针数组的效果,我们称为转移表
		int (*pfArr[])(int, int) = { NULL,Add,Sub,Mul,Div };
		printf("请选择:");
		scanf("%d", &input);
		if (input == 0)
		{
			printf("退出计算器\n");
		}
		else if (input >= 1 && input <= 4)
		{
			printf("请输入两个操作数:");
			scanf("%d %d", &a, &b);
			ret = pfArr[input](a, b);
			printf("%d\n", ret);
		}
		else
		{
			printf("选择错误,重新选择\n");
		}
	} while (input);
	return 0;
}

我们将函数名(地址)存入数组中,接着进入while循环,打印菜单,提示用户输入,进入if语句判断,然后用input来存储用户选择的功能,最后让input作为函数指针数组的下标,我们通过下标来访问数组的元素。 

我们修改后的代码不再使用switch语句,而是使用if语句和函数指针数组,因此冗余的代码也全部被优化掉,这为我们以后添加函数功能,还是删除一些功能,都变得更加方便,只要修改数组大小,删除数组元素就OK了。 

三、回调函数

回调函数的本质就是函数指针,只不过定义有所区别。它的定义是:把函数的指针(地址)作为参数传递给另一个函数,当这个指针被用来调用其所指向的函数时,称它为回调函数。

我们也可以通过回调函数来调用函数,更加方便地实现计算器功能。

#define _CRT_SECURE_NO_WARNINGS
#include //计算器(回调函数)
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 calc(int(*pf)(int, int))
{
	int a = 0;
	int b = 0;
	int ret = 0;
	printf("请输入两个操作数:");
	scanf("%d %d", &a, &b);
	ret = pf(a, b);
	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("退出计算器");
			break;
		default:
			printf("选择错误,重新选择");
			break;
		}
	} while (input);
	return 0;
}

实现结果如下: 

我们通过使用calc回调函数也是能够调用加减乘除这些函数的功能,这让我们在实现一个计算器程序时更加方便而且简化代码,使代码不冗余。

四、qsort函数

qsort是一个库函数,是基于快速排序算法实现的一个排序函数。

void qsort(

    void* base,//base指向了要排序的数组的第一个元素

    size_t num,//base指向的数组中的元素个数(待排序的数组的元素的个数)

    size_t size,//base指向的数组中元素的大小(单位是字节)

    int (*compare)(const void* p1, const void* p2)//函数指针-指针指向的函数是用来比较数组中的2个元素的

);

(一)排序整型数据 

下面我们通过使用qsort函数来实现整型升序排序。

#define _CRT_SECURE_NO_WARNINGS
//将一组整型数组升序排序
#include //com_arr是用来比较p1和p2指向的元素大小
int com_arr(const void* p1, const void* p2)
{
	return *(int*)p1 - *(int*)p2;//升序用p1-p2,降序则p2-p1
}
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[] = { 5,2,6,8,7,10,1,3,9,4 };
	int sz = sizeof(arr) / sizeof(arr[0]);
	print_arr(arr, sz);
	qsort(arr, sz, sizeof(arr[0]), com_arr);
	print_arr(arr, sz);
}
int main()
{
	test1();
	return 0;
}

运行结果如下:

 

 (二)排序结构数据

排序结构数据也可以用qsort函数来进行排序,给大家举个例子:

#define _CRT_SECURE_NO_WARNINGS
//qsort函数排序结构体数据
#include #include #include struct Stu
{
	char name[20];//姓名
	int age;//年龄
};
//比较两个结构体数据,不能用> < == 比较
//1.可以按照姓名比较
int cmp_stu_by_name(const void* p1, const void* p2)
{
	//两个字符串不能用> < ==比较
	//而是使用库函数strcmp - string compare
	//strcmp其实是按照对应位置上的字符的ASCII码值的大小进行比较
	return strcmp(((struct Stu*)p1)->name, ((struct Stu*)p2)->name);
}
void test2()
{
	struct Stu arr[] = { {"ZhnagSan",20}, {"LiSi",18},{"WangWu",22} };
	int sz = sizeof(arr) / sizeof(arr[0]);
	qsort(arr, sz, sizeof(arr[0]), cmp_stu_by_name);
	int i = 0;
	for (i = 0; i < sz; i++)
	{
		printf("%s ", arr[i].name);
	}
}
//2.可以按照年龄比较
int cmp_stu_by_age(const void* p1, const void* p2)
{
	return ((struct Stu*)p1)->age - ((struct Stu*)p2)->age;
}
void test3()
{
	struct Stu arr[] = { {"ZhnagSan",20}, {"LiSi",18},{"WangWu",22} };
	int sz = sizeof(arr) / sizeof(arr[0]);
	qsort(arr, sz, sizeof(arr[0]), cmp_stu_by_age);
	int i = 0;
	for (i = 0; i < sz; i++)
	{
		printf("%d ", arr[i].age);
	}
}
	
int main()
{
	test2();//按姓名进行比较
	//test3();//按年龄进行比较
	return 0;
}

排序后的结果如下: 

注意:比较两个结构体数据,不能用> < == 比较

比较字符要使用库函数strcmp

strcmp其实是按照对应位置上的字符的ASCII码值的大小进行比较

(三)模拟实现qsort函数 

qsort是一个库函数,它是使用快速排序的算法来进行排序的。在上一期我们讲过冒泡排序的原理和使用,感兴趣的朋友可以参考C语言:大战指针“哥斯拉”(2),现在我们将冒泡排序进行改造,来模拟实现qsort函数。

Tips:

改造的前提仍是使用冒泡排序

1.比较的代码部分要改成回调函数。

2.交换的代码部分也要进行修改。

#define _CRT_SECURE_NO_WARNINGS
//设计和实现bubble_sort,这个函数能够排序任何类型的数据。
#include #include void print(int arr[], int sz)
{
	int i = 0;
	for (i = 0; i < sz; i++)
	{
		printf("%d ", arr[i]);
	}
}
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 temp = *buf1;
		*buf1 = *buf2;
		*buf2 = temp;
		buf1++;
		buf2++;
	}
}
void bubble_sort(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 test()
{
	int arr[] = { 1,5,6,8,7,3,2,10,4,9 };
	int sz = sizeof(arr) / sizeof(arr[0]);
	bubble_sort(arr, sz, sizeof(arr[0]), cmp_int);
	print(arr, sz);
}
int main()
{
	test();
	return 0;
}

 升序排序结果如下:

小知识:

int (* compar)( const void *, const void * )

函数规定,在待比较的两个参数中,若

前一个参数 > 后一个参数,则返回值>0;

前一个参数 = 后一个参数,则返回值=0;

前一个参数 < 后一个参数,则返回值<0; 

上面的代码我们通过使用回调函数,通过bubble_sort 中的函数指针的形参,在函数中解引用这个指针后,调用指向的函数,接着以被调用的函数的返回值为参考,最终对数据进行交换排序。

结构体类型能进行这样的排序,类似的代码如下:

#define _CRT_SECURE_NO_WARNINGS
//使用bubble_sort对结构体数据进行排序
#include #include 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);
}
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 temp = *buf1;
		*buf1 = *buf2;
		*buf2 = temp;
		buf1++;
		buf2++;
	}
}
void bubble_sort(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 test()
{
	struct Stu arr[] = { {"Zhangsan",18},{"LiSi",24},{"WangWu",20} };
	int sz = sizeof(arr) / sizeof(arr[0]);
	bubble_sort(arr, sz, sizeof(arr[0]), cmp_stu_by_name);
	bubble_sort(arr, sz, sizeof(arr[0]), cmp_stu_by_age);
	//打印arr数组的内容
	int i = 0;
	for (i = 0; i < sz; i++)
	{
		printf("%s %d\n", arr[i].name, arr[i].age);
	}
}
int main()
{
	test();
	return 0;
}

输出结果:

 

我们在模拟实现qsort函数时要注意,函数的形参一直都是 void* 型,传入的实参要通过强制类型转换后才能得到返回值。

 五、总结

通过上面的学习,恭喜大家学会打败指针“哥斯拉”的最后一招,同时蜡笔小欣也给大家的坚持点赞。指针让我们在写代码程序时更加方便快捷,也能简化一下冗余的代码,只要我们平时多加练习,对指针的使用会更加熟练,我们下期再见。