深度剖析指针(中)——“C”

news2024/11/25 13:27:47

各位CSDN的uu们你们好呀,今天小雅兰的内容仍旧是深度剖析指针噢,在上一篇博客中,我已经写过了字符指针、数组指针、指针数组、数组传参和指针传参的知识点,那么这篇博客小雅兰会讲解一下函数指针、函数指针数组 、指向函数指针数组的指针的知识点,现在,就让我们进入指针的世界吧


函数指针

函数指针数组

指向函数指针的数组

回调函数


函数指针

仍然是采用我们的类比法!!!

整型指针——指向整型的指针  int *

字符指针——指向字符的指针  char *

数组指针——指向数组的指针  int arr[10];   int (*p)[10]=&arr;

函数指针——指向函数的指针

数组指针中存放的是数组的地址

函数指针中存放的应该是函数的地址

那么,函数有地址吗?

#define _CRT_SECURE_NO_WARNINGS 1
#include<stdio.h>
int Add(int x, int y)
{
	return x + y;
}
//&Add和Add就是一样的,没有区别
int main()
{
	printf("%p\n", Add);
	printf("%p\n", &Add);
	return 0;
}

 

 输出的是两个地址,这两个地址是 test 函数的地址。

 那我们的函数的地址要想保存起来,怎么保存?

下面我们看代码:

#define _CRT_SECURE_NO_WARNINGS 1
#include<stdio.h>
int Add(int x, int y)
{
	return x + y;
}
//&Add和Add就是一样的,没有区别
int main()
{
	printf("%p\n", Add);
	printf("%p\n", &Add);
	//函数的地址要存起来,就得放在函数指针变量中
	//pf就是函数指针
	int (*pf)(int, int) = Add;
	int ret = (*pf)(3, 5);
	int ret = Add(3, 5);
	int ret = pf(3, 5);
	//这三种写法都是可以的
	//pf前面的这颗*就是一个摆设
	return 0;
}

下面,再来看看:

void test()
{
  printf("hehe\n");
}
//下面pfun1和pfun2哪个有能力存放test函数的地址?
void (*pfun1)();
void *pfun2();

首先,能给存储地址,就要求pfun1或者pfun2是指针,那哪个是指针?

答案是:pfun1可以存放。pfun1先和*结合,说明pfun1是指针,指针指向的是一个函数,指向的函数无参数,返回值类型为void。

阅读两段有趣的代码:

源于《C陷阱和缺陷》

(* ( void (*)() ) 0 )();

  1.  将0强制类型转化为void (*)()类型的函数指针
  2.  这就意味着0地址处放着一个函数,函数没参数,返回类型是void
  3.  调用0地址处的这个函数

其实这句代码的意思就是一次函数调用

void (   *signal (  int , void(*)(int) )   )(int);

 signal括号里面只有类型,没有变量名,说明这是一个函数声明

void (*)(int) signal(int, void(*)(int));——可以这样理解

  1. 这句代码是一次函数声明
  2. 函数的名字是signal
  3. signal函数的参数第一个是int类型,第二个是void(*)(int)类型的函数指针
  4. 该函数指针指向的函数参数是int,返回类型是void
  5. signal函数的返回类型也是一个函数指针
  6. 该函数指针指向的函数参数是int,返回类型是void

可以把这句代码简化一下:

typedef void(*pfun_t)(int);

pfun_t signal(int, pfun_t);//将void(*)(int)重新起个别名叫pfun_t

注意:

typedef void (*pf_t2)(int);

//pf_t2是类型名

void (*pf)(int);

//pf是函数指针变量的名字 


 函数指针数组

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

int *arr[10];

//数组的每个元素是int*

那要把函数的地址存到一个数组中,那这个数组就叫函数指针数组,那函数指针的数组如何定义呢?

int (*parr1[10])();

int *parr2[10]();

int (*)() parr3[10];

答案是:parr1

parr1 先和 [] 结合,说明 parr1是数组,数组的内容是什么呢? 是 int (*)() 类型的函数指针。

函数指针数组的使用:

#include <stdio.h>
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 (*pf[4])(int, int) = { Add,Sub,Mul,Div };
	//0 1 2 3
	int i = 0;
	for (i = 0; i < 4; i++)
	{
		int ret = pf[i](8, 4);
		printf("%d\n", ret);
	}
	return 0;
}

 下面,我们来写一个计算器,来完成整数的+ - * /

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

