C语言--指针进阶2

news2024/7/6 19:59:46

目录

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

前言

本篇文章我们将继续学习指针进阶的有关内容

函数指针

我们依然用类比的方法1来理解函数指针这一全新的概念,如图1
图1

我们用一段代码来验证一下:

int Add(int x, int y)
{
	return x+y;
}

int main()
{
	printf("%p\n", &Add);
	printf("%p\n", Add);
	return 0;
}

打印结果如图2
图2
进一步验证了函数指针确实是存放函数的地址。
值得注意的是,函数名和取地址函数名的结果是一样的,这有别于数组名和取地址数组名

那么如果我们想用一个指针变量来存放函数的地址该怎么书写呢?
同样是类比数组指针的写法,如下:

int (*pf)(int,int) = Add;

这里的pf就是函数指针,在书写的时候只用交代类型即可(int char float等),不需要把形参也写进去

如果我们想通过函数指针调用这个函数怎么书写呢?
如下代码:

int Add(int x, int y)
{
	return x+y;
}

int main()
{
	int (*pf)(int, int) = Add;
    printf("%d\n", (*pf)(3, 5));
	printf("%d\n", pf(3, 5));
	printf("%d\n", Add(3, 5));
	return 0;
}

打印结果如图3

图3
所以以上三种形式的书写均可实现函数的调用。

来看两段有趣的代码
先来看第一个:

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

对于这样复杂的代码,我们来逐步地分析:
1,将0强制类型转换为void (*)() 类型的函数指针。
2,这就意味着0地址处放着一个函数,函数没有参数,返回类型是void。
3,调用0地址处对这个函数。

我们再来看第二个:

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

我们同样来逐步分析:(注意这里的signal并没有和结合)
1,上述的代码是一个函数的声明。
2,函数的名字是signal。
3,函数的参数第一个是int,第二个是void(
)(int)类型的函数指针。
4,该函数指针指向的函数参数是int,返回类型是void。

5,signal函数的返回类型也是一个函数指针。
6,该函数指针指向的函数参数是int,返回类型是void。
这样讲可能还是不好理解,我们再对代码进行一下简化:

typedef int* ptr_t;
typedef void(*pf_t)(int);//将void(*)(int)类型重新起个别名pf_t
int main()
{
      void(* signal(int,void(*)(int)))(int);
      pf_t signal(int,pf_t);
      return 0;
}

函数指针数组

同样是类比数组指针,比如整型数组指针就是存放整形指针的数组,那么函数指针数组就是存放函数指针的数组
我们来看下面这段代码:

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[4])(int, int) = { Add,Sub,Mul,Div };
	int i = 0;
	for (i = 0; i < 4; i++)
	{
		int ret = pf[i](8, 4);
		printf("%d\n", ret);
	}
	return 0;
}

打印结果如图4
图4
那么函数指针数组有什么作用呢?
我们可以通过函数指针数组来实现一个简单的计算器:

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[])(int, int) = { NULL,Add,Sub,Mul,Div };
	do
	{
		menu();
		printf("请选择: >");
		scanf_s("%d", &input);
		if (input == 0)
		{
			printf("退出计算器\n");
			break;
		}
		if (input >= 1 && input <= 4)
		{
			printf("请输入两个操作数:>");
			scanf_s("%d %d", &x, &y);
			ret = pfArr[input](x, y);
			printf("结果为%d\n", ret);
		}
		
	} while (input);
	return 0;
}

运行效果如图5
图5
这样我们就通过灵活使用函数指针数组,巧妙的简化了代码,防止冗长。

指向函数指针数组的指针

指向函数指针数组的指针的书写方式如下

int Add(int x, int y)
{
	return x + y;
}

int Sub(int x, int y)
{
	return x - y;
}
int main()
{
	int (*pf)(int, int) = Add;
	int (*pfArr[4])(int, int) = { Add,Sub };
	int (*(*ppfArr)[4])(int, int) = &pfArr;//ppfArr是一个指向函数指针数组的指针变量
	return 0;
}

我们分步来理解这个式子
1,ppfArr是一个指针变量。
2,该指针变量指向的是一个数组,有四个元素。
3,该数组的每个元素类型是int (
)(int,int),是一个函数指针。

回调函数

我们先来看概念:
回调函数就是一个通过函数指针调用的函数。如果你把函数的指针(地址)作为参数传递给另一个函数,当这个指针被用来调用其所指向的函数时,我们就说这是回调函数。回调函数不是由该函数的实现方直接调用,而是在特定的事件或条件发生时由另外的一方调用的,用于对该事件或条件进行响应
那么回调函数具体怎么使用呢?看下面这段代码

