那些 C语言指针 你不知道的小秘密 (完结篇)

news2025/1/20 4:53:17

本篇会加入个人的所谓‘鱼式疯言’
❤️❤️❤️鱼式疯言:❤️❤️❤️此疯言非彼疯言
而是理解过并总结出来通俗易懂的大白话,
我会尽可能的在每个概念后插入鱼式疯言,帮助大家理解的.
🤭🤭🤭可能说的不是那么严谨.但小编初心是能让更多人能接受我们这个概念 !!!
在这里插入图片描述

前言

在上一篇指针小秘密的文章中,我们主要理解了一下几点

  • 字符指针 :分别带友友们了解了 单字符 和 字符串 的所对应的字符指针以及使用细节
  • 数组指针 : 介绍了数组指针的概念 和 以及 数组指针 常用场合
  • 二维数组传参的本质 : 理解了二维数组本质的 内核

今天小编将带着大家给我们C语言最重要的指针内容画上一个完美的句号,这次主要的指针主题是咱们的函数指针,下面让我们最后享受我们魔术般的指针秘密吧 💕 💕 💕

目录

  1. 函数指针
  2. 函数指针数组
  3. 函数指针数组的实际运用:转移表
  4. 函数指针的实际运用:回调函数

一. 函数指针

这时就有小伙伴问了,什么 😲 😲 😲
函数居然还有指针 ! ! !

是的,我们函数也有自己专门的地址,而且可以通过指针变量来存储的自身函数的地址的

<1>. 函数指针的简介

C语言中的函数指针是指向函数的指针 变量 。它可以用来存储函数的地址,以便在程序中 调用 该函数。

函数指针的声明格式如下:

  return_type (*pointer_name)(parameter_list);
  • return_type是函数的返回类型
  • pointer_name是函数指针的名称
  • parameter_list是函数的参数列表。

<2>. 举个栗子

小编在上面提过一个东西
那就是我们的函数是否有自己的地址呢,下面让我们来做个测试吧 💖 💖 💖

#include <stdio.h>
void test()
{
	printf("hehe\n");
}

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

在这里插入图片描述
从中我们可以发现,函数是有地址的,而且聪明的小爱同学已经偷偷告诉我了

他发现 函数名&函数名 地址居然是一样,并提出了函数名就是函数地址的大胆猜想呢 ! ! !

这个猜想到底对不对呢,我们还有待考证,不妨带着问题我们继续研究吧 😁 😁 😁

<3>. 函数指针的声明

void test()
{
	printf("hehe\n");
}
void (*pf1)() = &test;
void (*pf2)() = test;
int Add(int x, int y)
{
	return x + y;
}
int(*pf3)(int, int) = Add;
int(*pf3)(int x, int y) = &Add;//x和y写上或者省略都是可以的

宝子们都看到了我们函数声明是这样子的,可他有哪些结构呢,是不是还是很模糊呢,下面请看

鱼式疯言

有图有真相
在这里插入图片描述

<4>. 函数指针变量的实际运用

通过函数指针调用指针指向的函数。

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

在这里插入图片描述

从 pf 和 (*pf) 得出的结果是一致的,这下和小爱同学彻底验证了,我们的函数名就代表地址

鱼式疯言

  1. 函数名相当于数组名一样也表示 地址
  1. int (*) (int ,int)是 函数指针 类型
  1. int (*p)(int ,int ) 是函数指针 变量

二. 函数指针数组

蛙趣,友友们是不是听到这个名字是不是很惊讶呢,那么高级的名字啊,又是函数,又是指针,还是数组的三者结合版啊 !! !

<1>. 函数指针数组的简介

C语言中的函数指针数组是由一组函数指针组成的数组。可以通过函数指针数组来实现函数的动态调用。

函数指针数组的定义方式如下:
// 声明一个函数指针类型
typedef void (*FuncPtr)();
// 声明一个函数指针数组
FuncPtr funcPtrArray[10];

鱼式疯言

函数指针数组本质上是存放地址的 数组,与 函数指针 类似。

<2>. 举个栗子

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


int sub(int x, int y)
{
	return x - y;
}

int main()
{
	//声明一个函数指针数组
	int (*p[2])(int x, int y) = { add,sub };
	
	//用 for 循环逐个调用
	for (int i = 0; i < 2; i++)
	{
		printf("%d ", p[i](5,4));
	}

	return 0;
}

