网络知识 娱乐 【C语言进阶】指针的进阶【中篇】

【C语言进阶】指针的进阶【中篇】

  💘作者:你我皆为凡人

 💘博客主页:你我皆为凡人的博客

 💘名言警句:时间不会为任何人停留,而事物与人,无时不刻也在变化着。每一个人,也都在不停向前!

 💘觉得博主文章写的不错的话,希望大家三连(✌关注,✌点赞,✌评论),多多支持一下!!

 💘系列作品:

 💘

 💘C语言编程刷题篇

 💘经典题型系列

 

 

文章目录

目录

文章目录

🙈 前言

💫函数指针

💫函数指针数组

💫指向函数指针数组的指针

💫回调函数

💞习题练习入口

 🙈 总结


🙈 前言

本篇既上篇后续又讲解了新的知识,关于函数指针,函数指针数组,指向函数指针数组的指针与回调函数这些一看就比较令人头疼的知识点,一些比较高的知识点,希望大家可以理解并且学会


提示:以下是本篇文章正文内容,下面案例可供参考

💫函数指针

之前的数组指针是指向数组的指针就是数组指针

函数指针就是指向函数的指针就是函数指针

那么我们知道 数组指针是取数组的地址,那么函数莫非就是取出函数的地址?

那么下面我们来探索函数指针:

#include
int add(int x, int y)
{
	return x + y;
}
int main()
{
	int arr[5] = { 0 };
	//&数组名--取出的数组的地址
	int(*p)[5] = &arr; //数组指针
	//&函数名-取出的就是函数的地址嘛?
	printf("%pn", &add);
	//函数名,是函数的首元素地址嘛,函数怎么有首元素呢?
	printf("%pn", add);
	//对于函数来说,&函数名和函数名都是函数的地址
	//如果要取出函数的地址,该如何存放呢?
	int (*pf)(int, int) = &add;
	return 0;
}

 首先要注意,函数指针其实和数组指针大同小异,&函数名和函数名都是函数的地址,如果要取出函数的地址,首先要说明是个指针 (*pf)函数内参数类型是(int,int)返回值是int,这就构成了函数指针,指向的是一个函数

我们使用指针是为了有朝一日可以用到它,那么函数指针是如何使用的呢

int (*pf)(int, int) = &add;
	int ret = (*pf)(2, 3);
	printf("%d", ret);
	return 0;

 其实(*pf)里面的*在这里没有意义,大家想一想,既然add可以是地址,&add也是地址,那么把add直接赋值给pf也可以,我们平常调用函数是 add(2,3),那么直接pf(2,3)也是相同的道理

那么函数指针如何使用呢?

int add(int x, int y)
{
	return x + y;
}
void calc(int(*pf)(int, int))
{
	int a = 3;
	int b = 5;
	int ret = pf(a, b);
	printf("%dn", ret);
}
int main()
{
	calc(add);
	return 0;
}

 这样看起来好像麻烦了很多 ,直接调用函数就可以了,为什么这样使用呢?别着急,下面会根据别的知识点讲解一个例子,让大家明白函数指针的应用场景

接下来让大家看两段有趣且看着很发麻的代码:

int main()
{
	 (*(void (*)())0)();
	void (*signal(int, void(*)(int)))(int);
	return 0;
}

 关于这两段代码的画图解析:

 

 这两段代码是采用c陷阱和缺陷,读起来是相当的困难,如果没有学习这一点知识,那么这种代码是肯定不知道该如何下手的

接下来我们来实行一个函数指针的用途 ,方便大家理解函数指针

我们来进行一个简单的计算器,普通版本下的是这样的:

//函数指针的用途
//写一个计算器
//加法,减法,乘法,除法

