函数指针和指针函数的讲解

文章目录

  • 指针函数
  • 函数指针
  • 函数指针的定义与指针函数的声明的区别
    • 函数指针的定义
    • 指针函数的声明
  • typedef在函数指针方面的使用
    • typedef和using 给函数指针的类型取别名
    • typedef和using 给函数的类型取别名

指针函数

指针函数:
也叫指针型函数,本质上就是一个函数,是一种特殊的函数类型,它返回一个指针作为函数的返回值。
指针型函数声明的格式:
返回类型* 函数名(参数列表);
或者
返回类型 *函数名(参数列表);
推荐使用第一种声明方式。更清晰明了
其中,返回类型是指针类型,函数名是指针型函数的名称,参数列表是指针函数的参数列表。

例如:
普通函数(即返回值不是地址)

int fun0(int);//声明了一个返回值类型为int,函数参数为int型的函数。

指针函数(即返回值是地址)

int* fun1 (int);//声明了一个返回值为整型的指针,函数参数为int,int的指针型函数。
void* fun2 (int, int);//声明了一个返回值为void型的指针,函数参数为int,int的指针型函数。

注意: 指针函数一定有返回值,形如 void* 的返回值类型,为任意指针类型。而不是无返回值。不管返回的是 int* 还是 void* 建议都判断是否为空指针。

例子:

int* func(int a, int b);//指针型函数声明; 或者为 int* func(int, int);即声明里的形参可以只写类型,不写形参变量。这也是推荐的写法。
	
int* func(int a, int b) //指针型函数的定义
{
   int* p = (int*)malloc(sizeof(int));//在堆内存中申请了一块4个字节的内存,并把指向这块堆内存的地址返回,赋值给指针变量p
   *p = a + b;//将a+b的和,赋值给指针变量p所指向的内存空间。注:当*p作为左值时,表示的就是它所指向的那块内存对应的变量。
   return p;
}

注意:和普通函数一样,指针函数没有形参时,声明时后面的括号()也是不能省略的,否则就变成 指针变量的定义了。

指针函数就是一个普通的函数,普通到仅仅是因为它的函数返回值是地址值而已。这与其他一般函数唯一的区别
就是在函数名前面多了一个*号,而这个函数就是一个指针函数。
一句话总结:返回的是地址值,在指针函数被调用时用指针变量来接收此指针函数返回的地址值。

函数指针

函数指针是指向函数的指针变量,即本质是一个指针变量。因此“函数指针”本身首先应是指针变量。
只不过该指针变量指向函数。这正如用指针变量可指向整型变量、字符型、数组一样,这里是指向函数。C/C++在编译时,每一个函数都有一个入口地址,该入口地址就是函数指针所指向的地址。有了指向函数的指针变量后,可用该指针变量调用函数,就如同用指针变量可引用其他类型变量一样,在这些概念上是大体一致的。函数指针有两个用途:调用函数和做函数的参数。

先看一个int类型的指针的声明,然后和函数型指针做对比:

int* p;

声明一个函数型(假如是一个有两个int类型的参数以及一个 int类型的返回值)的指针:

int (*fun_ptr) (int, int);//定义 一个【有两个int类型的参数以及一个int类型的返回值】类型 的函数指针。fun_ptr 就是指针变量名

故此指针可以用来指向:符合 一个有两个int类型的参数以及一个 int类型的返回值的任意函数。
在这里插入图片描述二者不同的只是 函数指针多了一个参数列表
例子:

#include <iostream>
#include <stdio.h>

void Fun(int a, int b){//函数定义
    std::cout << "a =" << a <<", b=" << b <<std::endl;
};

int Fun1(int a, int b){//函数定义
    std::cout << "a =" << a <<", b=" << b <<std::endl;// a = 2, b = 3
    return 0;
};