在这里插入图片描述
我们可以利用 函数指针数组 来分别调用不同的函数

鱼式疯言

  1. 函数指针数组本质上是 数组
  1. 竟然是 数组,类型必须一致,包括我们的函数的 参数 以及 返回类型 也是如此

三. 函数指针数组的实际运用:转移表

友友们是否做过简易的 计算器(加减乘除),我想小爱同学一定会这样写

<1>. 一般计算器

//简易计算器普通版
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 meau()
{
	printf("******* 1.加法 2.减法 *******\n");
	printf("******* 3.乘法 4.除法 *******\n");
	printf("*******  0 . 退  出   *******\n");
}
int main()
{
	int output = 0;
	do
	{
		int a = 0, b = 0;
		meau();
		printf("请选择你要进行的运算:");
		scanf("%d", &output);

		int t = 0;
		switch (output)
		{
		case 1:
			printf("请输入两个操作数:");
			scanf("%d%d", &a, &b);
			 t=add(a, b);
			 printf("%d\n", t);
			 break;
		case 2:
			printf("请输入两个操作数:");
			scanf("%d%d", &a, &b);
			t = sub(a, b);
			printf("%d\n", t);
			break;
		case 3:
			printf("请输入两个操作数:");
			scanf("%d%d", &a, &b);
			t = mul(a, b);
			printf("%d\n", t);
			break;	
		case 4:
			printf("请输入两个操作数:");
			scanf("%d%d", &a, &b);
			t = div(a, b);
			printf("%d\n", t);
			break;
		case 0:

			printf("计算器正在退出中...\n", t);
			break;
		default:
			printf("选择错误,请重新选择!\n");
			break;
		}
	} 
	while (output);
	return 0;
}

在这里插入图片描述
这样写固然没错,但我们有没有更好的调用方法呢

<2>. 转移表

#include<stdio.h>

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 output = 0;
	int (*p[5])(int x, int y) = { NULL,add,sub,mul,div };

	do
	{
		int a = 0, b = 0;
		meau();
		printf("请选择你要进行的运算:");
		scanf("%d", &output);

		int t = 0;
		if (output > 0 && output < 5)
		{
			printf("请输入两个操作数:");
			scanf("%d%d", &a, &b);
			printf("%d\n", p[output](a, b));
		}

	
	} while (output);
	return 0;
}

我们由数组转移到函数的调用的这个过程就叫转移表

所以,我们完美的利用了函数指针数组然后利用其下标引用,可以随时调用我们哪一组函数

鱼式疯言

当我们需要调用同类函数时, 函数指针数组 是个不错的选择哦

四. 函数指针的实际运用:回调函数

啥是回调函数呢,友友们先带着这个疑问来探究我们的今天要学的 回调函数 哦 ! ! !

<1>. 回调函数的简介

在C语言中,回调函数 是指一个函数作为参数传递给另一个函数,并且在后者执行过程中被调用的函数。

<2>. 举个栗子

就拿我们上面这个栗子说明吧,我们的普通版本的计算器是不是还可以利用 函数指针 来优化

//回调函数

#include<stdio.h>

//利用函数指针传参
void calc(int (*pcalc)(int x, int y))
{
	int m = 0, n = 0;
	scanf("%d%d", &m, &n);
	int fault = pcalc(m, n);
	printf("%d\n", fault);
	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;
}


void meau()
{
	printf("******* 1.加法 2.减法 *******\n");
	printf("******* 3.乘法 4.除法 *******\n");
	printf("*******  0 . 退  出   *******\n");
}

