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

news2024/12/22 8:07:32

文章目录

  • 指针函数
  • 函数指针
  • 函数指针的定义与指针函数的声明的区别
    • 函数指针的定义
    • 指针函数的声明
  • 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;
}

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

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

相关文章

线上CPU飙高问题排查!

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

整体迁移SVN仓库到新的windows服务器

一、背景 公司原有的SVN服务器年代比较久远经常出现重启情况&#xff0c;需要把SVN仓库重新迁移到新的服务器上&#xff0c;在网上也搜到过拷贝Repositories文件直接在新服务器覆盖的迁移方案&#xff0c;但考虑到原有的操作系统和现有的操作系统版本不一致&#xff0c;SVN版本…

【开源】基于JAVA的超市账单管理系统

项目编号&#xff1a; S 032 &#xff0c;文末获取源码。 \color{red}{项目编号&#xff1a;S032&#xff0c;文末获取源码。} 项目编号&#xff1a;S032&#xff0c;文末获取源码。 目录 一、摘要1.1 项目介绍1.2 项目录屏 二、功能模块三、系统设计3.1 总体设计3.2 前端设计3…

【计算机网络】14、DHCP

文章目录 一、概述1.1 好处 二、概念2.1 分配 IP2.2 控制租赁时间2.3 DHCP 的其他网络功能2.4 IP地址范围和用户类别2.5 安全 三、DHCP 消息3.1 DHCP discover message3.2 DHCP offers a message 如果没有 DHCP&#xff0c;IT管理者必须手动选出可用的 ip&#xff0c;这太耗时了…

每天五分钟计算机视觉:AlexNet网络的结构特点

本文重点 在前面的一篇文章中&#xff0c;我们对AlexNet网络模型的参数进行了详细的介绍&#xff0c;本文对其网络模型的特点进行总结。 特点 1、AlexNet的网络结构比LeNet5更深&#xff0c;模型包括5个卷积层和3个全连接层。参数总量大概为249MB。 2、Alex使用了ReLu激活函…

【vSphere 8 自签名 VMCA 证书】企业 CA 签名证书替换 vSphere VMCA CA 证书Ⅱ—— 创建和添加证书模板

目录 3. 使用 Microsoft 证书颁发机构创建 VMCA 证书模板3.1 打开 Certificate Template Console3.2 复制模板修改 Compatibility 选项卡修改 General 选项卡修改 Extensions 选项卡确认新模板 4. 将新模板添加到证书模板4.1 打开 Certificate Console4.2 创建证书模板 关联博文…

高端大气简历模板(精选8篇)

想要让简历在众多求职者中脱颖而出&#xff0c;吸引HR的眼球吗&#xff0c;可以看看这8篇精选的高端大气简历模板&#xff01;本文为大家提供了多种行业、职位的简历案例&#xff0c;助大家打造一份令人惊艳的简历&#xff0c;轻松斩获心仪职位&#xff01; 高端大气简历模板下…

【FPGA图像处理实战】- 图像基础知识

视频图像处理是FPGA主要应用方向之一&#xff0c;很多FPGA从事或准备进入这一领域&#xff0c;我们现在开始发布新的FPGA实战专栏——FPGA图像处理。 FPGA处理视频图像处理的主要优势是流水线和并行处理运算&#xff0c;特别是现在视频分辨率越来越大&#xff0c;从720p到1080…

机械臂运动规划、抓取末端执行器、抓取开源项目

运动规划 1.1已有抓取点 假设抓取点已检测到。这些方法设计了从机器人手到目标物体抓取点的路径。这里运动表示是关键问题。虽然存在从机器人手到目标抓握点的无限数量的轨迹&#xff0c;但是由于机器人臂的限制&#xff0c;许多区域无法到达。因此&#xff0c;需要对轨迹进行…

代码浅析DLIO(四)---位姿更新

0. 简介 我们刚刚了解过DLIO的整个流程&#xff0c;我们发现相比于Point-LIO而言&#xff0c;这个方法更适合我们去学习理解&#xff0c;同时官方给出的结果来看DLIO的结果明显好于现在的主流方法&#xff0c;当然指的一提的是&#xff0c;这个DLIO是必须需要六轴IMU的&#x…

