【C语言】指针进阶知识终章

本文深入探讨C语言中的函数指针,通过简单计算器的实现揭示其用法,消除代码冗余。讲解了函数指针数组的概念,并展示了如何通过函数指针数组简化代码。此外,还介绍了回调函数的概念,通过qsort函数和冒泡排序的通用版实现,阐述了回调函数在排序算法中的应用。最后,文章提供了完整的代码示例,帮助读者更好地理解和掌握这些概念。
摘要由CSDN通过智能技术生成

✨作者:@平凡的人1

✨专栏:《C语言从0到1》

✨一句话:凡是过往,皆为序章

✨说明: 过去无可挽回, 未来可以改变


🌹感谢您的点赞与关注,同时欢迎各位有空来访我的🍁平凡舍


🚀前言

回顾我们前面学习了指针数组、数组指针以及简单介绍了函数指针,传参问题等。下面我们将在这些学习过的内容之上继续延展下去,通过这一篇博客,你可以收获更多的知识与内容,同时夯实自己的基础。本篇内容可能比较多,请耐心仔细阅读!💖

动态

🚀有趣的代码

开始之前,基于前面的基础,我们先来看看两个有趣的代码

//代码1 
(*(void (*)())0)();

//代码2
void (*signal(int , void(*)(int)))(int);

这两个代码是什么意思呢?先想一想

代码1:

想看里面的部分void(*p)();p是函数指针,所以对于void(*)()是函数指针类型,0本身是个值,0之前放了个类型,强制类型转换,然后进行解引用。所以说代码1是一次函数调用,调用的0作为地址处的函数.

1.把0强制类型转换为:无参,返回类型是void的函数的地址

2.调用0地址处的这个函数

代码2:

signal是函数名,有两个参数,一个是整型,一个是函数指针类型,此时简单来说只剩下void(*)(int),这又是一个函数指针类型。所以说,

代码2是一次函数声明,声明的signal函数的第一个参数的类型是int,第二参数的类型是函数指针,该函数指针指向的函数参数是int,返回类型是void,signal函数的返回类型也是一个函数指针

其实这看起来是难以理解的,所以我们可以用typedf(起别名)来简化一下,更加容易认识代码:

image-20220602133903359

所以,我们要学会去拆分一下代码,不会导致看不懂别人写的代码是什么意思。

我们前面学到了函数指针,但是却没有举例说到函数指针的用途,函数指针究竟能够去做些什么呢?下面,我们一起来看一看。👇

查看源图像

🚀简单计算器

我们将基于简单计算器这个例子来阐述函数指针的用处在于哪,或者说怎么去用上函数指针呢?我们先来简单模拟实现一下简单计算器(基于整型类型)

实现整型加减乘除的功能

#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);
		printf("请选择2个操作数:>");
		scanf("%d %d", &x, &y);
		switch (input)
		{
		case 1:
			ret = Add(x, y);
			printf("%d\n", ret);
			break;
		case 2:
			ret = Sub(x, y);
			printf("%d\n", ret);
			break;
		case 3:
			ret = Mul(x, y);
			printf("%d\n", ret);
			break;
		case 4:
			ret = Div(x, y);
			printf("%d\n", ret);
			break;
		case 0:
			printf("退出计算器\n");
			break;
		default:
			printf("选择错误\n");
			break;
		}
	} while (input);
	return 0;
}

简单测试运行一下:

image-20220602151713048

你会发现,输入0退出居然要输入两个操作数,这是为什么呢?因为在操作之前我们把输入的数放在前面了,非常的奇怪,退出前居然还要输入两个数,这时候我们稍微改进一下:

#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("%d\n", ret);
			break;
		case 2:
			printf("请选择2个操作数:>");
			scanf("%d %d", &x, &y);
			ret = Sub(x, y);
			printf("%d\n", ret);
			break;
		case 3:
			printf("请选择2个操作数:>");
			scanf("%d %d", &x, &y);
			ret = Mul(x, y);
			printf("%d\n", ret);
			break;
		case 4:
			printf("请选择2个操作数:>");
			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;
}

稍微测试运行一下:

image-20220602163841493

总算解决刚开始出现的问题了,但是这时候又有一个问题:

image-20220602164114980

这一段代码太过冗余了,有点重复性,重复度太高了,我们想想办法解决?

能不能把相同的代码抽离出来,把相同的代码封装成一个函数,下面我们利用函数指针进行改进一下,避免代码的冗余

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

简单测试运行一下:

image-20220602170706917

