C语言之指针进阶篇(2)

news2025/1/21 6:30:39

目录

函数指针

函数名和&函数名 

函数指针的定义

函数指针的使用

函数指针陷阱

代码1

代码2 

注意

函数指针数组定义

函数指针数组的使用 

指向函数指针数组的指针

书写 


终于军训圆满结束了,首先回顾一下指针进阶篇(1)主要是指针&数组的点!

C语言之指针进阶篇(1)_唐棣棣的博客-CSDN博客icon-default.png?t=N7T8https://blog.csdn.net/m0_74841364/article/details/132223126?spm=1001.2014.3001.5502今天我们继续更深入的来了解指针!主要是指针&函数&数组的点!

函数指针

在前面我们已经了解过字符指针,数组指针,这里我们将介绍到函数指针!

指针数组——是数组,是存放指针的数组。

数组指针——是指针,指向数组的指针。

字符指针——指向字符的指针。

整型指针——指向整型的指针。

浮点型的指针——指向浮点型的指针。

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

函数名和&函数名 

类比数组指针。

数组指针——指向数组的指针——存放的是数组的地址——&数组名就是数组的地址

函数指针——指向函数的指针——存放的是函数的地址——函数地址是否是&函数名呢? 

那我们用代码来验证下。 

#include<stdio.h>
int Add(int x, int y)
{
	return x + y;
}
int main()
{
	//int x = 0;
	//int y = 0;
	//scanf("%d %d", x, y);
	printf("%p\n", &Add);
	printf("%p\n", Add);
	//&函数名 就是函数的地址
	//函数名 也是函数的地址
	//注意函数名并没有首元素这一说法
}

 &函数名和函数名,都是函数的地址。

那它们是否有什么区别和它们的类型是一样的吗?

它们没有区别,知识写法不一样。

它们两个类型是一样的。指向同一个函数,类型是相同的。

整形函数指针类型?

函数指针的定义

如果在程序中定义了一个函数,那么在编译时系统就会为这个函数代码分配一段存储空间,这段存储空间的首地址称为这个函数的地址。而且函数名和&函数名均表示的就是这个地址。既然是地址我们就可以定义一个指针变量来存放,这个指针变量就叫作函数指针变量,简称函数指针

 那该怎样去写函数指针呢?


#include<stdio.h>
int Add(int x, int y)
{
	return x + y;
}
int main()
{
	printf("%p\n", &Add);
	printf("%p\n", Add);
	int (*p)(int,int) = &Add;//更容易理解和熟悉
	int (*p)(int x,int y) = &Add;
	int (*p)(int x,int y) = Add;//写不写x y都可
	int (*p)(int,int) = Add;
//&和x y 写不写都可
}
这个语句就定义了一个指向函数的指针变量 p。
首先它是一个指针变量,所以要有一个“*”,即(*p);
其次前面的 int 表示这个指针变量可以指向返回值类型为 int 型的函数;
后面括号中的两个 int 表示这个指针变量可以指向有两个参数且都是 int 型的函数。
所以合起来这个语句的意思就是:
定义了一个指针变量 p,该指针变量可以指向返回值类型为 int 型,且有两个整型参数的函数。
p 的类型为 int(*)(int,int)。

int (*p) (int, int);

函数返回值类型 (* 指针变量名) (函数参数列表);

定义了一个指针变量 p,该指针变量可以指向返回值类型为 int 型,且有两个整型参数的函数。p 的类型为 int(*)(int,int)。

p就是函数指针变量。

 那如果写成int* p(int,int);?

*和int 结合 int* 变成了函数声明了。 

函数指针的使用

函数指针的使用最长应用在转移表和回调函数  ,那我们在接下来的文章都会讲到。

#include<stdio.h>
int Add(int x, int y)
{
	return x + y;
}
int main()
{
	printf("%p\n", &Add);
	printf("%p\n", Add);
	int (*p)(int,int) = &Add;
	int ret=(*p)(3, 5);//解引用找到函数
	int ret = (********p)(3, 5);//*是个摆设,写不写都可
	int ret = Add(3, 5);//直接调用Add函数就是用Add函数的地址
	int ret = p(3, 5);//这里也可以直接使用地址
	printf("%d", ret);
}

我们有以上三种写法去调用函数 Add,但是我们不可以乱写哦!

	int ret=* p(3, 5);

