C语言:指针详解【进阶】后篇

news2024/12/25 9:05:10

目录

  • 函数指针
  • 函数指针数组
  • 指向函数指针数组的指针
  • 回调函数

前言:

在C语言:指针详解【进阶】前篇中我们深入学习了字符指针数组指针指针数组以及数组传参和指针传参。我们对指针的应用有了较为深刻的认识,今天这里我们将更加深入的进行对更复杂的指针的探究。

函数指针

在前面我们知道一个指针变量可以指向一块内存的地址,我们也知道函数在使用时也要向内存申请一块内存空间,那我们不妨想一想我们能不能创建一个指针变量来指向一个函数呢?
我们先来看看函数的地址到底是什么?

#include <stdio.h>
void test()
{
	printf("hehe\n");
}
int main()
{
	test();
	printf("%p\n", &test);
	printf("%p\n", test);
	return 0;
}

在这里插入图片描述
这里我们分别打印&testtest的值,结果两者相同,那我们可以认为&testtest都代表的是函数test的地址,这里是两者没有区别的。

当我们知道函数的地址后,我们就可以创建一个指针来指向这个函数了,但问题是函数的形式有很多种,我们的函数指针的数据类型该如何定义呢?
这里的定义其实与数组指针的定义是类似的,在数组指针的定义时,我们把数组指针的数组名取出来后,剩下的就是数组的数据类型了
那么这里我们把函数的函数名取出来,那剩下的就是函数的数据类型,这样就可以来定义函数指针了。
在这里插入图片描述

函数指针在使用时可以先解引用指针pf,再在后边带上参数就行:

#include <stdio.h>
int Add(int x, int y)
{
	return x + y;
}
int main()
{
	int (*pf)(int , int) = &Add;
	//int (*pf)(int x, int y) = &Add;
	//int (*pf)() = &Add;
	//这三种定义方法都可行
	
	int ret1 = (*pf)(3, 5);
	printf("%d\n", ret1);
	return 0;
}

在这里插入图片描述

我们前面得知&testtest都代表的是函数test的地址,这里是两者没有区别的。那么这里我们的函数指针的使用就还有另一种方式,不用解引用pf,直接使用:

#include <stdio.h>
int Add(int x, int y)
{
	return x + y;
}
int main()
{
	int (*pf)(int , int) = &Add;
	int ret1 = (*pf)(3, 5);
	printf("%d\n", ret1);
	int ret2 = pf(3, 5);
	printf("%d\n", ret2);
	return 0;
}

在这里插入图片描述
其实这里的语句 int ret1 = (*pf)(3, 5);中的*就是一个摆设,没有实质性的用途,这里的*只是为了帮助我们来理解这个语句的。

所以这里我们可以任意的增加和删减*, 都是不影响结果的。
但是注意:如果你要使用*,那就必须把 *pf()括起来。

#include <stdio.h>
int Add(int x, int y)
{
	return x + y;
}
int main()
{
	int (*pf)(int , int) = &Add;
	int ret1 = (*****pf)(3, 5);
	printf("%d\n", ret1);
	return 0;
}

在这里插入图片描述
当我们学会函数指针后,你可能会疑惑,函数指针的使用场景是什么?我们为何不直接调用函数来使用呢?
其实函数指针本质是一个指针,我们指针的使用场景就是将数据传递到另一个函数中去使用,那当我们需要传递一个函数作为参数到另一个函数中时,就需要用到函数指针了
这里关于函数指针的使用就放在回调函数的板块讲解了。
这里我们分析两个有趣的的函数指针:

int main()
{
	//代码1
	(*(void(*)())0)();
	//代码2
	void (*signal(int, void(*)(int)))(int);
	return 0;
}

我们将代码1进行分解一下就好理解了:
在这里插入图片描述
代码1其实就是一次函数调用。


我们再对代码2进行分解:
在这里插入图片描述
代码2其实就是一次函数声明。
其实对于代码2,可以进行优化一下,使我们能更好的读懂代码;
这里我们要使用一个自定义关键词typedef(重命名)来操作。
我们将这个函数的返回值void(*)(int)进行重命名为ptr_t来进行简化代码。

//typedef void (*)(int) ptr_t; //错误写法
typedef void (*ptr_t)(int);
int main()
{
	ptr_t signal(int, void(*)(int));
	return 0;
}

注意:

在对返回值是函数指针的类型重命名时,新名字要放在函数指针的内部,不能放在后边,这样才符合语法。


函数指针数组

