当前位置: 首页 > news >正文

函数指针与指针函数的区别

目录:
一、函数指针
1 函数类型
2 函数指针(指向函数的指针)
3 函数指针数组
二.函数指针和指针函数比较
1 定义不同
2 写法不同
3.用法不同
三.函数指针做函数参数(回调函数)
1 利用回调函数实现打印任意类型数据
2 提供能够打印任意类型数组函数
3 利用回调函数 提供查找功能
四.练习

一、函数指针

1 函数类型

通过什么来区分两个不同的函数?

一个函数在编译时被分配一个入口地址,这个地址就称为函数的指针,函数名代表函数的入口地址。

函数三要素: 名称、参数、返回值。C语言中的函数有自己特定的类型。

这一点和数组一样,因此我们可以用一个指针变量来存放这个入口地址,然后通过该指针变量调用函数。

注意:通过函数类型定义的变量是不能够直接执行,因为没有函数体。只能通过类型定义一个函数指针指向某一个具体函数,才能调用。举例如下:

typedef int(p)(int, int);void my_func(int a,int b){printf("%d %d\n",a,b);
}void test(){p p1;//p1(10,20); //错误,不能直接调用,只描述了函数类型,但是并没有定义函数体,没有函数体无法调用p* p2 = my_func;p2(10,20); //正确,指向有函数体的函数入口地址
}

2 函数指针(指向函数的指针)

  • 函数指针定义方式(先定义函数类型,根据类型定义指针变量); typedef void(FUNC_TYPE)(int, char);

  • 先定义函数指针类型,根据类型定义指针变量; typedef void( * FUNC_TYPE2)(int, char);

  • 直接定义函数指针变量; void(*pFunc3)(int, char) = func;

void func(int a, char c)
{printf("hello world\n");
}void test01()
{//1、先定义出函数类型,再通过类型定义函数指针typedef void(FUNC_TYPE)(int, char);FUNC_TYPE* pFunc = func;//pFunc(10, 'a');//2、定义出函数指针类型,通过类型定义函数指针变量typedef void(*FUNC_TYPE2)(int, char);FUNC_TYPE2 pFunc2 = func;//pFunc2(20, 'b');//3、直接定义函数指针变量void(*pFunc3)(int, char) = func;pFunc3(30, 'c');
}

个人理解:和多维数组 中提到的数组指针非常类似,定义也是三种形式。这里func是一个地址名称,因此需要用一个地址名称去对等赋值。

  • 第一个方式,定义普通类型,需要将pFunc转换成地址形态去对等,才有后面的写法pFunc(10, 'a');

  • 第二方式,因为类型已经是指针类型,定义出来的本来就是地址名称(即指针变量),因此直接对等;

  • 第三种方式,和第二种相同,直接弄一个指针,省略了再赋值地址名称。

3 函数指针数组

//函数指针的数组
void func1()
{printf("func1 调用了\n");
}void func2()
{printf("func2 调用了\n");
}void func3()
{printf("func3 调用了\n");
}void test02()
{void(*pArray[3])();pArray[0] = func1;pArray[1] = func2;pArray[2] = func3;for (int i = 0; i < 3; i++){pArray[i]();}
}

二.函数指针和指针函数比较

1 定义不同

  • 指针函数本质是一个函数,其返回值为指针。

  • 函数指针本质是一个指针,其指向一个函数。

2 写法不同

  • 指针函数:int* fun(int x,int y);

  • 函数指针:int (*fun)(int x,int y);

可以简单粗暴的理解为,指针函数的*是属于数据类型的,而函数指针的星号是属于函数名的。

一个很简单的判定方法就是观察(*),记住其中一个即可:

  • int (*p)(int,int);有括号,*与p结合,*p就是个指针,指向返回值为整型且有两个整型参数的函数的指针,因此称为函数指针。

  • int*p(int,int); 没有括号,*与int结合,int*为返回类型,p就是一个函数名,这时就是一个指针函数,只不过返回值类型为int*。

3 用法区别

1)指针函数