int main(int argc, char *argv[])
{

   void (*fun_ptr) (int, int);//定义一个函数指针 

    fun_ptr = Fun;//函数名其实就是函数的地址,函数可以看成和数组名一样都是一个指针常量,即指针的指向地址不可改变
   // fun_ptr = Fun1;//编译报错,函数指针(fun_ptr)的类型 和 Fun1函数的类型不一致。必须要保证返回值和参数列表一样。否则函数指针不能指向此函数地址

    // std::cout << "Fun函数地址:" << Fun <<std::endl;//这样方式打印函数地址会把函数指针转换为bool类型,故打印出来的是总是1,改用下面的printf函数
    fun_ptr(2,3);//使用函数指针来调用函数
    printf("Fun函数的地址为:%p\n",Fun);//Fun函数的地址为:0x400910
    printf("Fun1函数的地址为:%p\n",Fun1);//Fun1函数的地址为:0x400922
    printf("fun_ptr的指向地址:%p\n",fun_ptr);//fun_ptr的指向地址:0x400910
}

函数指针的定义与指针函数的声明的区别

函数指针的定义

void (*fun) (int, int);//fun 是一个有两个int类型的参数以及一个 int类型的返回值的函数指针。//指针就是变量。变量名是fun.

注意:这里提指针和地址的区别,指针是变量,它是用来存储地址 ,而地址是一个常量值。类似于,变量和变量的值之间的关系。

指针函数的声明

void* fun (int, int);//fun是返回一个指针的函数

注意: 定义函数指针时,第一个()是不能省略的,如果省略的话,就会导致完全不同的情况了。就变成 指针函数的声明了。

typedef在函数指针方面的使用

typedef 操作符是在C/C++中经常使用的。它不仅可以给简单类型 取别名。还可以给复杂类型 取别名。
格式如下:
typedef 类型 别名;
在C++11 中又引入了using的另一个作用 来给类型 取别名。using这个作用和typedef的作用是一样的,二者等价,在某些复杂类型上使用有using 比用typedef更容易直观理解。
格式如下:
using 别名 = 类型;
注意:using之前已有的作用是 引入命名空间 如引入标准库的命名空间: using namespace std;

typedef和using 给函数指针的类型取别名

typedef 在给 函数指针类型 取别名上的应用(这也是typedef 在给复杂类型 取别名上的应用):

typedef void (*FUN) (int, int);//这里的FUN是一种 void (*) (int, int) 类型的别名,而不是上面的函数指针,而是一个类型。可以FUN来表示 函数指针 的类型。

注意: 也许这样 写 typedef void (*) (int, int) FUN; 更符合 “typedef 类型 别名;”的格式。但是这样写会编译报错,也就是这种写法是错误的。
也可以用using 来取别名(C++11中开始支持)

	using FUN = void (*) (int, int);//感觉这样方式更符合理解。这也是为啥C++11中开始支持使用using来给类型取别名的意义。

注意:不管 typedef 还是using 都是给已有的简单类型 或者 复杂类型 取一个别名,而不是创建一个新的类型。

例子:

#include <iostream>
#include <stdio.h>

typedef void (*FUNC) (int, int);//使用typedef来取别名
using FUNC1 = void (*) (int, int);//使用using来取别名
using FUNC2 = int (*) (int, int);//使用using来取别名

void Fun(int a, int b){//函数定义
    std::cout << "a =" << a <<", b=" << b <<std::endl;
};

int Fun1(int a, int b){//函数定义
    std::cout << "a =" << a <<", b=" << b <<std::endl;
    return 0;
};