前面我们学习了指针数组,该数组内部可以放置相同类型的指针,我们今天又学习了函数指针,那我们是否可以创建一个函数指针数组来存放函数指针呢?答案是可以的。
函数指针数组的定义与指针数组的定义是一样的,都要有:数组元素类型,数组名,数组元素个数

void test1()
{}
void test2()
{}
void test3()
{}
void test4()
{}
int main()
{
	void (*pf1)() = &test1;
	void (*pf2)() = &test2;
	void (*pf3)() = &test3;
	void (*pf4)() = &test4;
	void *parr[4]() = { pf1,pf2,pf3,pf4 };
	void (*)() parr[4] = { pf1,pf2,pf3,pf4 };
	void (*parr[4])() = { pf1,pf2,pf3,pf4 };
	//这三种函数指针数组的定义写法,哪种是正确的?
	return 0;
}

这里和函数指针的声明类似,变量名要紧挨着*,只是这里变量名先和[]结合,作为一个数组。
所以这里的第三种定义是正确的。

void (*parr[4])() = { pf1,pf2,pf3,pf4 };

关于函数指针数组的用途:
我们知道函数指针数组中存放的是统一类型的函数指针,那么我们对于某一类数据进行相似操作时,就可以使用到函数指针数组来方便的调用一些函数了,同时有助于减少相似代码的书写,简化程序。
这里就举例实现一个简易计算器来说明函数指针数组的用途:

这里是函数的编写及头文件的声明:

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

版本二(使用函数指针数组):

int main()
{
	int input = 0;
	int x = 0;
	int y = 0;
	int (*parr[5])(int, int) = { NULL, Add, Sub, Mul, Div };
	do
	{
		menu();
		printf("请选择:> ");
		scanf("%d", &input);
		if(0 < input && input < 5)
		{
			printf("请输入操作数:> ");
			scanf("%d %d", &x, &y);
			printf("%d\n", parr[input](x, y));
		}
		else if (input == 0)
		{
			printf("退出计算器\n");
			break;
		}
		else
		{
			printf("输入错误,请重新输入!\n");
		}
	} while (input);
	return 0;
}

两者的代码一比较就可以发现,使用函数指针数组后,代码中的重复代码大大下降,代码整体简洁清晰了不少。

这里就体现了函数指针数组的用途:转移表


指向函数指针数组的指针

我们刚刚学习了函数指针数组,我们是否可以用一个指针来指向这个数组?那这个指向函数指针数组的指针又该如何定义呢?

#include <stdio.h>
void test(const char* str)
{
	printf("%s\n", str);
}
int main()
{
	 //函数指针pfun
	 void (*pfun)(const char*) = test;//&test 和 test 是一样的
	 //函数指针的数组pfunArr
	 void (*pfunArr[5])(const char* str);
	 pfunArr[0] = test;
	 return 0;
}

这里的定义其实与指针的定义是类似的,在数组指针的定义时,我们把数组指针的数组名取出来后,剩下的就是数组的数据类型了
那么这里我们把函数指针数组的函数名取出来,那剩下的就是函数指针数组的数据类型,这样就可以来定义函数指针数组的指针了。
在这里插入图片描述

#include <stdio.h>
void test(const char* str)
{
	printf("%s\n", str);
}
int main()
{
	 //函数指针pfun
	 void (*pfun)(const char*) = test;//&test 和 test 是一样的
	 //函数指针的数组pfunArr
	 void (*pfunArr[5])(const char* str);
	 pfunArr[0] = test;
	 //指向函数指针数组pfunArr的指针ppfunArr
	 void (*(*ppfunArr)[5])(const char*) = &pfunArr;
	 return 0;
}

回调函数

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

就是上面所说的使用函数指针进行传参的应用。
解释:

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

这里我们来进行分析一个qsort函数(库函数),进行对回调函数的了解。
这里我们需要先去了解一下qsort函数, 明白这个函数是什么用处,又该如何使用?
我们打开cplusplus网站,进行搜索。
在这里插入图片描述
得知qsort函数是一个排序的函数。
注意:它可以排序任意类型的数据。
函数使用:
在使用qsort函数时,我们需要只知道要排序的第一个元素的地址,要排序元素的个数,每个元素的大小,以及一个能比较两个元素的大小的函数。
这里我们唯一要设计的就是编写一个能比较两个元素的大小的函数。
注意:设计的函数要和库里给定的该比较函数模板格式要一致。

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

在这里插入图片描述
这里我们设计的cmp_int函数就是回调函数。