函数指针陷阱

阅读两段有趣的代码!

我们改怎样去理解这两端代码呢?

代码1

(*(void (*)())0)();
//函数调用

图错误待修改  

  • 在调用 0地址处的函数,这个函数没有参数,返回类型是void
  • 把0从int类型强制转化成函数指针类型,指向了0处的地址处的函数

代码2 

void (*signal(int , void(*)(int)))(int);
//函数声明

图错误待修改 

  • 这个代码是一次函数声明,声明的是signal函数
  • signal函数有两个参数,一个是int类型,一个是函数指针类型该类型是void (*)(int)
  • 函数指针类型 该函数指针指向的函数,参数是int, 返回类型是void
  • signal函数的返回类型也是函数指针类型,该类型是void (*) (int)
  • 函数指针类型 该函数指针指向的函数,参数是int, 返回类型是void

代码2太复杂了,那能不能简化代码2???当然可以。

void (* signal(int, void(*)(int)))(int);
//void(*)(int) signal(int void(*)(int));❌
//重定义
typedef void(*pfun_t)(int);
pfun_t signal(int pfun_t);

注 :推荐《C陷阱和缺陷》

注意

  1. 函数指针变量&函数指针&函数指针类型
  2. 函数指针类型 修饰一个 函数指针变量(可以是函数等 / 也可以没有/或者 将其他类型强制转化成函数指针类型) 意味着则该函数指针指向一个 返回类型是,参数是的函数
  3. 函数指针定义
    int(*p)(int,int)=Add;
  4. 函数指针调用
    (*p)(3,5);
    p(3,5);
    (*p)();
    p();//不传参数
  5. 函数指针声明
    void (* signal(int,int))(int);
  6. 函数指针类型和函数指针变量的写法
    int(*p)(int,int);
    void(*p)(int);

函数指针数组定义

char * arr[5]字符指针数组——数组——存放的是字符指针

int * arr[5]整形指针数组——数组——存放的是整形指针

int(*p[5])(int,int)   void(*p[5])(int,char)等等

函数指针数组——数组——存放的是函数指针(函数的地址)

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

int (*parr1[10]])();
int *parr2[10]();
int (*)() parr3[10];

 答案是:parr1 parr1 先和 [] 结合,说明parr1是数组。

               数组的内容是什么呢? 是 int (*)() 类型的函数指针。

#include<stdio.h>
int Add(int x, int y)
{
	return x + y;
}
int Sub(int x, int y)
{
	return x - y;
}
int main()
{
	int(*p1)(int, int) = &Add;
	int(*p1)(int, int) = &Sub;
	//函数指针数组中存放的类型相同的多个元素
	int(*p[])(int, int) = { &Add,&Sub };
	//p是函数指针数组_存放函数指针的数组
	//均是返回类型为int,函数参数是int,int类型
}

函数指针数组的使用 

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

使用条件:函数类型/函数参数类型必须一摸一样

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

能不能让代码变得简单一点?

//简化后
#include<stdio.h>
void meau()
{
	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;
	//用函数指针数组
	//
	do
	{
		meau();
		printf("请选择>\n");
		scanf("%d", &input);
		int(*p[])(int, int) = { NULL,&Add,&Sub,&Mul,&Div };
		                      //0      1    2     3    4
		if (input == 0)
			printf("退出游戏\n");
		else if (input > 0 && input <= 4)
		{
			printf("请输入两个操作数:\n");
			scanf("%d %d", &x, &y);
			ret = (*p[input])(x, y);
			printf("ret=%d\n", ret);
			//	ret = p[input](x, y);//用函数指针数组的下标找到指向的函数
		}
		else//其他
			printf("选择错误,请重新选择!\n");
	} while (input);
	return 0;
}

 

当然出了使用函数指针数组去更高效的使用计算器,下章我们将使用回调函数去高效优化计算器!

指向函数指针数组的指针

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

指向整型指针数组的指针

#include<stdio.h>
int main()
{
	int a = 0;
	int b = 0;
	int c = 0;
	int* arr[] = { &a,&b,&c };//整形指针数组
	int* (*p)[3] = &arr;//p是指针,是指向整形指针数组的指针
	return 0;
}

函数指针数组的指针