int main()
{
	int input = 0;
	int x, y;
	int ret = 0;
	scanf_s("%d", &input);
	switch (input)
	{
	case 1:
		printf("请输入两个操作数:");
		scanf_s("%d %d", &x, &y);
		ret = Add(x, y);
		printf("%d\n", ret);
	case 2://Sub
	case 3://Mul
	case 4://Div
	}
	return 0;
}

我们会发现,case等于不同的数时,总会执行重复的语句。我们能不能这样思考:假设我们把这些重复的语句封装成一个函数,然后把不同运算的函数地址转过去调用呢?

我们定义一个Calc函数:

void Calc(int(*pf)(int, int))
{
	int x = 0;
	int y = 0;
	int ret = 0;
	printf("请输入两个操作数:");
	scanf_s("%d %d", &x, &y);
	ret = pf(x, y);
	printf("%d\n", ret);
	
}

这样我们就实现了在特定的事件或条件发生时由另外的一方调用的,用于对该事件或条件进行响应的这样一个效果(即case等于不同的值是执行不同的响应)。

以上就是本章全部内容,下一章我们将运用回调函数的特性来模拟实现库函数–qsort(快速排序)。

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

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

相关文章

idea报错idea start filed

今天遇到idea启动失败的问题 问题分析&#xff1a; address already in use&#xff1a;bind idea需要的端口被占用 解决 重启就行&#xff0c;重启会重新分配端口。 官方解决 查看给的网站地址&#xff0c;这里官方给出的原因&#xff08;访问好慢&#xff0c;搭梯子我才…

图节点嵌入相关算法学习笔记

引言 本篇笔记为coggle 2月打卡任务&#xff0c;正好也在学习cs224w&#xff0c;干脆就一起做了&#xff0c;以下是任务列表&#xff1a; 任务名称难度任务1&#xff1a;图属性与图构造低、1任务2&#xff1a;图查询与遍历低、2任务3&#xff1a;节点中心性与应用中、2任务4&…

Spark计算框架入门笔记

Spark是一个用于大规模数据处理的统一计算引擎 注意&#xff1a;Spark不仅仅可以做类似于MapReduce的离线数据计算&#xff0c;还可以做实时数据计算&#xff0c;并且它还可以实现类似于Hive的SQL计算&#xff0c;等等&#xff0c;所以说它是一个统一的计算引擎 既然说到了Spar…

js 拖动--动态改变div的宽高大小

index.html 如下&#xff1a;&#xff08;可以新建一个index.html文件直接复制&#xff0c;打开运行&#xff09; <!DOCTYPE html> <html lang"en"> <head> <meta charset"UTF-8"> <meta http-equiv"X-UA-Compatible&qu…

Python tkinter -- 第18章 画布控件之窗口

18.2.22 create_window(position, **options) 可以在画布控件中放置其他tkinter控件。放置的方法就是使用窗口组件。一个窗口组件只能容纳一个控件。如果要放置多个控件&#xff0c;可以把这些控件作为Frame控件的子控件&#xff0c;将Frame控件放入窗口组件中&#xff0c;就可…

超简单 华为OD机试用Python实现 -【踢石头子,踢石子问题】(2023-Q1 新题)

华为OD机试题 华为OD机试300题大纲踢石头子,踢石子问题题目输入输出示例一输入输出Python 代码如下所示算法思路华为OD机试300题大纲 参加华为od机试,一定要注意不要完全背诵代码,需要理解之后模仿写出,通过率才会高。 华为 OD 清单查看地址:blog.csdn.net/hihell/categ…

ChatGPT似乎有的时候并不能搞懂Java的动态分派,你懂了吗?

目录 碎碎念 ChatGPT 中出现的问题 那么正确答案应该是什么呢&#xff1f; 分派的相关知识点总结&#xff1a; 分派是什么&#xff1f; 静态分派与动态分派&#xff1a; Java语言是静态多分派&#xff0c;动态单分派的&#xff1b; 静态分派&#xff1a;静态重载多分派…

追梦之旅【数据结构篇】——详解C语言实现二叉树

详解C语言实现二叉树~&#x1f60e;前言&#x1f64c;什么是二叉树&#xff1f;二叉树的性质总结&#xff1a;整体实现内容分析&#x1f49e;1.头文件的编写&#xff1a;&#x1f64c;2.功能文件的编写&#xff1a;&#x1f64c;1&#xff09;前序遍历的数值来创建树——递归函…

IGKBoard(imx6ull)-Input设备编程之按键控制

文章目录1- input子系统介绍2- input事件目录&#xff08;1&#xff09;struct input_event 结构体&#xff08;2&#xff09;type&#xff08;事件类型&#xff09;&#xff1a;&#xff08;3&#xff09;code&#xff08;事件编码&#xff09;&#xff08;4&#xff09;value…