很轻松的,一个简易计算器的功能就实现了,我们来运行一下这个程序

 

 但是这有问题啊!!!我选择了一个8,理应打印选择错误,而不应该打印请输入两个操作数呀!!!也不应该再打印这个ret!!!所以,我们要把程序修改一下!!!

#include <stdio.h>
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;
}
void menu()
{
	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;
	int x = 0;
	int y = 0;
	int ret = 0;
	do
	{
		menu();
		printf("请选择:>\n");
		scanf("%d", &input);
		switch (input)
		{
		case 1:
			printf("请输入两个操作数:>\n");
			scanf("%d %d", &x, &y);
			ret = Add(x, y);
			printf("%d\n", ret);
			break;
		case 2:
			printf("请输入两个操作数:>\n");
			scanf("%d %d", &x, &y);
			ret = Sub(x, y);
			printf("%d\n", ret);
			break;
		case 3:
			printf("请输入两个操作数:>\n");
			scanf("%d %d", &x, &y);
			ret = Mul(x, y);
			printf("%d\n", ret);
			break;
		case 4:
			printf("请输入两个操作数:>\n");
			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;
}

这样一修改,功能实现才完全正确,但是,还是有问题,这个代码代码冗余的问题非常严重!!!如果未来要增加一些其他的功能,例如:<<  >>  &   |    &&    ||  那么我们的代码会越写越长,case会·越写越多,这样显然是不太好的!!!

使用函数指针数组的实现:

#include <stdio.h>
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;
}
void menu()
{
	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;
	int x = 0;
	int y = 0;
	int ret = 0;
	int (*pfArr[])(int, int) = { 0,Add,Sub,Mul,Div };
	                       //    0  1   2   3   4
	do
	{
		menu();
		printf("请选择:>\n");
		scanf("%d", &input);
		if (input == 0)
		{
			printf("退出计算器\n");
			break;
		}
		else if (input >= 1 && input <= 4)
		{
			printf("请输入两个操作数:>\n");
			scanf("%d %d", &x, &y);
			ret = pfArr[input](x, y);
			printf("%d\n", ret);
		}
		else
		{
			printf("选择错误\n");
		}
	} while (input);
	return 0;
}

函数指针数组的用途:转移表


指向函数指针数组的指针

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

#include <stdio.h>
int Add(int a, int b)
{
	return a + b;
}
int Sub(int a, int b)
{
	return a - b;
}
int main()
{
	int (*pf)(int, int) = Add;
	//函数指针数组
	int (*pfArr[4])(int, int) = { Add,Sub };
	//ppfArr是一个指向函数指针数组的指针变量
	int (*(*ppfArr)[4])(int, int) = &pfArr;
	return 0;
}

回调函数

回调函数就是一个通过函数指针调用的函数。如果你把函数的指针(地址)作为参数传递给另一个 函数,当这个指针被用来调用其所指向的函数时,我们就说这是回调函数。

回调函数不是由该函数的实现方直接调用,而是在特定的事件或条件发生时由另外的一方调用的,用于对该事件或条件进行响应。

之前我们实现了一个简易版计算器功能,但是那个代码的冗余问题非常严重,后面我们用函数指针数组的方法改造了一下这段代码,那现在,我们换一种新的方式来改造,就是我们的回调函数啦!!!

改造前:

#include <stdio.h>
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;
}
void menu()
{
	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;
	int x = 0;
	int y = 0;
	int ret = 0;
	do
	{
		menu();
		printf("请选择:>\n");
		scanf("%d", &input);
		switch (input)
		{
		case 1:
			printf("请输入两个操作数:>\n");
			scanf("%d %d", &x, &y);
			ret = Add(x, y);
			printf("%d\n", ret);
			break;
		case 2:
			printf("请输入两个操作数:>\n");
			scanf("%d %d", &x, &y);
			ret = Sub(x, y);
			printf("%d\n", ret);
			break;
		case 3:
			printf("请输入两个操作数:>\n");
			scanf("%d %d", &x, &y);
			ret = Mul(x, y);
			printf("%d\n", ret);
			break;
		case 4:
			printf("请输入两个操作数:>\n");
			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;
}

 改造后:

#include <stdio.h>
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;
}
void menu()
{
	printf("#######################################\n");
	printf("#######1.Add      2.Sub################\n");
	printf("#######3.Mul      4.Div################\n");
	printf("#######0.exit          ################\n");
	printf("#######################################\n");
}
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);
}
int main()
{
	int input = 0;
	int x = 0;
	int y = 0;
	int ret = 0;
	do
	{
		menu();
		printf("请选择:>\n");
		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;
}

也就是说:上述代码中的Add、Sub、Mul、Div都是回调函数!!! 

之前用函数指针数组改造的那个代码实质上解决的是case语句过多的问题,而用回调函数改造的此代码实质上解决的是代码冗余的问题。

改造的两份代码解决的是完全不一样的问题,所以谈不上哪个方法更好!!!

其实回调函数还有更多内容,下一篇博客小雅兰带你玩转qsort,今天的内容就先到这里啦


好啦,告辞!!!

 

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

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

相关文章

【Spark分布式内存计算框架——Spark Streaming】8. Direct 方式集成底层原理 集成Kafka 0.10.x

Direct 方式集成底层原理 SparkStreaming集成Kafka采用Direct方式消费数据&#xff0c;如下三个方面优势&#xff1a; 第一、简单的并行度&#xff08;Simplified Parallelism&#xff09; 读取topics的总的分区数目 每批次RDD中分区数目&#xff1b;topic中每个分区数据 被…

布局三八女王节,巧借小红书数据分析工具成功引爆618

对于小红书“她”经济来说&#xff0c;没有比三八节更好的阵地了。伴随三八女王节逐渐临近&#xff0c;各大品牌蓄势待发&#xff0c;这场开春后第一个S级大促活动&#xff0c;看看品牌方们可以做什么&#xff1f; 洞察流量&#xff0c;把握节点营销时机 搜索小红书2023年的三…

学员作品|微博“绿洲”APP产品分析

一产品架构1. 产品功能架构图绿洲的主要功能模块可以拆分为六部分&#xff1a;首页、发现、发布动态、个人中心、水滴、消息。整体功能架构图如下&#xff1a;2. 用户使用路径图用于浏览动态&#xff1a;用于发布动态&#xff1a;新用户引导路径&#xff1a;二市场分析1. 产品定…

【Linux】Linux中gcc/g++的使用

本期主题&#xff1a;程序的编译过程和gcc/g的使用博客主页&#xff1a;小峰同学分享小编的在Linux中学习到的知识和遇到的问题小编的能力有限&#xff0c;出现错误希望大家不吝赐&#x1f341; 1.背景知识 预处理&#xff08;进行宏替换&#xff0c;去注释&#xff0c;头文件的…

索引、索引失效、索引的存储

文章目录1.索引种类2.创建索引注意点3.索引失效的情况4.为什么索引使用B树存储&#xff08;1&#xff09;如果使用数组来存储索引&#xff08;2&#xff09;如果使用二叉查找树来存储索引&#xff08;3&#xff09;如果使用平衡二叉查找树来存储索引&#xff08;4&#xff09;如…

同为(TOWE)防雷产品助力福建移动南平分公司防雷改造

01 公司简介中国移动通信集团福建有限公司南平分公司属于福建移动地级分公司&#xff0c;所属行业为电信、广播电视和卫星传输服务。现已建成覆盖范围广、业务品种多、通信质量高的综合通信网络&#xff0c;具备行业领先的经营管理制度。移动通信大楼的综合防雷及地接系统&…

第八节 构造器和this关键字、封装

构造器的作用 定义在类中的&#xff0c;可以用于初始化一个类的对象&#xff0c;并返回对象的地址。 构造器的注意事项 1.任何类定义出来&#xff0c;默认就自带了无参数构造器&#xff0c;写不写都有。 2.一旦定义了有参数构造器&#xff0c;那么无参数构造器就没有了&#xf…

互联网人看一看,这些神器你用过哪些?

很多小伙伴在剪辑视频的过程中经常可以看到一些语音素材&#xff0c;经常刷视频的小伙伴也可以看到很多视频中经常出现一些AI合成的声音或者音效&#xff0c;这些配音可以给视频增添很多亮点&#xff01;那么大家都是怎么将文字转语音的呢&#xff1f;今天给大家分享5款非常专业…

【Git】Git常用命令及练习—Git环境配置

目录 一、Git环境配置 1.1基本配置 1.2为常用指令配置别名&#xff08;可选&#xff09; 1.3 解决GitBash乱码问题 二、Git命令详细操作 1.获取本地仓库 2.基础操作命令及练习 基础操作练习 3.分支命令及练习 开发中分支使用原则与流程 分支练习 &#x1f49f; 创作不…

Ubuntu20.04安装Cuckoo

参考链接&#xff1a; &#xff08;1&#xff09;【主要参考】http://www.wityx.com/post/134851_1_1.html &#xff08;2&#xff09;【主要参考】在Ubuntu18.04上搭建Cuckoo Sandbox2.0.7 https://www.jianshu.com/p/4dd6373fa206 &#xff08;3&#xff09;与Cuckoo的斗智斗…

Kafka入门(五)

下面聊聊Kafka常用命令 1、Topic管理命令 以topic&#xff1a;test_1为例 1.1、创建topic ./bin/kafka-topics.sh --create --bootstrap-server localhost:9092 --replication-factor 3 --partitions 3 --topic test_1参数说明&#xff1a; –bootstrap-server&#xff1a;…

【论文笔记】Decoupling Representation and Classifier for Long-Tailed Recognition

这一篇其实并不是提出什么新的东西&#xff0c;而且是做了点类似综述的技术调用实验。省流&#xff1a;T-normalization最好用 摘要 现状&#xff1a;Existing solutions usually involve class-balancing strategies, e.g. by loss re-weighting, data re-sampling, or tran…

【操作方法】windows防火墙添加出入站规则方法

【操作方法】windows防火墙添加出入站规则方法说明一、入站规则1.打开防火墙&#xff0c;点击“高级设置”2.点击“入站规则”后点击“新建规则”3.例3.1选择“端口”3.2添加需要放通的端口3.3选择操作动作为“允许连接”3.4选择应用区域&#xff0c;此处我选择所有区域二、出站…

Linux: malloc()的指向指针发生指向偏移后,释放前需要将指针指向复原。

Linux: malloc()的指向指针发生指向偏移后&#xff0c;释放前需要将指针指向复原。 #include <stdlib.h> #include <stdio.h> #include <unistd.h> #include <sys/types.h> #include <sys/stat.h> #include <string.h> #include <time…

A Contextual-Bandit Approach to Personalized News Article Recommendation-论文学习

A Contextual-Bandit Approach to Personalized News Article Recommendation-论文学习 github地址&#xff1a;bandit-learning 摘要 该算法根据用户和文章的上下文信息依次选择文章为用户服务&#xff0c;同时根据用户点击反馈调整其文章选择策略&#xff0c;以最大化用户…

不是,到底有多少种图片懒加载方式?

一、也是我最开始了解到的 js方法&#xff0c;利用滚动事件&#xff0c;判断当时的图片位置是否在可视框内&#xff0c;然后进行渲染。 弊端&#xff1a;代码冗杂&#xff0c;你还要去监听页面的滚动事件&#xff0c;这本身就是一个不建议监听的事件&#xff0c;即便是我们做了…

Qt音视频开发18-不同视频打开无缝切换

一、前言 在轮询视频的时候&#xff0c;通常都是需要将之前的视频全部关闭&#xff0c;然后打开下一组视频&#xff0c;在这个切换的过程中&#xff0c;如果是按照常规的做法&#xff0c;比如先关闭再打开新的视频&#xff0c;肯定会出现空白黑屏之类的过度空白区间&#xff0…

【解决办法】windows防火墙出入站规则放通telnet方法

【操作方法】windows防火墙出站规则放通telnet方法一、出站规则1.新建出站规则中选择“程序”2.选择路径&#xff0c;点击“下一页”3.选择“允许连接”4.选择所有区域二、入站规则注&#xff1a;打开防火墙添加出入站规则参考【操作方法】windows防火墙添加出入站规则方法 一、…

Learining C++ No.12【vector】

引言&#xff1a; 北京时间&#xff1a;2023/2/27/11:42&#xff0c;高数考试还在进行中&#xff0c;我充分意识到了学校的不高级&#xff0c;因为题目真的没什么意思&#xff0c;虽然挺平易近人&#xff0c;但是……&#xff0c;考试期间时间比较放松&#xff0c;所以不能耽误…

通过python技术获取甲流分布数据

近期&#xff0c;多地学校出现因甲流导致的班级停课&#xff0c;儿科甲流患者就诊量呈数倍增长。此轮甲流为何如此严重&#xff1f;感染甲流之后会出现哪些症状&#xff1f; 经过专家的介绍甲流之所以这么严重有这些原因导致的。一、疫情完全放开后很多孩子不戴口罩了&#x…