void  memu()
{
	printf("****************************n");
	printf("**    1 add      2 sub *****n");
	printf("***   3 mul      4 div *****n");
	printf("****      0 exit      ******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
	{
		memu();
		printf("请选择:");
		scanf("%d", &input);
		switch (input)
		{
		case 1:
			printf("请输入两个实操数字:");
			scanf("%d %d", &x, &y);
			ret = add(x, y);
			printf("%dn", ret);
			break;
		case 2:
			printf("请输入两个实操数字:");
			scanf("%d %d", &x, &y);
			ret = sub(x, y);
			printf("%dn", ret);
			break;
		case 3:
			printf("请输入两个实操数字:");
			scanf("%d %d", &x, &y);
			ret = mul(x, y);
			printf("%dn", ret);
			break;
		case 4:
			printf("请输入两个实操数字:");
			scanf("%d %d", &x, &y);
			ret = div(x, y);
			printf("%dn", ret);
			break;
		case 0:
			printf("退出计算器n");
			break;
		default:
			printf("选择错误n");
			break;
		}
	} while (input);
	return 0;
}

 我们发现case1,2,3,4之间有着太多的代码重复了,我们可不可以直接封装一个函数,但是怎么来实现呢?

我们如下进行改进:(部分代码,因为都写上太多了)

我们可以看到下面,使用了函数指针,来把那些重复的代码都放了进去,每个加减乘除函数名字都是地址,那么使用函数指针来接收,在内部调用这个函数,这件事回调函数,那么在不知道函数指针的概念的时候是不支持这样的

所以函数指针并不是摆设,而是暂时没有用得到的地方,其实是跟高明的一种方法

//函数指针的用途
//写一个计算器
//加法,减法,乘法,除法
//封装的一个计算函数
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("%dn", ret);
}
int main()
{
	int input = 0;
	do
	{
		memu();
		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;
}

💫函数指针数组

数组是一个存放相同类型数据的存储空间,我们已经学习了多个数组,比如:

int* arr【5】

char* arr【6】

int  arr【4】

那么函数指针是指针,把函数和指针放到数组中,其实就是函数指针的数组

下面的代码来讲解函数指针数组:

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 (*pf)(int, int) = add;//pf是函数指针
	//仿造
	int (*arr[4])(int, int) = { add,sub,mul,div };
}

 那么我们如何来理解呢?画图理解:

 那么如何使用呢?我们可以用遍历的方法:

int main()
{
	int (*pf)(int, int) = add;//pf是函数指针
	//仿造
	int (*arr[4])(int, int) = { add,sub,mul,div };
	int i = 0;
	for (i = 0; i < 4; i++)
	{
		int ret = arr[i](8, 4);
		printf("%dn", ret);
	}
}

 看到这里大家可能有一些疑惑,这有什么用呢?不要着急,下面来看一组代码运用,还是上面的那个计算器,我们修改一下:

//函数指针的用途
//写一个计算器
//加法,减法,乘法,除法
//转移表
void  memu()
{
	printf("****************************n");
	printf("**    1 add      2 sub *****n");
	printf("***   3 mul      4 div *****n");
	printf("****      0 exit      ******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 };
	do
	{
		memu();
		printf("请选择:");
		scanf("%d", &input);
		pfarr[input];//根据输入的数字获取下标为几的元素
		if (input == 0)
		{
			printf("退出计算器n");
			
		}
		else if (input >= 1 && input <= 4)
		{
			printf("请输入两个实操数字:");
			scanf("%d %d", &x, &y);
			ret = pfarr[input](x, y);
			printf("%dn", ret);
		}
		else
		{
			printf("选择错误n");
		}
	} while (input);
	return 0;
}

 在上面的代码中,我们使用了函数指针的数组,使得代码变的非常简洁,即使我们的计算器想深入一些功能,增加一些功能,也变得很简单

💫指向函数指针数组的指针

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

指针指向一个数组,数组的元素都是函数指针

如何定义呢?如下方所示:(了解即可,在这里并不会深入)

#include
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;
}
#include
int main()
{
	//函数指针数组
	int (*pfarr[])(int, int) = { 0,add,sub,mul,div };
	//指向函数指针数组的指针
	int (*(*ppfarr)[])(int,int) = &pfarr;
	return 0;
}