#include<stdio.h>
int main()
{
	int(*arr[5])(int, int) = { NULL,&Add,&Sub,&Mul,&Div };
	p = &arr;//存放函数指针数组的指针
	int(*(*p)[5])(int, int) = &arr;
	return 0;
}

书写 

无论是函数指针&函数指针数组&函数指针数组的指针等等,可以一直延申下去!

我们在书写变量是,首先将变量p写出来,在添加其类型。

也可以从简单的函数指针的基础上修改!

✔✔✔✔✔最后,感谢大家的阅读,若有错误和不足,欢迎指正!最近的心情多做事少说话。

代码------→【gitee:https://gitee.com/TSQXG】

联系------→【邮箱:2784139418@qq.com】

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

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

相关文章

C语言实现三字棋

实现以下&#xff1a; 1游戏不退出&#xff0c;继续玩下一把&#xff08;循环&#xff09; 2应用多文件的形式完成 test.c. --测试游戏 game.c -游戏函数的实现 game.h -游戏函数的声明 (2)游戏再走的过程中要进行数据的存储&#xff0c;可以使用3*3的二维数组 char bor…

【送书活动】揭秘分布式文件系统大规模元数据管理机制——以Alluxio文件系统为例

前言 「作者主页」&#xff1a;雪碧有白泡泡 「个人网站」&#xff1a;雪碧的个人网站 「推荐专栏」&#xff1a; ★java一站式服务 ★ ★ React从入门到精通★ ★前端炫酷代码分享 ★ ★ 从0到英雄&#xff0c;vue成神之路★ ★ uniapp-从构建到提升★ ★ 从0到英雄&#xff…

Modelsim仿真问题解疑二:ERROR: [USF-ModelSim-70]

现象&#xff1a;在Vivado中已配置modelsim为仿真工具后&#xff0c;运行仿真&#xff0c;报错USF-ModelSim-70和ERROR: [Vivado 12-4473] 详细报错内容如下 ERROR: [USF-ModelSim-70] compile step failed with error(s) while executing C:/Users/ZYP_PC/Desktop/verilog_t…

多线程与高并发——并发编程(7)

文章目录 七、JUC并发工具1 CountDownLatch应用&源码分析1.1 CountDownLatch介绍1.2 CountDownLatch应用1.3 CountDownLatch源码分析1.3.1 有参构造1.3.2 await 方法1.3.3 countDown方法2 CyclicBarrier应用&源码分析2.1 CyclicBarrier介绍2.2 CyclicBarrier应用2.3 Cy…

python DVWA文件上传POC练习

首先&#xff0c;构造POC我们首先要明白漏洞利用的流程&#xff0c;然后要知道请求包的格式&#xff0c;然后才能针对性的POC 这里先选择低难度的文件上传&#xff0c;低难度的是没有任何过滤可以直接上传的&#xff0c;先上传一个php一句话木马&#xff0c;使用burpsuite抓包 …

go channel实践与源码探索(初始化、发送消息、接收消息、关闭)

文章目录 概要一、并发编程1.1、Actor模型1.2、CSP模型 二、Go Channel实践三、源码分析3.1、初始化3.2、发送消息3.3、接收消息3.4、关闭通道 总结 概要 通道&#xff08;Channel&#xff09;是Go语言提供的协程之间通信与同步的方式。我们知道在并发编程&#xff08;多进程、…

mysql检查表、分析表、优化表

MySQL提供了分析表、检查表和优化表的语句。分析表主要是分析关键字的分布&#xff0c;检查表主要是检查表是否存在错误&#xff0c;优化表主要是消除删除或者更新造成的空间浪费。 文章目录 概述分析表检查表优化表 概述 对数据库的管理常规就是进行预防性的维护&#xff0c;…

SSM - Springboot - MyBatis-Plus 全栈体系(五)

第二章 SpringFramework 四、SpringIoC 实践和应用 2. 基于 XML 配置方式组件管理 2.5 实验五&#xff1a;高级特性&#xff1a;FactoryBean 特性和使用 2.5.1 FactoryBean 简介 FactoryBean 接口是Spring IoC容器实例化逻辑的可插拔性点。 用于配置复杂的Bean对象&#x…

前端list.push,封装多个对象

js var fruit [apple, banana];fruit.push(pear);console.log(fruit); // [apple, banana, pear]现在为对象 data1:{addUser: 1,editUser: 1,addTime: null,editTime: 1527410579000,userId: 3,systemNo: mc,userName: zengzhuo,userPassword: e10adc3949ba59abbe56e057f20f88…