int main(int argc, char *argv[])
{

    FUNC fun_ptr = Fun; //比直接 void (*fun_ptr) (int, int) = Fun;写简洁
    FUNC1 fun_ptr1 = Fun; //比直接 void (*fun_ptr1) (int, int) = Fun;写简洁
    FUNC2 fun_ptr2 = Fun1; //比直接 int (*fun_ptr2) (int, int) = Fun1;写简洁

     // std::cout << "Fun函数地址:" << Fun <<std::endl;//这样方式打印函数地址会把函数指针转换为bool类型,故打印出来的是总是1,改用下面的printf函数
    fun_ptr(2,3);//使用函数指针来调用函数  // a = 2, b = 3
    fun_ptr1(5,3);//使用函数指针来调用函数 // a = 5, b = 3
    fun_ptr2(6,3);//使用函数指针来调用函数 // a = 6, b = 3
    printf("Fun函数的地址为:%p\n",Fun);//Fun函数的地址为:0x400910
    printf("Fun1函数的地址为:%p\n",Fun1);//Fun1函数的地址为:0x400922
    printf("fun_ptr的指向地址:%p\n",fun_ptr);//fun_ptr的指向地址:0x400910
    printf("fun_ptr1的指向地址:%p\n",fun_ptr1);//fun_ptr1的指向地址:0x400910
    printf("fun_ptr2的指向地址:%p\n",fun_ptr2);//fun_ptr2的指向地址:0x400922
    return 0;
}

typedef和using 给函数的类型取别名

也可以给函数类型取别名

typedef void FUNC4(int, int);//使用typedef来给函数的类型取别名,不看 前面的typedef 单独看 void FUNC4(int, int);是一个函数声明,即函数原型。

和函数指针取别名进行比较:

typedef void (*FUNC) (int, int);//使用typedef来给函数指针的类型取别名
分析:
//void (*FUNC) (int, int); //定义一个 返回值为void,拥有两个int参数类型的 函数指针,函数指针名称为FUNC
//void FUNC4(int, int); //声明一个 返回值为void,拥有两个int参数类型的 函数,此函数的函数名是 FUNC4.
//它们就是一个表示 函数指针,一个表示函数
//同理 形如上面的 两者前面都加上typedef后,FUNC表示的函数指针的类型别名。 FUNC4表示的函数的类型的别名。

完整代码演示:

#include <iostream>
#include <stdio.h>

typedef void (*FUNC) (int, int);//使用typedef来给函数指针的类型取别名
typedef void FUNC4(int, int);//使用typedef来给函数的类型取别名,不看 前面的typedef 单独看 void FUNC4(int, int);是一个函数声明,即函数原型。
//或者使用using
//using FUNC = void (*) (int,int); //等号右侧表示函数指针的类型
//using FUNC4 = void (int,int); //等号右侧表示函数的类型
//两个类型的区别就是一个有*,一个没有*


//void (*FUNC) (int, int); //定义一个 返回值为void,拥有两个int参数类型的 函数指针,函数指针名称为FUNC
//void FUNC4(int, int); //声明一个 返回值为void,拥有两个int参数类型的 函数,此函数的函数名是 FUNC4.
//它们就是一个表示 函数指针,一个表示函数
//同理 两者前面都加上typedef后,FUNC表示的函数指针的类型别名。 FUNC4表示的函数类型的别名。

void Fun(int a, int b){//函数定义
    std::cout << "a =" << a <<", b=" << b <<std::endl;
};

int Fun1(int a, int b){//函数定义
    std::cout << "a =" << a <<", b=" << b <<std::endl;
    return 0;
};

int main(int argc, char *argv[])
{
    FUNC fun_ptr = Fun; //比直接 void (*fun_ptr) (int, int) = Fun;简洁
    FUNC4* fun_ptr3 = Fun;// 在FUNC4后加* 表示的就是函数指针的类型。可以这样理解 FUNC4是函数的类型 FUNC4*就是指向 和FUNC4相同类型的函数地址 的指针的类型。
    //(void (int,int))* fun_ptr3 = Fun; //直接这样写语义会报错,这也就是为啥用typedef来给复杂类型取别名的用处,至于编译解析交给编译器完成。

    // std::cout << "Fun函数地址:" << Fun <<std::endl;//这样方式打印函数地址会把函数指针转换为bool类型,故打印出来的是总是1,改用下面的printf函数
    fun_ptr(2,3);//使用函数指针来调用函数  // a = 2, b = 3
    fun_ptr3(5,3);//使用函数指针来调用函数 // a =5, b=3
    printf("Fun函数的地址为:%p\n",Fun);//Fun函数的地址为:0x400910
    printf("fun_ptr3的指向地址:%p\n",fun_ptr3);//fun_ptr的指向地址:0x400910
    return 0;
}

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