【华为OD机试模拟题】用 C++ 实现 - 九宫格按键输入(2023.Q1)

最近更新的博客 【华为OD机试模拟题】用 C++ 实现 - 去重求和(2023.Q1) 文章目录 最近更新的博客使用说明九宫格按键输入题目输入输出示例一输入输出说明示例二输入输出说明Code使用说明 参加华为od机试,一定要注意不要完全背诵代码,需要理解之后模仿写出,通过率才会高…

webp格式转换成png怎么转

相对于png 图片&#xff0c;webp比png小了45%&#xff0c;但是缺点是你压缩的时候需要的时间更久了&#xff1b;优点是体积小巧&#xff1b;缺点是兼容性不太好, 只有opera,和chrome支持&#xff0c;不仅如此在后期的编辑修改上也很多软件无法打开。所以我们通常要将webp格式转…

9.1 IGMPv1实验

9.4.1 IGMPv1 实验目的 熟悉IGMPv1的应用场景掌握IGMPv1的配置方法实验拓扑 实验拓扑如图9-7所示&#xff1a; 图9-7&#xff1a;IGMPv1 实验步骤 &#xff08;1&#xff09;配置IP地址 MCS1的配置 MCS1的IP地址配置如图9-8所示&#xff1a; 图9-8&#xff1a;MCS1的配置 …

xgboost学习-XGBoost的智慧

文章目录一、选择弱评估器&#xff1a;重要参数booster二、XGB的目标函数&#xff1a;重要参数objective三、求解XGB的目标函数四、参数化决策树 alpha&#xff0c;lambda五、寻找最佳树结构&#xff1a;求解 ω与T六、寻找最佳分枝&#xff1a;结构分数之差七、让树停止生长&a…

redis(10)事务和锁机制

Redis事务定义 Redis 事务是一个单独的隔离操作&#xff1a;事务中的所有命令都会序列化、按顺序地执行。事务在执行的过程中&#xff0c;不会被其他客户端发送来的命令请求所打断。 Redis 事务的主要作用就是串联多个命令防止别的命令插队。 Multi、Exec、discard Redis 事务中…

【数据挖掘实战】——应用系统负载分析与容量预测(ARIMA模型)

项目地址&#xff1a;Datamining_project: 数据挖掘实战项目代码 目录 一、背景和挖掘目标 1、问题背景 2、传统方法的不足 2、原始数据 3、挖掘目标 二、分析方法与过程 1、初步分析 2、总体流程 第一步&#xff1a;数据抽取 第二步&#xff1a;探索分析 第三步&a…

【华为OD机试模拟题】用 C++ 实现 - 内存池(2023.Q1)

最近更新的博客 【华为OD机试模拟题】用 C++ 实现 - 去重求和(2023.Q1) 文章目录 最近更新的博客使用说明内存池题目输入输出示例一输入输出说明Code使用说明 参加华为od机试,一定要注意不要完全背诵代码,需要理解之后模仿写出,通过率才会高。 华为 OD 清单查看地址:…

C++【string类用法详细介绍string类模拟实现解析】

文章目录string 类用法介绍及模拟实现一、string介绍二、string类常用接口1. string类对象的常见构造接口2.string类对象的常见容量接口3.string类对象的常见修改接口4. string类对象的常见访问及遍历接口5.string其他接口1.不常用查找接口2.字符替换3.字符串拼接4.字符串排序5…

纯x86汇编实现的多线程操作系统实践 - 第三章 BSP的守护执行

本章我们将详细讲解BSP剩下的执行代码&#xff0c;它们被安排在bp_32.asm文件中。bp_32.asm主要完成以下功能&#xff1a;系统中断初始化加载字符图形数据到内存区域将AP的启动代码和32位保护模式下的代码分别加载到内存中显示主界面以及系统启动信息向所有AP群发启动命令进入守…

linux 解压.gz文件 报错 gzip:stdin:not in gzip format(已解决)

目录 1、问题&#xff1a; 2、分析原因 3、解决办法 1、问题&#xff1a; 在解压一个以【.gz】&#xff08;注意不是.tar.gz&#xff09;结尾的压缩包时&#xff0c;遇到报错 【gzip&#xff1a;stdin&#xff1a;不是gzip格式】 翻译一下问题&#xff1a;【gzip&#xff1a;st…

纯x86汇编实现的多线程操作系统实践 - 第一章 系统整体结构说明

现代CPU都是多核系统&#xff0c;拥有多个执行内核&#xff08;即计算引擎&#xff09;&#xff0c;可并发执行不同的代码。在CPU众多的执行内核中&#xff0c;有一个为主执行内核&#xff08;BSP&#xff09;&#xff0c;在CPU上电后&#xff0c;该主执行内核会率先启动&#…