讲到这里指针详解【进阶】后篇的知识讲解就结束了。
关于指针的知识是重点,也是难点,不仅仅是知识的了解,更要进行大量的练习才能巩固知识。
这几天我会出一期关于指针进阶习题的练习和讲解,来进行加深对指针的更进一步的记忆。
同时对于qsort函数,我们不仅仅只是会使用它,还要学会去自己实现一个qsort函数。同样会放在指针进阶习题的练习和讲解文章之后马上更新。


感兴趣的的小伙伴点点赞,点点关注,谢谢大家的阅读哦!!!
点点关注,后期不错过哦。😘
你们的鼓励就是我的动力,欢迎下次继续阅读!!!😘😘😘

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/479530.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

BusterNet网络Python模型实现学习笔记之二

文章目录 一、squeeze函数的用法二、nn.CrossEntropyLoss函数三、isinstance函数四、定义冻结层 freeze_layers五、SummaryWriter 基础用法六、Python 基础语法1.变量嵌入到字符串2. enumerate() 函数3. 进度条库tqdm4. 字典&#xff08;dict&#xff09;展开为关键字参数&…

TAPFixer总结

相关工作 Menshen 检测属性用户写 et al检测属性就简单三个 未来工作&#xff1a; liveness; implicit; 数据集&#xff1b; 抽象方式合并&#xff1b;抽象规则配置&#xff1b;缓解谓词爆炸&#xff1b;concurrency的说明; 代码简化工作&#xff1b;给出能修复的漏洞种类 …

《基于光电容积法和机器学习的冠状动脉疾病患者出血风险预测》阅读笔记

目录 一、论文摘要 二、论文十问 三、论文亮点与不足之处 四、与其他研究的比较 五、实际应用与影响 六、个人思考与启示 参考文献 一、论文摘要 在冠状动脉疾病&#xff08;CAD&#xff09;患者的抗血栓治疗过程中&#xff0c;出血事件是关注的主要焦点。本研究旨在探讨…

浅谈一下布隆过滤器的设计之美

1 缓存穿透 2 原理解析 3 Guava实现 4 Redisson实现 5 实战要点 6 总结 布隆过滤器是一个非常有用的数据结构。它可以在大规模数据中高效地判断某个元素是否存在。布隆过滤器的应用非常广泛&#xff0c;不仅在搜索引擎、防垃圾邮件等领域中经常用到&#xff0c;而且在许多…

R语言单因素方差分析

R中的方差分析 介绍用于比较独立组的不同类型的方差分析&#xff0c;包括&#xff1a; 单因素方差分析&#xff1a;独立样本 t 检验的扩展&#xff0c;用于在存在两个以上组的情况下比较均值。这是方差分析检验的最简单情况&#xff0c;其中数据仅根据一个分组变量&#xff0…

【数据结构】七大排序总结

目录 &#x1f33e;前言 &#x1f33e; 内部排序 &#x1f308;1. 直接插入排序 &#x1f308;2. 希尔排序 &#x1f308;3. 直接选择排序 &#x1f308;4. 堆排序 &#x1f308;5. 归并排序 &#x1f308;6. 冒泡排序 &#x1f308;7. 快速排序 &#x1f33e;外部排序 &…

4 月份 火火火火 的开源项目

盘点 4 月份 GitHub 上 Star 攀升最多的开源项目&#xff0c;整个 4 月份最火项目 90% 都是 AI 项目&#xff08;准确的说&#xff0c;最近半年的热榜都是 AI 项目&#xff09; 本期推荐开源项目目录&#xff1a; 1. AI 生成逼真语音 2. 复旦大模型 MOSS&#xff01; 3. 让画中…

万万没想到在生产环境翻车了,之前以为很熟悉 CountDownLatch

前言 需求背景 具体实现 解决方案 总结 前言 之前我们分享了CountDownLatch的使用。这是一个用来控制并发流程的同步工具&#xff0c;主要作用是为了等待多个线程同时完成任务后&#xff0c;在进行主线程任务。然而&#xff0c;在生产环境中&#xff0c;我们万万没想到会…

【LeetCode】583. 两个字符串的删除操作

583. 两个字符串的删除操作&#xff08;中等&#xff09; 思路 这道题的状态定义和 1143. 最长公共子序列 相同&#xff0c;「定义一个 dp 数组&#xff0c;其中 dp[i]表示到位置 i 为止的子序列性质&#xff0c;并不是必须以 i 结尾」&#xff0c;此时 dp 数组的最后一位即为…

富士康终于醒悟了,重新加码中国制造,印度制造信不过