VBA数据库解决方案第七讲:如何利用Recordset对象打开数据库的数据记录集

《VBA数据库解决方案》教程&#xff08;版权10090845&#xff09;是我推出的第二套教程&#xff0c;目前已经是第二版修订了。这套教程定位于中级&#xff0c;是学完字典后的另一个专题讲解。数据库是数据处理的利器&#xff0c;教程中详细介绍了利用ADO连接ACCDB和EXCEL的方法…

zabbix监控nginx

zabbix是什么 web界面提供的一种可视化的监控服务软件 以分布式的方式系统监控以及网络监控&#xff0c;硬件监控等等开源的软件 zabbix的架构 1、c/s模式 客户端和服务端&#xff0c;zabbix server服务端 zabbix agent 客户端 2、通过B/S B是浏览器 S服务端&#xff0c;通…

WEBAPI返回图片显示在VUE前端

WEBAPI部分 通过nuget安装Opencvsharp &#xff0c;这部分用来做图像处理 在controller中写如下方法&#xff0c;我要对原图进行旋转使用了Opencv&#xff0c;如果不需要旋转可以用注释的代码 [HttpGet(Name "ShowImage")]public async Task<IActionResult> …

广州华锐互动:数字孪生系统让生产工艺流程可视化,提高生产效率和质量

随着科技的飞速发展&#xff0c;数字化技术已经深入到各个行业&#xff0c;制造业也不例外。生产制造数字孪生系统作为一种新型的生产管理工具&#xff0c;正逐渐成为制造业的发展新趋势。 生产制造数字孪生系统是一种基于三维数字化技术的生产过程模拟与优化系统。通过对实际生…

【.net core 7】新建net core web api并引入日志、处理请求跨域以及发布

效果图&#xff1a; 1.新建.net core web api项目 选择src文件夹》添加》新建项目 输入框搜索&#xff1a;web api 》选择ASP.NET Core Web API 输入项目名称、选择位置为项目的 src文件夹下 我的项目是net 7.0版本&#xff0c;实际选择请看自己的项目规划 2.处理Progr…

Linux程序设计(下)

系列文章目录 文章目录 系列文章目录十、调试断言 十一、进程和信息号进程表进程调度启动新进程信号**信号处理****发送信号** 十二、POSIX线程线程创建线程同步线程属性取消一个线程pthread_exit, exit, _exit 十三、管道popen, pipe父子进程将管道用作标准输入和标准输出 命名…

mybatis多表查询(xml)

多表查询都用resultMap resultMap 说白了就是他可以手动设置映射参数&#xff0c;例如 可以指定 column代表数据库的参数 property 代表实体类的参数 <id column"roleid" property"id"></id> column代表数据库的参数 property 代表实体类…

C++入门篇第十篇----继承

前言&#xff1a; 本篇我们将开始讲解C的继承&#xff0c;我想要说的是&#xff0c;C的主体基本就是围绕类和对象展开的&#xff0c;继承也是以类和对象为主体&#xff0c;可以说&#xff0c;C相较于C优化的地方就在于它对于结构体的使用方法的高度扩展和适用于更多实际的场景…

外包干了2年,技术退步明显。。。

前言 简单的说下&#xff0c;我大学的一个同学&#xff0c;毕业后我自己去了自研的公司&#xff0c;他去了外包&#xff0c;快两年了我薪资、技术各个方面都有了很大的提升&#xff0c;他在外包干的这两年人都要废了&#xff0c;技术没一点提升&#xff0c;学不到任何东西&…

软件工程 - 第8章 面向对象建模 - 3 - 动态建模

状态图 状态是指在对象生命周期中满足某些条件、执行某些活动或等待某些事件的一个条件和状况 。 案例一&#xff1a;描述烧水器在工作时的详细行为细节 “人就是一个类&#xff0c;而你”、我”、张三”等都是“人这个类的一个实例&#xff0c;站着”、“躺着等都是对象的一…