指针函数的写法

int *fun(int x,int y);
int * fun(int x,int y);
int* fun(int x,int y);  //建议这种

这个写法看个人习惯,其实如果*靠近返回值类型的话可能更容易理解其定义。

注意:在调用指针函数时,需要一个同类型的指针来接收其函数的返回值。

也可以将其返回值定义为 void*类型,在调用的时候强制转换返回值为自己想要的类型,不过不建议这么使用,因为强制转换可能会带来风险。

typedef struct _Data {int a;int b;
}Data;//指针函数Data* f(int a, int b) {Data data = {a, b};return &data;
}int test02()
{Data* myData = f(3, 5);printf("函数指针myData = %d, b = %d\n",myData->a, myData->b);
}

2)函数指针

函数指针是需要把一个函数的地址赋值给它,有两种写法:

fun=&Function;
fun=Function;

取&不是必需的,因为一个函数标识符就表示了它的地址,如果是函数调用,还必须包含一个圆括号括起来的参数表。

调用函数指针的方式也有两种:

x=(*fun)();
x=fun();

两种方式均可,其中第二种看上去和普通的函数调用没啥区别,如果可以的话,建议使用第一种,因为可以清楚的指明这是通过指针的方式来调用函数。当然,也要看个人习惯,如果理解其定义,随便怎么用都行啦。