面试就是这么简单,offer拿到手软(一)—— 常见非技术问题回答思路

面试系列&#xff1a; 面试就是这么简单&#xff0c;offer拿到手软&#xff08;一&#xff09;—— 常见非技术问题回答思路 面试就是这么简单&#xff0c;offer拿到手软&#xff08;二&#xff09;—— 常见65道非技术面试问题 文章目录 一、前言二、常见面试问题回答思路问…

webGIS使用JS,高德API完成简单的智慧校园项目基础

代码实现 <!DOCTYPE html> <html lang"en"> <head><meta charset"UTF-8"><meta http-equiv"X-UA-Compatible" content"IEedge"><meta name"viewport" content"widthdevice-width, i…

react之@路径解析配置和联想配置

react之路径解析配置和联想配置 一、介绍二、路径解析配置三、联想路径配置 一、介绍 1.路径解析配置&#xff08;webpack&#xff09;&#xff0c;把 / 解析为 src/2.路径联想配置&#xff08;VsCode&#xff09;&#xff0c;VsCode 在输入 / 时&#xff0c;自动联想出来对应…

ARM64版本的chrome浏览器安装

这一快比较玄学&#xff0c;花个半个小时左右才能安装好&#xff0c;也不知道是个什么情况。 sudo snap install chromium只需要以上这个命令&#xff0c;当然&#xff0c;也可以自己去找安装包进行安装&#xff0c;但是测试后发现并没有那么好装&#xff0c;主要是两个部分 一…

Halcon参考手册目标检测和实例分割知识总结

1.1 目标检测原理介 目标检测&#xff1a;我们希望找到图像中的不同实例并将它们分配给某一个类别。实例可以部分重叠&#xff0c;但仍然可以区分为不同的实例。如图(1)所示&#xff0c;在输入图像中找到三个实例并将其分配给某一个类别。 图(1)目标检测示例 实例分割是目标检…

打造个性化github主页 一

文章目录 概述创建仓库静态美化GitHub 统计信息卡仓库 GitHub 额外图钉仓库 热门语言卡仓库 GitHub 资料奖杯仓库 GitHub 活动统计图仓库 打字特效添加中文网站统计仓库 总结 概述 github作为全球最大的代码托管平台&#xff0c;作为程序员都多多少少&#xff0c;都使用过他。…

基于SpringBoot的公益慈善平台

✌全网粉丝20W,csdn特邀作者、博客专家、CSDN新星计划导师、java领域优质创作者,博客之星、掘金/华为云/阿里云/InfoQ等平台优质作者、专注于Java技术领域和毕业项目实战✌ &#x1f345;文末获取项目下载方式&#x1f345; 一、项目背景介绍&#xff1a; 基于SpringBoot的公益…

【上海大学数字逻辑实验报告】三、组合电路(二)

一、实验目的 掌握8421码到余3码的转换。掌握2421码到格雷码的转换。进一步熟悉组合电路的分析和设计方法。学会使用Quartus II设计8421码到余3码的转换电路逻辑图。学会使用Quartus II设计2421码到格雷码的转换电路逻辑图。 二、实验原理 8421码是最常用的BCD码&#xff0c…

YOLOv5算法进阶改进(7)— 将主干网络SPPF更换为SimSPPF / SPP-CSPC / SPPF-CSPC

前言:Hello大家好,我是小哥谈。SimSPPF是YOLOv6中提出的一种改进的空间金字塔池化方法,它是SPPF的升级版。SimSPPF通过在不同尺度上使用不同大小的池化核来提取特征,从而提高了检测器的性能。与SPPF相比,SimSPPF可以在不增加计算成本的情况下提高检测器的性能。本节课就教…

Nacos源码解读01——服务注册

Nacos 2.0 架构设计及新模型 参考 https://zhuanlan.zhihu.com/p/344572647 使用GRPC注册临时实例流程图 SpringBoot自动注入 注入对应服务注册的Bean 监听Tomcat启动事件 NacosAutoServiceRegistration 继承了AbstractAutoServiceRegistration 而 AbstractAutoServiceR…