4月25日富士康在郑州揭牌新事业总部&#xff0c;显示出在扰攘了数年之后&#xff0c;富士康再度加强郑州富士康的发展力度&#xff0c;这应该是富士康在印度努力数年之后终于清醒了&#xff0c;印度制造终究不如中国制造可靠。 一、苹果和富士康在印度发展的教训 这两年苹果和富…

智能算法系列之基于粒子群优化的模拟退火算法

文章目录 前言1. 算法结合思路2. 问题场景2.1 Sphere2.2 Himmelblau2.3 Ackley2.4 函数可视化 3. 算法实现代码仓库&#xff1a;IALib[GitHub] 前言 本篇是智能算法(Python复现)专栏的第四篇文章&#xff0c;主要介绍粒子群优化算法与模拟退火算法的结合&#xff0c;以弥补各自…

【unity项目实战】3DRPG游戏开发07——其他详细的设计

敌人动画设计 新增图层动画,把权重设为1 在新图层默认新建一个空状态Base State,实现怪物默认动画播放Base State,因为Base State是空动画,所以默认会找上一个层的动画,这样就实现了两个图层动画的切换,也可以选择修改权重的方式实现 敌人随机巡逻 显示敌人巡逻的范…

网络字节序和主机字节序详解(附代码)

一、网络字节序和主机字节序 网络字节序和主机字节序是计算机网络中常用的两种数据存储格式。 主机字节序&#xff1a; 指的是在计算机内部存储数据时采用的字节排序方式。对于一个长为4个字节的整数&#xff0c;若采用大端字节序&#xff0c;则该整数在内存中的存储顺序是&a…

AppScan-被动手动扫描

被动扫描是针对性的扫描&#xff0c;浏览器代理到AppScan&#xff0c;然后进行手工操作&#xff0c;探索产生出的流量给AppScan进行扫描。这样可以使得扫描足够精准&#xff0c;覆盖率更加高&#xff0c;还能减少不必要的干扰 &#xff08;一&#xff09;环境准备 1、火狐安装…

SAP UI5 之Controls (控件) 笔记三

文章目录 官网 Walkthrough学习-Controls控件1.0.1 在index.html中使用class id 属性控制页面展示的属性1.0.2 我们在index.js文件中引入 text文本控制1.0.3打开浏览器查看结果 官网 Walkthrough学习-Controls控件 Controls控件 在前面展示在浏览器中的Hello World 是在Html …

Presto 之Hash Join的Partition

一. 前言 在Presto中&#xff0c;当两表Join为Hash Join并且join_distribution_type为PARTITIONED的时候&#xff0c;Presto会将Build表分区&#xff08;Partition&#xff09;后再进行Join操作。在Presto中的Join操作中&#xff0c;对表的分区有两级&#xff0c;第一级是将Has…

超简单搭建一个自用的ChatGPT网站(支持给网站添加访问密码)

前言&#xff1a; 有小伙伴留言想在自己的服务器搭建上图所示的ChatGPT网站&#xff0c;那么今天就是教大家如何在自己的服务器搭建像上图所示的ChatGPT网站 准备条件&#xff1a; 1&#xff09;一台服务器(这里用centos7) 2&#xff09;ChatGPT的API-KEY 一、Docker环境部署…

存储资源调优技术——SmartThin智能精简配置技术

目录 基本概念 工作原理 SmartThin关键技术 SmartThin主要功能 应用场景 精简LUN&#xff0c;存储空间超分配 按需动态分配存储资源&#xff0c;提高存储资源利用率 Thick和Thin LUN的区别如下 基本概念 Thin Lun属于存储资源的虚拟化&#xff0c;因此需要基于RAID 2.0存…

当影像遇上Python:用MoviePy库轻松搞定视频编辑

I. 简介 当影像遇上Python&#xff1a;用MoviePy库轻松搞定视频编辑 I. 简介II. 安装III. 使用 &#x1f680;&#x1f3ac;1. 创建一个视频剪辑对象2. 剪辑视频3. 剪切视频片段4. 改变视频尺寸和速度5. 合并视频6. 合并多个视频7. 用混合模式合并视频8. 添加音频9. 添加背景音…

台北房价预测

目录 1.数据理解1.1分析数据集的基本结构&#xff0c;查询并输出数据的前 10 行和 后 10 行1.2识别并输出所有变量 2.数据清洗2.1输出所有变量折线图2.2缺失值处理2.3异常值处理 3.数据分析3.1寻找相关性3.2划分数据集 4.数据整理4.1数据标准化 5.回归预测分析5.1线性回归&…