没有任何问题,解决了代码的冗余问题。这就是函数指针的作用。

通过函数地址传递给函数参数,进入函数内部,去调用函数,这就是回调函数。后面会讲到。

查看源图像


🚀函数指针数组

指向函数指针数组的指针是一个 指针

开始之前,我们先来理解函数指针数组:把函数和指针放在数组中,其实就是函数指针数组,怎么理解呢?看一段代码:

image-20220602172341399

怎么去调用里面的函数呢?

image-20220602172819980 image-20220602172900428

所以函数指针数组有什么用?怎么去用?下面来进行演示

还是刚开始冗余代码版本的计算器:

#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("%d\n", ret);
			break;
		case 2:
			printf("请选择2个操作数:>");
			scanf("%d %d", &x, &y);
			ret = Sub(x, y);
			printf("%d\n", ret);
			break;
		case 3:
			printf("请选择2个操作数:>");
			scanf("%d %d", &x, &y);
			ret = Mul(x, y);
			printf("%d\n", ret);
			break;
		case 4:
			printf("请选择2个操作数:>");
			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;
}

我们想一下,如果要添加实现x&y,x^y,x>>y,x<<y的功能,此时就是添加4,5,6,7,8之类的选项,case的选项越来越多以此类推,代码会变得越来越长,这时候,把代码写得整洁一些:把switch语句去掉,创建一个函数指针数组存放函数,通过输入的选择作为下标去调用即可,下面我们来看看代码的修改:

#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;
	int (*pfArr[5])(int, int) = { 0,Add, Sub,Mul,Div};
	//添加0进去,使之下标对应起来
	do
	{
		menu();
		printf("请选择:>");
		scanf("%d", &input);
		//直接让input作为下标
		if (input == 0)
		{
			printf("退出计算器");
		}
		else if (input >= 1 && input <= 4)
		{
			printf("请选择2个操作数:>");
			scanf("%d %d", &x, &y);
			ret = pfArr[input](x, y);
			printf("%d\n", ret);
		}
		else
		{
			printf("选择错误\n");
		}
	} while (input);
	return 0;
}

这样修改的好处在于,以后想添加新的功能,只需要把函数的地址放在数组里面即可,改变范围即可,稍微调整一下代码即可。通过函数指针数组便于以后修改代码。通过这个简单的例子,演示了函数指针数组的作用。下面我们来简单测试一下:

image-20220602191215848

🚩指向函数指针数组的指针

在这里,顺便提一提指向函数指针数组的指针。前面我们写了函数指针数组,是数组,我们对它&,放到一个指针里面即可。

这里基于上述的函数指针数组来用代码简单表示一下:

image-20220602192213673

当然,你会发现,可以套娃套下去…这里就不展开说明了

查看源图像


🚀回调函数

刚开始,实现计算机的时候有说到:

image-20220602192536817

这就是用了回调函数的机制,什么是回调函数?

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

说到这里,太抽象了,难以理解,这时候,我们请出一个例子qsort函数的使用。

说到qsort函数,我们先来说一说冒泡排序

🚩冒泡排序优化版

void bubble_sort(int arr[], int sz)
{
	int i = 0;
	for (i = 0; i < sz - 1; i++)
	{
		int flag = 1;
		//用来判断数组是否有序,提高效率
		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;
				flag = 0;
			}
		}
		if (flag == 1)
		{
			break;
		}
	}
}
#include <stdio.h>
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);
	int i = 0;
	for (i = 0; i < sz; i++)
	{
		printf("%d ", arr[i]);
	}
	return 0;
}

虽然优化了一下下,提高了效率,但是不管怎么优化,都只能排序整型数组。那怎么办呢?qsort

🚩qsort函数的使用

image-20220602194422843

使用快速排序的思想实现的一个排序函数

下面,我们来简单理解一下qsort函数的参数的意思:

image-20220602195517237

可以看到,比较函数有void*,所以我们很有必要来理解一下void*指针:

image-20220602200351183

这时候,如果用void*来接收,就不会报警告了:

image-20220602200508468

void*是无具体类型的指针,这种指针可以接收任意类型的地址,void*是无具体类型的指针,所以不能解引用操作,也不能±整数操作。这里的参数是void*的原因是因为不知道传过来的类型的指针是什么,所以定义为void*