int add(int x, int y) {return x + y;
}
int sub(int x, int y) {return x - y;
}
//函数指针
int test02()
{int (*fun)(int x, int y);//第一种写法fun = add;printf("(*fun)(5, 3) = %d\n", (*fun)(5, 3));//第二种写法fun = &sub;printf("(*fun)(5, 3) = %d,(fun)(5, 3) = %d\n",(*fun)(5, 3), fun(5, 3));}

三.函数指针做函数参数(回调函数)

函数参数除了是普通变量,还可以是函数指针变量。

函数指针变量常见的用途之一是把指针作为参数传递到其他函数,指向函数的指针也可以作为参数,以实现函数地址的传递。

//形参为普通变量
void fun( int x ){}
//形参为函数指针变量
void fun( int(*p)(int a) ){}

1 利用回调函数实现打印任意类型数据


//提供一个打印函数,可以打印任意类型的数据
void printText(void* data, void(*myPrint)(void*))
{myPrint(data);
}void myPrintInt(void* data)
{int* num = data;printf("%d\n", *num);
}void test01()
{int a = 10;printText(&a, myPrintInt);
}
void printText(void* data, void(*myPrint)(void*))
{myPrint(data);
}struct Person
{char name[64];int age;
};void myPrintPerson(void * data)
{struct Person * p = data;printf("姓名: %s 年龄: %d\n", p->name, p->age);
}void test02()
{struct Person p = { "Tom", 18 };printText(&p, myPrintPerson);
}

2 提供能够打印任意类型数组函数

//提供一个函数,实现可以打印任意类型的数组 
void printAllArray(void * pArray , int eleSize, int len , void(*myPrint)(void*) )
{char * p = pArray;for (int i = 0; i < len;i++){//获取数组中每个元素的首地址char * eleAddr = p + eleSize * i;//printf("%d\n", *(int *)eleAddr);//交还给用户做打印操作myPrint(eleAddr);}
}void myPrintInt(void * data)
{int * num = data;printf("%d\n", *num);
}void test01()
{int arr[5] = { 1, 2, 3, 4, 5 };int len = sizeof(arr) / sizeof(int);printAllArray(arr, sizeof(int), len, myPrintInt);
}

3 利用回调函数 提供查找功能

struct Person
{char name[64];int age;
};void myPrintperson(void * data)
{struct Person * p = data;printf("姓名:%s  年龄:%d \n", p->name, p->age);
}//查找数组中的元素是否存在
//参数1  数组首地址   参数2  每个元素的大小  参数3  数组元素个数  参数4 查找数据
int findArrayEle(void * pArray, int eleSize, int len, void * data ,  int(*myCompare)(void* ,void* )  )
{char * p = pArray;for (int i = 0; i < len;i++){//每个元素的首地址char * eleAddr = p + eleSize * i;//if ( 数组中的变量的元素 == 用户传入的元素)if ( myCompare(eleAddr,data)  ){return 1;}}return 0;
}int myComparePerson(void * data1,void * data2)
{struct Person * p1 = data1;struct Person * p2 = data2;return   strcmp(p1->name, p2->name) == 0 && p1->age == p2->age;
}void test02()
{struct Person personArray[] ={{ "aaa", 10 },{ "bbb", 20 },{ "ccc", 30 },{ "ddd", 40 },};int len = sizeof(personArray) / sizeof(struct Person);printAllArray(personArray, sizeof(struct Person), len, myPrintperson);//查找数组中指定的元素是否存在struct Person p = { "ccc", 30 };int ret = findArrayEle(personArray, sizeof(struct Person), len, &p, myComparePerson);if (ret){printf("找到了元素\n");}else{printf("未找到\n");}
}

四.练习

提供一个函数,实现对任意类型的数组进行排序,排序规则利用选择排序,排序的顺序 用户可以自己指定

void selectSort(void* pAddr, int elesize, int len, int(*myCompare)(void*, void*))
{char* temp = malloc(elesize);for (int i = 0; i < len; i++){int minOrMax = i; //定义最小值 或者 最大值 下标for (int j = i + 1; j < len; j++){//定义出 j元素地址char* pJ = (char*)pAddr + elesize * j;char* pMinOrMax = (char*)pAddr + elesize * minOrMax;//if ( pAddr[j] < pAddr[minOrMax])/* 从大到小if ( *num1 > *num2){return 1;}return 0;*/if (myCompare(pJ, pMinOrMax)){minOrMax = j; //更新最小值或者最大值下标}}if (i != minOrMax){//交换i和minOrMax 下标元素char* pI = (char*)pAddr + i * elesize;char* pMinOrMax = (char*)pAddr + minOrMax * elesize;memcpy(temp, pI, elesize);memcpy(pI, pMinOrMax, elesize);memcpy(pMinOrMax, temp, elesize);}}if (temp != NULL){free(temp);temp = NULL;}}int myCompareInt(void* data1, void* data2)
{int* num1 = data1;int* num2 = data2;if (*num1 > *num2){return 1;}return 0;
}void test01()
{int arr[] = { 10, 30, 20, 60, 50, 40 };int len = sizeof(arr) / sizeof(int);selectSort(arr, sizeof(int), len, myCompareInt);for (int i = 0; i < len; i++){printf("%d\n", arr[i]);}
}struct Person
{char name[64];int age;
};int myComparePerson(void* data1, void* data2)
{struct Person* p1 = data1;struct Person* p2 = data2;//if ( p1->age < p2->age)//{//    return  1;//}//return 0;//按照年龄 升序排序return  p1->age < p2->age;
}void test02()
{struct Person pArray[] ={{ "aaa", 10 },{ "bbb", 40 },{ "ccc", 20 },{ "ddd", 30 },};int len = sizeof(pArray) / sizeof(struct Person);selectSort(pArray, sizeof(struct Person), len, myComparePerson);for (int i = 0; i < len; i++){printf("姓名:%s 年龄:%d\n", pArray[i].name, pArray[i].age);}
}

相关文章:

函数指针与指针函数的区别

目录&#xff1a;一、函数指针1 函数类型2 函数指针(指向函数的指针)3 函数指针数组二.函数指针和指针函数比较1 定义不同2 写法不同3.用法不同三.函数指针做函数参数(回调函数)1 利用回调函数实现打印任意类型数据2 提供能够打印任意类型数组函数3 利用回调函数 提供查找功能四…...

死锁的四个必要条件以及如何避免死锁

死锁的四个必要条件以及如何避免死锁 一.什么是死锁&#xff1f;二.死锁的四个必要条件 1.互斥条件&#xff1a;2.请求与保持条件&#xff1a;3.不剥夺条件:4.循环等待条件: 三.如何避免死锁 1.破坏请求保持条件2.破坏不剥夺条件3.破坏循环等待条件 死锁的四个必要条件以及如…...

浏览器多线程到事件循环机制

浏览器与js运行机制 进程与线程 进程 进程是CPU分配资源的最小单位&#xff0c;它是一个可以自己独立运行且拥有自己资源空间的任务程序&#xff1b;包括程序以及程序所使用的内存及系统资源 线程 线程是CPU调度的最小单位&#xff0c;它就是程序中的一个执行流&#xff1…...

Lambda表达式的本质

一直想写一篇文章&#xff0c;来总结lambda表达式&#xff0c;但是之前感觉总结的不是特别到位&#xff0c;现在看了几篇文章和视频后&#xff0c;感觉对lambda表达式有了比较深刻的认识&#xff0c;现在进行记录总结如下&#xff1a; lambda表达式又叫做匿名函数&#xff0c;…...

类的加载过程(生命周期)

类的加载过程(生命周期) 一、装载&#xff1a;通过一个类的全限定名获取定义此类的二进制字节流将这个字节流所代表的静态存储结构转化为方法区的运行时数据结构在内存中生成一个代表这个类的java.lang.Class对象&#xff08;将字节码加载到内存中&#xff09;&#xff0c;作为…...

2023最新谷粒商城笔记之MQ消息队列篇(全文总共13万字,超详细)

MQ消息队列 其实队列JDK中本身就有&#xff0c;不过这种队列也只能单体服务可能会使用&#xff0c;一旦项目使用的分布式架构&#xff0c;那么一定还是需要用到一个消息中间件的。我们引入消息队列的原因就是对我们的页面相应速度再优化&#xff0c;让用户的体验更好&#xff…...

多变量线性回归模型

多变量线性回归模型 模型参数为n1维向量&#xff0c;此时模型公式为 hθ(x)θ0x0θ1x1θ2x2...θnxnh_{\theta}(x)\theta_{0}x_{0}\theta_{1}x_{1}\theta_{2}x_{2}...\theta_{n}x_{n} hθ​(x)θ0​x0​θ1​x1​θ2​x2​...θn​xn​ 可以简化为 hθ(x)θTXh_{\theta}(x)\th…...

php 基于ICMP协议实现一个ping命令

php 基于ICMP协议实现一个ping命令 网络协议是什么ICMP 协议什么是ICMP?ICMP 的主要功能ICMP 在 IPv4 和 IPv6 的封装Wireshark抓包ICMP 请求包分析PHP构建 ICMP 数据包php中的 pack & unpack 函数字节和字符packunpackICMP计算校验和步骤总结网络协议是什么 网络协议&…...

Java基本数据类型

1.概述 佛说&#xff0c;大千世界&#xff0c;无奇不有。在这个世界里&#xff0c;物种的多样性&#xff0c;遍地开花&#xff0c;同样&#xff0c;在Java的世界里&#xff0c;也有着异曲同工之妙&#xff0c;Java秉承面向对象的特性&#xff0c;必然少不了区分对象的类型&…...

English Learning - L2 语音作业打卡 Day2 2023.2.22 周三

English Learning - L2 语音作业打卡 Day2 2023.2.22 周三&#x1f48c; 发音小贴士&#xff1a;&#x1f48c; 当日目标音发音规则/技巧&#xff1a;&#x1f36d; Part 1【热身练习】&#x1f36d; Part2【练习内容】&#x1f36d;【练习感受】&#x1f353;元音[ ɑː ]&…...

45. 跳跃游戏 II

题目&#xff1a; 45. 跳跃游戏 II难度中等1974收藏分享切换为英文接收动态反馈给定一个长度为 n 的 0 索引整数数组 nums。初始位置为 nums[0]。每个元素 nums[i] 表示从索引 i 向前跳转的最大长度。换句话说&#xff0c;如果你在 nums[i] 处&#xff0c;你可以跳转到任意 num…...

应届生Java面试50题线程篇(含解析)

什么是线程&#xff1f; 答&#xff1a;线程是操作系统能够进行运算调度的最小单位&#xff0c;是程序执行流的最小单元。在Java中&#xff0c;可以通过实现Runnable接口或继承Thread类来创建线程。 创建线程的方式有哪些?各自的优缺点是什么&#xff1f; 继承 Thread 类&…...

【数据库】第七章 数据库设计

第七章数据库设计 数据库设计概述 数据库设计的基本步骤 需求分析概念结构设计逻辑结构设计物理结构设计数据库实施数据库运行和维护 需求分析 收集需求&#xff0c;理解需求 收集各个角色的需求 概念数据库设计 建立概念模型 &#xff0c;E-R图/IDEF1x图 消除冲突&…...

Burp Suite 常用模块简介

Burp Suite 常用模块分为 目标站点(target)模块 代理(proxy)模块 攻击(Intruder)模块 重放(Repeater) 模块 Target模块是对站点资源的收集&#xff0c;与站点各资源包发出和相应包的记录 Proxy模块是核心模块&#xff0c;可以拦截数据包发送往浏览器&#xff0c;进行修改后再…...

QML Item和Rectangle详解

1.Item和Rectangle Item类型是Qt Quick中所有可视项的基本类型。 Qt Quick中的所有可视项都继承Item。尽管Item对象没有视觉外观&#xff0c;但它定义了视觉项中常见的所有属性&#xff0c;例如x和y位置、宽度和高度、锚定和键处理支持。 Rectangle继承自Item&#xff0c;多…...

常见前端基础面试题(HTML,CSS,JS)(六)

GET 和 POST 的区别 从 http 协议的角度来说&#xff0c;GET 和 POST 它们都只是请求行中的第一个单词&#xff0c;除了语义不同&#xff0c;其实没有本质的区别。 之所以在实际开发中会产生各种区别&#xff0c;主要是因为浏览器的默认行为造成的。 受浏览器的影响&#xf…...

深度学习 李沐报错

3.6. softmax回归的从零开始实现 — 动手学深度学习 2.0.0 documentation softmax从0开始实现 函数执行需要加main指定 改成这样 if __name__"__main__":print(evaluate_accuracy(net, test_iter)) 不然会这样出错 RuntimeError: An attempt has been m…...

【JAVA程序设计】(C00104)基于Springboot的家庭理财管理系统——有文档

基于Springboot的家庭理财管理系统项目简介项目获取开发环境项目技术运行截图运行视频项目简介 基于Springboot开发的家庭理财管理系统设计与实现共分为三个角色&#xff1a;系统管理员、家庭管理员、家庭用户 管理员角色包含以下功能&#xff1a; 用户管理、修改密码、角色管…...

【第五章 AOP概述,底层原理,AOP术语,切入点表达式,AOP操作(基于注解方式,基于xml配置文件)】

第五章 AOP概述&#xff0c;底层原理&#xff0c;AOP术语&#xff0c;切入点表达式&#xff0c;AOP操作&#xff08;基于注解方式&#xff0c;基于xml配置文件&#xff09; 1.AOP概述&#xff1a; &#xff08;1&#xff09;什么是AOP&#xff1a; ①面向切面编程&#xff08;…...

面试官: 你知道 JWT、JWE、JWS 、JWK嘛?

想起了 之前做过的 很多 登录授权 的项目 它相比原先的session、cookie来说&#xff0c;更快更安全&#xff0c;跨域也不再是问题&#xff0c;更关键的是更加优雅 &#xff0c;所以今天总结了一篇文章来介绍他 JWT 指JSON Web Token&#xff0c;如果在项目中通过 jjwt 来支持 J…...

业务系统对接大模型的基础方案:架构设计与关键步骤

业务系统对接大模型&#xff1a;架构设计与关键步骤 在当今数字化转型的浪潮中&#xff0c;大语言模型&#xff08;LLM&#xff09;已成为企业提升业务效率和创新能力的关键技术之一。将大模型集成到业务系统中&#xff0c;不仅可以优化用户体验&#xff0c;还能为业务决策提供…...

黑马Mybatis

Mybatis 表现层&#xff1a;页面展示 业务层&#xff1a;逻辑处理 持久层&#xff1a;持久数据化保存 在这里插入图片描述 Mybatis快速入门 ![在这里插入图片描述](https://i-blog.csdnimg.cn/direct/6501c2109c4442118ceb6014725e48e4.png //logback.xml <?xml ver…...

DockerHub与私有镜像仓库在容器化中的应用与管理

哈喽&#xff0c;大家好&#xff0c;我是左手python&#xff01; Docker Hub的应用与管理 Docker Hub的基本概念与使用方法 Docker Hub是Docker官方提供的一个公共镜像仓库&#xff0c;用户可以在其中找到各种操作系统、软件和应用的镜像。开发者可以通过Docker Hub轻松获取所…...

Day131 | 灵神 | 回溯算法 | 子集型 子集

Day131 | 灵神 | 回溯算法 | 子集型 子集 78.子集 78. 子集 - 力扣&#xff08;LeetCode&#xff09; 思路&#xff1a; 笔者写过很多次这道题了&#xff0c;不想写题解了&#xff0c;大家看灵神讲解吧 回溯算法套路①子集型回溯【基础算法精讲 14】_哔哩哔哩_bilibili 完…...

Cilium动手实验室: 精通之旅---20.Isovalent Enterprise for Cilium: Zero Trust Visibility

Cilium动手实验室: 精通之旅---20.Isovalent Enterprise for Cilium: Zero Trust Visibility 1. 实验室环境1.1 实验室环境1.2 小测试 2. The Endor System2.1 部署应用2.2 检查现有策略 3. Cilium 策略实体3.1 创建 allow-all 网络策略3.2 在 Hubble CLI 中验证网络策略源3.3 …...

Auto-Coder使用GPT-4o完成:在用TabPFN这个模型构建一个预测未来3天涨跌的分类任务

通过akshare库&#xff0c;获取股票数据&#xff0c;并生成TabPFN这个模型 可以识别、处理的格式&#xff0c;写一个完整的预处理示例&#xff0c;并构建一个预测未来 3 天股价涨跌的分类任务 用TabPFN这个模型构建一个预测未来 3 天股价涨跌的分类任务&#xff0c;进行预测并输…...

渲染学进阶内容——模型

最近在写模组的时候发现渲染器里面离不开模型的定义,在渲染的第二篇文章中简单的讲解了一下关于模型部分的内容,其实不管是方块还是方块实体,都离不开模型的内容 🧱 一、CubeListBuilder 功能解析 CubeListBuilder 是 Minecraft Java 版模型系统的核心构建器,用于动态创…...

Mac软件卸载指南,简单易懂!

刚和Adobe分手&#xff0c;它却总在Library里给你写"回忆录"&#xff1f;卸载的Final Cut Pro像电子幽灵般阴魂不散&#xff1f;总是会有残留文件&#xff0c;别慌&#xff01;这份Mac软件卸载指南&#xff0c;将用最硬核的方式教你"数字分手术"&#xff0…...

实现弹窗随键盘上移居中

实现弹窗随键盘上移的核心思路 在Android中&#xff0c;可以通过监听键盘的显示和隐藏事件&#xff0c;动态调整弹窗的位置。关键点在于获取键盘高度&#xff0c;并计算剩余屏幕空间以重新定位弹窗。 // 在Activity或Fragment中设置键盘监听 val rootView findViewById<V…...

Maven 概述、安装、配置、仓库、私服详解

目录 1、Maven 概述 1.1 Maven 的定义 1.2 Maven 解决的问题 1.3 Maven 的核心特性与优势 2、Maven 安装 2.1 下载 Maven 2.2 安装配置 Maven 2.3 测试安装 2.4 修改 Maven 本地仓库的默认路径 3、Maven 配置 3.1 配置本地仓库 3.2 配置 JDK 3.3 IDEA 配置本地 Ma…...