💫回调函数

回调函数就是一个通过函数指针调用的函数。如果你把函数的指针(地址)作为参数传递给另一个函数,当这个指针被用来调用其所指向的函数时,我们就说这是回调函数。回调函数不是由该函数的实现方直接调用,而是在特定的事件或条件发生时由另外的一方调用的,用于对该事件或条件进行响应。
我知道看起来似乎难以理解,不用担心,我们用一个例子来解释说明
首先我们在以前接触过冒泡排序,知道冒泡排序只能运行在整形中,只能对于整形排序,我们下面根据冒泡排序与库函数qsort来展开说明:
初始版(只能排序整型数据):
#include
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  arr[j + 1])
			{
				int tmp = arr[j];
				arr[j] = arr[j + 1];
				arr[j + 1] = tmp;
				flag = 0;
			}
		}
		if (flag == 1)
		{
			break;
		}
	}
}
int main()
{
	int arr[] = { 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实现排序整数)(相当于回调函数)

//比较两个整型元素
//e1指向另外一个整数
//e2指向另外一个整数
#include
#include
int cmp_int(const void* e1, const void* e2)//void*是无具体类型的指针,可以接受任意类型的地址
//所以不能解引用操作,也不能+-整数
{
	return (*(int*)e1) - (*(int*)e2);
}
void test1()
{
	int arr[] = { 9,8,7,6,5,4,3,2,1,0 };
	//把数组生成升序
	int sz = sizeof(arr) / sizeof(arr[0]);
	qsort(arr, sz, sizeof(arr[0]), cmp_int);
	int i = 0;
	for (i = 0; i name, ((struct stu*)e2)->name);
}
void test2()
{
	//测试使用qsort来排序结构数据
	struct stu s[] = { {"zhangsan",15},{"lisi",59},{"liwa",44} };
	int sz = sizeof(s) / sizeof(s[0]);
	qsort(s, sz, sizeof(s[0]), cmp_stu);
	//调试观看内存
}
int main()
{
	test1();
	test2();
	return 0;
}

接下来改造第一个冒泡排序(模拟qsort函数)

#include
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++;
	}
}
int cmp_int(const void* e1, const void* e2)
{
	return (*(int*)e1) - (*(int*)e2);
}
void bubblu_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;
		int flag = 1;
		for (j = 0; j  0)
			{
				//交换
				swap((char*)base + j * width, (char*)base + (j + 1) * width,width);
				flag = 0;
			}
		}
		if (flag == 1)
		{
			break;
		}
	}
}
int main()
{
	int arr[] = { 9,8,7,6,5,4,3,2,1,0 };
	//把数组生成升序
	int sz = sizeof(arr) / sizeof(arr[0]);
	bubblu_sort(arr, sz, sizeof(arr[0]), cmp_int);
	int i = 0;
	for (i = 0; i < sz; i++)
	{
		printf("%d ", arr[i]);
	}
	return 0;
}

💞习题练习入口

看完这些不具体操作操作那么是不可以的,可以点击上方直达去练习一些有关习题,也可以随便看一看C语言的一些习题,练习练习选择题和编程题,让自己的知识得到巩固,直接点入标题就可直达,另外想获得大厂内推资格也可以去看看:

大厂内推

很推荐大家去练习练习这里面的习题,对新手很友好,题目都是从最简单的方面,可以选择简单入门中等困难等一系列的难度,本人练习好久,让我们一起进行刷题之旅吧:

牛客网站练习直达

 

 

 🙈 总结


本篇既上篇后续又讲解了新的知识,关于函数指针,函数指针数组,指向函数指针数组的指针与回调函数这些一看就比较令人头疼的知识点,一些比较高的知识点分别举了不同的例子让大家可以知道这些知识的运用,还讲解了关于转移表是什么与库函数qsort的模拟实现,一步一步的提升,如果觉得博主讲解的还不错的话,希望大家多多支持,感谢大家观看