下面我们还是通过上面的例子来对qsort函数进行简单应用:(记得引用头文件 #include <stdlib.h>)

image-20220602200945453

通过qsort成功实现排序,那能不能实现降序?

image-20220602201110268

我们只要通过改变e1和e2相减的位置即可实现。使之逻辑相反。这个就是回调函数来实现qsort的功能!

这里只是qsort的基本使用。

上面是利用qsort函数来排序整型的,下面我们利用qsort函数来排序结构体

通过结构体的名字进行排序:

image-20220602202813449

通过结构体的年龄来进行排序:

image-20220602203216623

好了,通过上面,已经对qsort有了一定的认识,并且会逐渐的运用,这时候,想想:怎么把冒泡排序改造一下

🚩冒泡排序通用版

把冒泡排序改造成类似qsort函数的实现

void Swap(char* a, char* b,int width)
{
	int i = 0;
	for (i = 0; i < width; i++)
	{
		char tmp = *a;
		*a = *b;
		*b = tmp;
		a++;
		b++;
	}
}

int cmp_int(const void* e1, const void* e2)
{
	return *(int*)e2 - *(int*)e1;
}
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 flag = 1;
		//用来判断数组是否有序,提高效率
		int j = 0;
		for (j = 0; j < sz - 1 - i; j++)
		{
			//通过利用强转base为(char*)乘以宽度的多少来进行比较
			if (cmp((char*)base+j*width,(char*)base+(j+1)*width)>0)
			{
				Swap((char*)base + j * width, ((char*)base + (j + 1) * width),width);
				flag = 0;
			}
		}
		if (flag == 1)
		{
			break;
		}
	}
}

void test()
{
	int arr[] = { 1,2,3,4,5,6,7,8,9,10 };
	int sz = sizeof(arr) / sizeof(arr[0]);
	bubble_sort(arr, sz, sizeof(arr[0]), cmp_int);
	int i = 0;
	for (i = 0; i < sz; i++)
	{
		printf("%d ", arr[i]);
	}
}

int main()
{
	test();
}
image-20220602205641844

运行成功!在这里,我们稍微改造了冒泡排序,现在,来通过冒泡排序,排序结构体:

struct Stu
{
	char name[20];
	int age;
};

int cmp_stu_by_age(const void* e1, const void* e2)
{
	return ((struct Stu*)e1) ->age - ((struct Stu*)e2)->age;
}

void Swap(char* a, char* b,int width)
{
	int i = 0;
	for (i = 0; i < width; i++)
	{
		char tmp = *a;
		*a = *b;
		*b = tmp;
		a++;
		b++;
	}
}

int cmp_int(const void* e1, const void* e2)
{
	return *(int*)e2 - *(int*)e1;
}
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 flag = 1;
		//用来判断数组是否有序,提高效率
		int j = 0;
		for (j = 0; j < sz - 1 - i; j++)
		{
			//通过利用强转base为(char*)乘以宽度的多少来进行比较
			if (cmp((char*)base+j*width,(char*)base+(j+1)*width)>0)
			{
				Swap((char*)base + j * width, ((char*)base + (j + 1) * width),width);
				flag = 0;
			}
		}
		if (flag == 1)
		{
			break;
		}
	}
}

void test()
{
	int arr[] = { 1,2,3,4,5,6,7,8,9,10 };
	int sz = sizeof(arr) / sizeof(arr[0]);
	bubble_sort(arr, sz, sizeof(arr[0]), cmp_int);
	int i = 0;
	for (i = 0; i < sz; i++)
	{
		printf("%d ", arr[i]);
	}
}
void test1()
{
	struct Stu s[] = { {"zhangsan",1} ,{"lisi",2},{"wangwu",3} };
	int sz = sizeof(s) / sizeof(s[0]);
	//qsort(s, sz, sizeof(s[0]), cmp_stu_by_name);
	bubble_sort(s, sz, sizeof(s[0]), cmp_stu_by_age);
	for (int i = 0; i < sz; i++)
	{
		printf("%d\n", s[i].age);
	}
}

int main()
{
	//test();
	test1();
}

简单测试运行:

image-20220602210421315

好了,关于其他排序的话这里就先不展开了,就先到这里结束了!


🚀结语

好了,通过本篇博客我们刚开始看了两个有趣的代码,以及模拟实现简单计算器,认识到了什么是函数指针数组,以及使用的例子,还略微提及了指向函数指针数组的指针,以及最后的回调函数,以及后续通过回调函数而展开冒泡排序与qsort函数的实现!如果觉得还不错的话,多多支持哦!这次博客也到此结束了!>🌹

评论 63
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

平凡的人1

佬的鼓励是我创作的动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值