Qt点亮I.MX6U开发板的一个LED

本篇开始将会介绍与开发版相关的Qt项目&#xff0c;首先从点亮一个LED开始。I.MX6U和STM32MP157的相关信息都会用到&#xff0c;但是后期还是将I.MX6U的学习作为重点。当然其他开发版的开发也可以参考本博文。 文章目录 1. Qt是如何操控开发板上的一个LED2. 出厂内核设备树中注…

linux修改最大线程数却未生效的原因

可能是没有重新对新文件进行编译 更改一个进程所能创建的最大进程数之前 更改一个进程所能创建的最大进程数之后 测试代码 #include <iostream> #include <unistd.h> #include <sys/wait.h> #include <string.h> #include <stdio.h> #include…

学生信息系统(python实现)

#codingutf-8 import os.path filenamestudent.txtdef menm():#菜单界面print(学生管理系统)print(-----------------------------功能菜单-----------------------------)print(\t\t\t\t\t\t1.录入学生信息)print(\t\t\t\t\t\t2.查找学生信息)print(\t\t\t\t\t\t3.删除学生信息…

合宙Air724UG LuatOS-Air LVGL API控件-图片(Gif)

图片&#xff08;Gif&#xff09; GIF图片显示&#xff0c;core版本号要>3211 示例代码 方法一 -- 创建GIF图片控件 glvgl.gif_create(lvgl.scr_act()) -- 设置显示的GIF图像 lvgl.gif_set_src(g,"/lua/test.gif") -- gif图片居中 lvgl.obj_align(g, nil, lvgl…

软件测试行业35岁职场魔咒,你准备怎么应对?

以前就流传一种说法“IT行业职场35岁危机”&#xff0c;那时我半信半疑。 或许那时觉得还离我比较遥远&#xff0c;也或许那时每天都重复着996&#xff0c;工作上的任务已经应接不暇&#xff0c;每天都处在忙碌中&#xff0c;也没精力花时间去过多思考这个问题。休息的时候能好…

机器学习实战-系列教程7:SVM分类实战2线性SVM(鸢尾花数据集/软间隔/线性SVM/非线性SVM/scikit-learn框架)项目实战、代码解读

&#x1f308;&#x1f308;&#x1f308;机器学习 实战系列 总目录 本篇文章的代码运行界面均在Pycharm中进行 本篇文章配套的代码资源已经上传 SVM分类实战1之简单SVM分类 SVM分类实战2线性SVM SVM分类实战3非线性SVM 3、不同软间隔C值 3.1 数据标准化的影响 如图左边是没…

GLSL ES着色器 精度限定字

目录 前言 WebGL支持的三种精度 数据类型的默认精度 float类型没有默认精度 预处理指令 在GLSL ES中常用的三种预处理指令。 预定义的内置宏 前言 GLSL ES新引入了精度限定字&#xff0c;目的是帮助着色器程序提高运行效率&#xff0c;削减内存开支。顾名思义&#xf…

Kafka3.0.0版本——消费者(自动提交 offset)

目录 一、自动提交offset的相关参数二、消费者&#xff08;自动提交 offset&#xff09;代码示例 一、自动提交offset的相关参数 官网文档 参数解释 参数描述enable.auto.commi默认值为 true&#xff0c;消费者会自动周期性地向服务器提交偏移量。auto.commit.interval.ms如果…

《TCP/IP网络编程》阅读笔记--I/O复用

1--基于I/O复用的服务器 多进程服务器端具有以下缺点&#xff1a;当有多个客户端发起连接请求时&#xff0c;就会创建多个进程来分别处理客户端的请求&#xff0c;创建多个进程往往需要付出巨大的代价&#xff1b; I/O复用的服务器端可以减少进程数&#xff0c;无论连接多少个客…

Ubuntu22.04_如何调试ROS2_humble的源代码

这里的源码&#xff0c;是指的ros2 humble的官方源码。如果是自己手撸的节点或相关源码&#xff0c;请参考本人以前的贴子&#xff0c; Ubuntu20.04vscode快速调试ROS通用程序_ubuntu20.04vscode那个版本和ros 兼容_高精度计算机视觉的博客-CSDN博客 Ubuntu20.04&#xff0b;…