Edge 旧版本回退

微软官网 下载策略文件 下载后&#xff0c;解压打开 cad 包&#xff0c;把里面的 Windows\ADMX\ 下 3 个 *.admx 文件解压到 C:\Windows\PolicyDefinitions Windows\ADMX\zh-CN 下 3 个 *.adlm 文件解压到 C:\Windows\PolicyDefinitions\zh-CN Windows 搜索 gpedit&#xff…

Swin Transformer实战图像分类(Windows下,无需用到Conda,亲测有效)

目录 前言 一、从官网拿到源码&#xff0c;然后配置自己缺少的环境。 针对可能遇到的错误&#xff1a; 二、数据集获取与处理 2.1 数据集下载 2.2 数据集处理 三、下载预训练权重 四、修改部分参数配置 4.1 修改config.py 4.2 修改build.py 4.3 修改units.py 4.4 修…

LeetCode的几道题

一、捡石头 292 思路就是&#xff1a; 谁面对4块石头的时候&#xff0c;谁就输&#xff08;因为每次就是1-3块石头&#xff0c;如果剩下4块石头&#xff0c;你怎么拿&#xff0c;我都能把剩下的拿走&#xff0c;所以你就要想尽办法让对面面对4块石头的倍数&#xff0c; 比如有…

python常用函数

1.len函数求字符串长度 例如 2.input函数为输入 input里边可以是任意类型的数据 但是它返回的值是一个字符串(即现在只能做出打印那些操作) 想做出其他操作的话,要强制类型转换 例,用str转换为字符串(类似的还有float),字符串可以互相拼接 所以要记得用了input函数后要强制…

十六进制数列求和

高精度数组的集大成 做的时候在和高中同学叙叙旧&#xff0c;差点寄掉 代码如下&#xff1a; #include<stdio.h> void expand(int len); const char hexadecimal[17] "0123456789ABCDEF"; int result[20], mid[20], l_result[100];int main(void) {char tm…

深度学习常见回归分支算法逐步分析,各种回归之间的优缺点,适用场景,举例演示

文章目录 1、线性回归&#xff08;Linear Regression&#xff09;1.1 优点1.2 缺点1.3 适用场景1.4 图例说明 2、多项式回归&#xff08;Polynomial Regression&#xff09;2.1 优点2.2 缺点2.3 适用场景2.4 图例说明 3、决策树回归&#xff08;Decision Tree Regression&#…

疫苗接种(链表练习)

很明显&#xff0c;数组也可以做&#xff0c;但是我想练习链表 这道题我上交的时候&#xff0c;同一份代码&#xff0c;三个编译器&#xff0c;三个成绩&#xff0c;有点搞心态 代码如下&#xff1a; #include<stdio.h> #include<math.h> #include<stdlib.h&…

线上CPU飙高问题排查!

https://v.douyin.com/iRTqH5ug/ linux top命令 top 命令是 Linux 下一个强大的实用程序&#xff0c;提供了系统资源使用情况的动态、实时概览。它显示了当前正在运行的进程信息&#xff0c;以及有关系统性能和资源利用情况的信息。 以下是 top 命令提供的关键信息的简要概述…

面试数据库八股文十问十答第一期

面试数据库八股文十问十答第一期 作者&#xff1a;程序员小白条&#xff0c;个人博客 1.MySQL常见索引、 MySQL常见索引有: 主键索引、唯一索引、普通索引、全文索引、组合索引(最左前缀)主键索引特点&#xff1a;唯一性&#xff0c;非空&#xff0c;自增&#xff08;如果使用…

Linux中的UDEV机制与守护进程

Linux中的UDEV守护进程 udev简介守护进程守护进程概念守护进程程序设计守护进程的应用守护进程和后台进程的区别 UDEV的配置文件自动挂载U盘 udev简介 udev是一个设备管理工具&#xff0c;udev以守护进程的形式运行&#xff0c;通过侦听内核发出来的uevent来管理/dev目录下的设…