int main()
{
	int intput=0;
	
	do
	{
		meau();
		scanf("%d", &intput);
		switch (intput)
		{
			
		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 (intput);


	return 0;
}

在这里插入图片描述
小伙伴有没有发现,我们的传递函数时,是用什么来接收的呢,答案应该很明朗了吧,就是我们本篇文章的主角:函数指针

函数指针的出现让我们能够不断重新调用我们重复类型的函数

鱼式疯言

函数 作为 实际参数 时,我们就可以用 函数指针 来作为 形式参数 来接收

函数指针虽好,可不要贪杯哦,他好像我们函数指针数组一样,是要保持 类型统一性

函数指针的优势:可以不用想普通的计算器一样反复调用同样多行语句,从而减少我们代码过多的 冗长

比如
在这里插入图片描述

在这里插入图片描述

总结

  • 函数指针: 理解函数指针的如何声明,为回调函数做好铺垫
  • 函数指针数组:熟悉了函数指针的具体的场景同时也为转移表做好了铺垫
  • 函数指针数组的实际运用:转移表 ——> 真正去贴合实际去理解我们函数指针数组一般的运用场景
  • 函数指针的实际运用:回调函数 ——> 让我们真正理解了当函数作为参数的实际运用

如果觉得小编写的还不错的咱可支持 三连 下 (定有回访哦) , 不妥当的咱请评论区 指正

希望我的文章能给各位宝子们带来哪怕一点点的收获就是 小编创作 的最大 动力 💖 💖 💖

在这里插入图片描述

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

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

相关文章

蓝桥杯嵌入式第11届真题(完成) STM32G431

蓝桥杯嵌入式第11届真题(完成) STM32G431 题目 代码 程序和之前的大同小异&#xff0c;不过多解释 main.c /* USER CODE BEGIN Header */ /********************************************************************************* file : main.c* brief :…

第4讲 小程序首页实现

首页 create.vue <template><view class"vote_type"><view class"vote_tip_wrap"><text class"type_tip">请选择投票类型</text><!-- <text class"share">&#xe739;分享给朋友</text&g…

免费分享一套PyQt6学生信息管理系统 Python管理系统 Python源码,挺漂亮的

大家好&#xff0c;我是java1234_小锋老师&#xff0c;看到一个不错的PyQt6学生信息管理系统 Python管理系统 Python源码&#xff0c;分享下哈。 项目视频演示 【免费】PyQt5 学生信息管理系统 Python管理系统 Python源码 Python毕业设计_哔哩哔哩_bilibili【免费】PyQt5 学生…

康熙字典的部首里为啥没有王字旁

很多汉字的偏旁部首&#xff0c;是“王”&#xff0c;但在康熙字典的部首列表里&#xff0c;却没有它。而新华字典是有的。 这不科学啊&#xff0c;于是我还通过Unicode编码查了下康熙部首的编码部分&#xff0c;确认了一下&#xff0c;发面里面确实没有。 康熙部首&#xff08…

顺序表、链表(ArrayList、LinkedList)

目录 前言&#xff1a; 顺序表&#xff08;ArrayList&#xff09;&#xff1a; 顺序表的原理&#xff1a; ArrayList源码&#xff1a; 的含义&#xff1a;​编辑 ArrayList的相关方法&#xff1a;​编辑 向上转型List&#xff1a; 练习题&#xff08;杨辉三角&#x…

单页404源码

<!doctype html> <html> <head> <meta charset"utf-8"> <title>简约 404错误页</title><link rel"shortcut icon" href"./favicon.png"><style> import url("https://fonts.googleapis.co…

Java中抽象类和接口的区别

抽象类和接口都是 Java 中多态的常见使用方式. 都需要重点掌握. 同时又要认清两者的区别(重要!!! 常见面试题)。 核心区别: 抽象类中可以包含普通方法和普通字段, 这样的普通方法和字段可以被子类直接使用(不必重写而重写抽象方法), 而接口中不能包含普通方法&#xff08;接口…

生成式人工智能(AIGC)之最全详解图解

生成式人工智能&#xff08;AIGC&#xff09;之最全详解图解 1. AIGC的发展历程1.1 AIGC演化重要时间节点AIGC发展历程图OpenAI大语言模型发展进程 1.2技术推进路线 2.AIGC技术场景2.1 技术场景 3.1AIGC相关应用4.AIGC未来发展前景 1. AIGC的发展历程 AIGC&#xff08;AI-Gene…

ClickHouse--04--数据库引擎、Log 系列表引擎、 Special 系列表引擎

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 文章目录 1.数据库引擎1.1 Ordinary 默认数据库引擎1.2 MySQL 数据库引擎MySQL 引擎语法字段类型的映射 2.ClickHouse 表引擎3.Log 系列表引擎几种 Log 表引擎的共性是&#…

任务调度

1.学习目标 1.1 定时任务概述 1.2 jdk实现任务调度 1.3 SpringTask实现任务调度 1.4 Spring-Task 分析 1.5 Cron表达式 https://cron.qqe2.com/ 2. Quartz 基本应用 2.1 Quartz 基本介绍 2.2 Quartz API介绍 2.3 入门案例 <dependency> <groupId>org.springframe…

《Java 简易速速上手小册》第7章:Java 网络编程(2024 最新版)

文章目录 7.1 网络基础和 Java 中的网络 - 揭开神秘的面纱7.1.1 基础知识7.1.2 重点案例&#xff1a;实现一个简单的聊天程序7.1.3 拓展案例 1&#xff1a;使用 UDP 进行消息广播7.1.4 拓展案例 2&#xff1a;建立一个简单的 Web 服务器 7.2 创建客户端和服务器 - 构建沟通的桥…

Microsoft Word 清除格式

Microsoft Word 清除格式 References 选择文本&#xff0c;用快捷键 Ctrl Shift N&#xff0c;可以快速清除格式。 选择文本&#xff0c;清除格式。 References [1] Yongqiang Cheng, https://yongqiang.blog.csdn.net/

Java图形化界面编程—— ImageIO 笔记

2.8.4 ImageIO的使用 在实际生活中&#xff0c;很多软件都支持打开本地磁盘已经存在的图片&#xff0c;然后进行编辑&#xff0c;编辑完毕后&#xff0c;再重新保存到本地磁盘。如果使用AWT要完成这样的功能&#xff0c;那么需要使用到ImageIO这个类&#xff0c;可以操作本地磁…

【数据结构】二叉树先序、中序、后序及层次四种遍历(C语言版)

数据结构——二叉树先序、中序、后序三种遍历 一、图示展示&#xff1a; &#xff08;1&#xff09;先序遍历&#xff08;2&#xff09;中序遍历&#xff08;3&#xff09;后序遍历&#xff08;4&#xff09;层次遍历&#xff08;5&#xff09;口诀 二、代码展示&#xff1a; 一…

使用JNDI API

使用JNDI API Java EE应用程序使用JNDI API作为通用的名称查找服务来查找组件、外部资源和组件环境属性。JNDI API公开底层命名协议和实现的详细信息。 1. JNDI API的架构 JNDI架构由以下组件组成: JNDI API:它可让Java应用程序访问各种命名服务和目录服务。JNDI服务提供商接…

‘vue-cli-service‘ 不是内部或外部命令,也不是可运行的程序

遇到 vue-cli-service 不是内部或外部命令&#xff0c;也不是可运行的程序或批处理文件。 的错误时&#xff0c;通常意味着Vue CLI没有被正确安装或配置在项目中。这可能是因为node_modules目录缺失了必要的包&#xff0c;或者局部安装的Vue CLI没有被正确设置到系统的PATH环境…

理解JAVA命名和目录接口(JNDI)

理解JAVA命名和目录接口(JNDI) 考虑访问网站的场景,Web用户要求记住四字节的IP地址而不是有意义的名称。例如,假设Web用户用123.23.3.123而不是hotmail.com访问hotmail网站。在这种情形下,Web用户难以记住不同的IP地址来访问不同的网站。因此,要使其变得对Web用户简单方…

九、java 继承

文章目录 java 继承3.1 根父类Object3.2 方法重写3.3 继承案例&#xff1a;图形类继承体系3.4 继承的细节3.4.1 构造方法3.4.2 重名与静态绑定3.4.3 重载和重写3.4.4 父子类型转换3.4.5 继承访问权限protected3.4.6 可见性重写3.4.7 防止继承final 3.5 继承是把双刃剑3.5.1 继承…

lime-echart 一个基于 JavaScript 的开源可视化图表库 使echarts图表能跑在uniapp各端中的插件

Lime-echart 是一个基于 JavaScript 的开源可视化图表库&#xff0c;旨在使 ECharts 图表能够在 UniApp 各个端中运行。UniApp 是一个跨平台的应用程序开发框架&#xff0c;允许开发人员使用 Vue.js 开发一次&#xff0c;然后部署到多个平台&#xff0c;包括 iOS、Android、Web…

openGauss学习笔记-218 openGauss性能调优-确定性能调优范围-硬件瓶颈点分析-I/O

文章目录 openGauss学习笔记-218 openGauss性能调优-确定性能调优范围-硬件瓶颈点分析-I/O218.1 查看I/O状况218.2 性能参数分析 openGauss学习笔记-218 openGauss性能调优-确定性能调优范围-硬件瓶颈点分析-I/O 获取openGauss节点的CPU、内存、I/O和网络资源使用情况&#xf…