C语言——函数指针

2023-05-16

目录

1. 函数指针概念

1.1 函数指针的声明

1.2 函数指针的定义

1.3 使用typedef定义函数指针的别名

1.4 将常数转换为函数指针

1.5 函数指针的调用

1.6 将函数指针作为函数的传入参数

2. 简单的例子


1. 函数指针概念

1.1 函数指针的声明

类似变量在内存中会分配一个空间,函数在内存中也会分配一个空间,这个空间的入口(或者叫首地址)称为函数的地址。用整型指针可以保存整形变量的地址,同样地,用函数指针可以保存函数的地址。

我们回顾一下怎么定义一个整型指针:

int *p_int;

由于整型变量只需要定义变量的类型即可完全规定这个变量的格式(个人理解,变量声明限定了变量的格式,变量定义规定了其内容)。但是对于一个函数来说(将函数也看成一种复杂的变量),函数的格式由哪些东西唯一确定呢?函数返回值,函数参数个数和参数类型

那么我们是不是可以通过函数返回值,函数参数个数和参数类型来声明函数指针变量的格式?

int (*p_int);//声明了一个整型指针变量
int (*p)(int,int);//声明了一个函数指针变量,该变量指向一个函数,该函数的返回值为int类型,并且接受两个int类型的形参。

上面的代码声明了一个函数指针,将其与整型指针类比,是不是可以理解为:由于函数的格式的确定需要返回值,参数个数以及参数类型这些东西唯一确定,那么声明一个函数指针就需要这三个东西来唯一确定函数指针的格式

1.2 函数指针的定义

下面来看看,已经声明了函数指针,怎么定义这个函数指针。

类似的,还是用整形变量的定义来类比。

int *p_int;    //声明一个整型指针变量
int arr[2]={1,2};//定义一个整型数组
*p_int=arr;    //整型指针变量的定义(赋值)

不过这次用的是整形数组,见上面的代码。整型数组arr[],其名称代表数组的入口,因此*p_int=arr; 可以给整型指针变量赋值,赋值的过程相当于规定了变量的内容,上面1.1中声明的过程相当于规定了变量的格式,变量的格式和内容都规定了,就可以使用这个变量了。此时通过p_int这个指针就可以访问arr数组。

int Max(int x, int y);//声明max函数
int main(void){
    int (*p)(int,int);  //定义函数指针
    p=Max;              //类似的,函数名也代表函数的入口(函数地址),可以用其初始化函数指针,但是前提是函数指针的格式与用来初始化的函数的格式相同
    return 0;
}

上面的代码先定义了一个函数指针,其指向一个函数,该函数的返回值是int,并且传入参数为两个int类型的参数。而Max函数本身就是返回值是int,并且传入参数为两个int类型的参数的函数,因此可以用Max函数初始化函数指针p。而函数名Max就是函数的入口,因此p=Max就对函数指针p进行了初始化。

1.3 使用typedef定义函数指针的别名

我们都知道使用typedef关键字可以定义数据类型的别名,最常见的是给结构体定义别名,如下:

//嵌入式开发中最常见的使用typedef定义unsigned int的别名为uint32_t
typedef unsigned  int uint32_t;

//定义node结构体,Node是其别名
typedef struct node{
    int val;
    struct node* next;
}Node;
typedef Node* pNode;//定义Node*类型的别名为pNode
pNode head;//使用类型的别名声明struct node*类型的变量;

同样地,使用typedef关键字可以定义函数指针的别名,如下:

//HANDLER是函数指针类型的别名,该类型的函数指针指向一个返回值为int,参数为两个int的函数
typedef int (*HANDLER)(int,int);

HANDLER表示一个函数指针,指向一个返回值是int,参数为两个int类型的函数。

使用HANDLER可以定义函数指针类型的变量,

typedef int (*HANDLER)(int,int);//定义一个函数指针类型的别名
int Max(int x, int y);//声明max函数

int main(void){

    int (*p)(int,int);  //定义函数指针
    HANDLER q;//使用函数指针的别名声明q这个函数指针
    q=Max;
    p=Max;  //给函数指针赋值(其值就是函数名)
    int res1=(*p)(a,b);  //赋值完成之后,通过函数指针调用函数
    int res2=q(a,b);
    printf("res1:%d; res2:%d ",res1,res2);   //打印res1和res2的值,就是a和b的最大值,1
}

1.4 将常数转换为函数指针

嵌入式开发经常会遇到的问题,将一个常数,比如0x0001转换为一个“指向返回值为void的函数的指针”,我们先看看一个指向返回值为void的函数的指针,这种类型的变量怎么定义。

void (*p)();//定义了一个函数指针p,其指向一个返回值为void的函数

然后由于常数0x0001不是指针类型的变量,我们先需要强制类型转换,将常数类型转为函数指针类型,如下:

(void (*)())0x0001;//将0x0001强制类型转换为一个函数指针,其指向一个返回值为void类型的函数

这个时候我们成功将常数0x0001转为了函数指针。如果想让程序跳转到0x0001的地方执行,那么只需要通过函数指针调用函数就行了,如下:

(void (*)())0x0001;//将0x0001强制类型转换为一个函数指针,其指向一个返回值为void类型的函数
(*(void (*)())0x0001)();//函数指针前加“*”,解引用,然后调用函数

结合上一节说的typedef关键字,可以将上面两行代码写成如下的形式:

typedef void (*funcptr)();//定义函数指针类型的别名
(funcptr)0x0001;//使用funcptr将0x0001强制类型转换为一个函数指针
((*funcptr)0x0001)();//函数指针前加“*”,解引用,然后调用函数

1.5 函数指针的调用

知道了如何定义和声明函数指针,下面就是如何使用函数指针了。这里还是用整型数组的使用来类比。

int *p_int;    //声明一个整型指针变量
int arr[2]={1,2};//定义一个整型数组
*p_int=arr;    //整型指针变量的定义(赋值)
printf("%d",(*p_int)[0]);//该行代码打印(*p_int)[0],就是arr[0],即arr数组第一个元素

通过上面的代码可以看出,使用“*+指针名”可以达到与使用数组名一样的访问数组元素的目的。那么,看看下面的代码:

int Max(int x, int y);//声明max函数
int main(void){
    int a=0, b=1;
    int (*p)(int,int);  //定义函数指针
    p=Max;              //类似的,函数名也代表函数的入口(函数地址),可以用其初始化函数指针,但是前提是函数指针的格式与用来初始化的函数的格式相同
    int res=(*p)(a,b);//通过*+函数变量名,可以访问p指向的函数,与res=Max(a,b)相同
    return 0;
}

使用“*+指针名”可以通过函数指针访问该指针指向的那个函数。是不是跟数组的访问很类似?如果把函数也当成变量(只不过这个“变量”,其结构有比较多的因素决定,其内容可以占据很大的空间),那么这样的函数指针调用是不是就更容易理解了。

1.6 将函数指针作为函数的传入参数

有了函数指针之后,是不是就可以将函数指针作为参数传递给另一个函数?我们给函数传递参数的目的是什么?为了使用外部的数据来改变函数的操作或者调用逻辑,影响函数的返回值或者通过传入变量的指针,在函数中通过指针修改变量的内容。

函数指针作为函数的传入参数,可以给函数传递一个函数供其内部调用,这个传入的函数,我们可以在外部完成函数的实现,这样可以在保证主逻辑不变的情况下,通过改变传入函数的逻辑,最终影响主逻辑的输出

int Max(int x, int y);//声明max函数
int GetValue(int x,int y,int(*p)(int,int));//声明getvalue函数
int main(void){
    int a=1,b=0;        //定义两个变量a,b
    int maxValue=GetValue(a,b,Max);//将函数指针作为参数传递给函数
    return 0;
}

//这个函数通过函数指针p调用它的传入参数(p指向的函数的入口)
int GetValue(int x,int y,int(*p)(int,int)){
    return (*p)(x,y);//通过函数指针,调用传入的函数参数,得到函数参数的返回值,然后返回该函数的返回值
}
//定义max函数,该函数返回两个变量中较大的那个变量的值
int Max(int x, int y){
    return x>y?x:y;
}

这样将外部定义的函数通过函数指针传递给内部函数,可以在满足一定内部算法封装的情况下,通过外部定义的函数来改变算法的具体输出形式。比如C标准库的qsort排序算法,需要我们自己定义比较函数,来决定排序的结果是升序还是降序

2. 简单的例子

举一个简单的例子,该例子先声明一个函数指针,然后定义该函数指针,最后通过函数指针访问该指针指向的函数,实现函数调用的功能

#include <stdio.h>
#include <stdlib.h>
//声明max函数
int Max(int x, int y);
int main(void){

    int a=1,b=0;        //定义两个变量a,b
    int *q;
    int (*p)(int,int);  //定义函数指针
    p=Max;  //给函数指针赋值(其值就是函数名)
    int res=(*p)(a,b);  //赋值完成之后,通过函数指针调用函数
    printf("%d",res);   //打印res的值,就是a和b的最大值,1
    return 0;
}
//定义max函数,该函数返回两个变量中较大的那个变量的值
int Max(int x, int y){
    return x>y?x:y;
}

下面是通过给函数传递函数指针,在函数中调用外部定义的函数的例子。

#include <stdio.h>
#include <stdlib.h>

int Max(int x, int y);//声明max函数
int Min(int x, int y);//声明min函数
int GetValue(int x,int y,int(*p)(int,int));//生命getvalue函数
int main(void){

    int a=1,b=0;        //定义两个变量a,b
    int maxValue=GetValue(a,b,Max);
    int minValue=GetValue(a,b,Min);

    printf("The smaller value between a and b is:%d\n",minValue);   //打印res的值,就是a和b的最大值,1
    printf("The larger value between a and b is:%d\n",maxValue);   //打印res的值,就是a和b的最大值,1

    return 0;
}
//这个函数通过函数指针p调用它的传入参数(p指向的函数的入口)
int GetValue(int x,int y,int(*p)(int,int)){
    return (*p)(x,y);//通过函数指针,调用传入的函数参数,得到函数参数的返回值,然后返回该函数的返回值
}
//定义max函数,该函数返回两个变量中较大的那个变量的值
int Max(int x, int y){
    return x>y?x:y;
}
//定义max函数,该函数返回两个变量中较小的那个变量的值
int Min(int x, int y){
    return x<y?x:y;
}

 

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

C语言——函数指针 的相关文章

  • IDEA 2018.3.2 版本配置SVN

    IDEA 2018 3 2 版本配置SVN 1 首先安装TortoiseSVN2 配置IDEA3 IDEA开启SVN 1 首先安装TortoiseSVN 安装的过程中 xff0c 需要勾选command line client tools
  • springmvc 上传文件,单个文件,多个文件

    1 Java代码部分 xff1a 1 1spring配置 xff1a span class token operator lt span span class token operator span span class token ope
  • Jquery实现免上传预览图片功能

    Demo如下 span class token doctype lt DOCTYPE html gt span span class token tag span class token tag span class token punct
  • jQuery :not() 选择器

    JQ选择器 xff0c 不选择某些标签 基础用法 xff1a p not class 多个not p not class not id span class token doctype lt DOCTYPE html gt span spa
  • 弱智笔记 js用==比较 空字符串==0,返回true

    span class token keyword if span span class token punctuation span span class token string 34 34 span span class token o
  • PHP7+apache2.4环境搭建

    PHP 43 apache环境搭建 版本 xff1a PHP Version 7 1 30 apache2 4 下载 xff1a 下载php时 xff0c 注意下载VC14 x64 Thread Safe xff0c 否则没有php7apa
  • JAVA构造器注意事项

    JAVA构造器 1 JAVA默认提供无参构造器 2 默认无参构造器 对域进行初始化规则 数值型 xff1a 0 布尔类型 xff1a false 对象类型 xff1a null 3 如果自己写了一个带参构造器 xff0c 默认的无参构造器失
  • 原生大数据|elasticSearch|低版本kibana组件的汉化

    前言 xff1a 大数据的范畴里包括EFK ELK xff0c 这些套件安装部署是非常的成熟 xff0c 因此是比较好部署安装的 xff0c 一般的 xff0c 困难出现在部署完成后的运营和维护 kibana这个组件的版本低于7我们就应该认
  • JAVA继承-注意事项

    JAVA继承 1 子类所有构造器 xff0c 会隐式调用父类的无参构造器 原理 xff1a 子类所有构造器 xff0c 都会在第一行隐式调用super 问题 xff1a 如果父类没有无参构造器 xff0c 编译报错 解决 xff1a 在子类
  • mac上安装brew出错curl: Failed to connect to raw.githubusercontent.com port 443解决方法

    问题描述 由于最近重做了电脑系统 xff0c 重新下载安装brew 就报错了 xff0c raw githubusercontent com 在国内由于不可描述的原因就无法访问 解决方法一 参考网上的解决方法 首先是访问这个网址 https
  • Hexo + gitHub pages

    网址 xff1a https oldmee github io hexo的写作流程就是会按照日期自动帮你归类 xff0c 你new了一个page会生成一个markdown文件 xff0c 你就可以愉快的写作了 xff0c 边写边看效果 xf
  • 使用策略模式优化过多的if else语句

    此处的过多是指if else超过三层 xff0c 如下面的代码 xff1a public class MainStart public static void main String args String message 61 34 se
  • 在基于图像的深度学习中如何做数据的自动标注以及自动标注的等级介绍

    作者 xff1a Tobias Schaffrath Rosario 编译 xff1a ronghuaiyang 原文 xff1a 在基于图像的深度学习中如何做数据的自动标注以及自动标注的等级介绍 ronghuaiyang的博客 CSDN博
  • 【Java】 牛客网华为机试108题汇总

    文章目录 目录 目录 1 求字符串最后一个单词长度 2 计算字符串个数 3 明明的随机数 4 字符串分割 5 进制转换 6 质数因子 7 HJ19 简单错误记录 8 HJ25 数据分类处理 9 HJ30 字符串合并处理 1 求字符串最后一个
  • OpenCV的简单使用教程与基本函数(C++版本)

    OpenCV的简单使用教程 xff08 C 43 43 xff09 OpenCV简介OpenCV的使用基础打开 显示和保存图像图像存储变量 Mat类图像元素的存储读入图像文件创建Mat类复制Mat类图像元素的访问OpenCV画图命令行交互界
  • 【实用技巧】知网文献不限量免费下载方法,亲测可用

    众所周知 xff0c 知网可以查看和下载相关的文献资料 xff0c 只要用校园网就能免费的下载和查阅 xff0c 但是也有不少学校并没有和知网合作 xff0c 这样就没办法下载 xff0c 只能充钱才能享受 那么有没有白嫖的办法呢 xff1
  • git rm 删除 以及清空暂存区

    一 使用linux命令rm删除 xff1a 在当前工作区有文件readme txt xff0c 并被git跟踪 xff0c 且有提交历史 运行如下命令 xff1a rm readme txt 分析如下 xff1a xff08 1 xff09
  • centOS开启和关闭防火墙

    CentOS 7 0默认使用的是firewall作为防火墙 xff0c 在安装某些软件的时候就需要关闭防火墙 一 查看防火墙的状态 开启显示 running xff0c 关闭后显示 not running 执行命令 firewall cmd
  • postgresql|数据库|pg数据库的文件系统详解---最全面的解析

    前言 xff1a postgresql是一个非常成熟的开源的功能强大的关系型数据库 xff0c 总体来说 xff0c 该数据库安装简单 xff0c 使用复杂 xff0c 复杂度在多个维度都会有所体现 xff0c 比如 xff0c SQL语法
  • k8s部署ingress-nginx的方法步骤

    1 ingress概述 我们知道service的表现形式为IP PORT xff0c 即工作在第四层传输层 xff08 TCP IP层 xff09 xff0c 那么对于不同的URL地址经常对应用不同的后端服务或者虚拟服务器 xff0c 这些

随机推荐

  • 计算机软技术,如何画好一张架构图?

    什么是架构图 xff1f 如何画好一张架构图 xff0c 要做好这件事情首先要回答的就是什么是架构图 我们日常工作中经常能看到各种各样的架构图 xff0c 而且经常会发现大家对架构图的理解各有侧重 深入追究到这个问题 xff0c 可能一下子
  • yum与apt

    linux系统分类 一般来说著名的linux系统基本上分两大类 xff1a 1 RedHat系列 xff1a Redhat Centos Fedora等 2 Debian系列 xff1a Debian Ubuntu等 RedHat 系列 1
  • netstat常用场景记录

    参数说明 netstat命令各个参数说明如下 xff1a a 显示所有连线中的端口 不加此参数默认不显示处于监听状态的端口 n 不进行DNS轮询 xff0c 显示IP 可以加速操作 r 显示路由表信息 t 显示TCP传输协议的端口连线状况
  • log4j动态加载配置文件

    应用场景与问题 当项目在运行时 xff0c 我们如果需要修改log4j 1 X或者log4j2的配置文件 xff0c 一般来说我们是不能直接将项目停止运行再来修改文件重新部署的 于是就有这样一个问题 xff1a 如何在不停止当前项目的运行的
  • vm options、program arguments、environment property

    系统变量 system property 是java应用程序自身指定的变量 xff0c 通常我们可以在启动应用的时候指定 xff0c 指定方式可以有以下两种 输入java jar help 回顾一下java启动jar文件的命令 java o
  • 解决IntelliJ IDEA各种中文乱码问题

    1 修改文件编码方式 打开IntelliJ IDEA gt File gt Setting gt Editor gt File Encodings xff0c 将Global Encoding Project Encoding Defaul
  • 解析IDEA中的Artifacts配置

    1 Artifact 2 Artifact名称 3 Artifact类型 4 输出路径 xff08 也就是Deployment root部署根目录 xff09 xff0c 项目运行后的输出根目录 5 输出根目录 xff0c 即4指定的地址
  • IDEA代码以及注释格式化,行宽设置,以及自动换行

    一 设置代码最大行宽 xff0c 以及自动换行 勾选wrap on typing xff0c 即在编码时 xff0c 超出最大行宽 xff0c 则自动换行 xff0c 或者采用下面这种方式 xff0c 在手动格式化的时候 xff0c 进行自
  • IDEA设置代码规范,代码格式化设置,以及ALIBABA编码规约

    阿里巴巴格式化模板文件下载地址 https github com alibaba p3c 第一个文件是 代码格式化时用的模板 第二个文件是 注释模板 一 eclipse 格式化设置 格式化模板导入 依次点击 xff1a Window gt
  • 数组的初始化 array initializer is not allowed here

    此处不允许使用数组初始值设定项 array initializer is not allowed here 数组的使用分声明和初始化两部分 xff0c 两者可同时进行 xff0c 也可分开进行 int array 声明 array 61 n
  • Maven打包所有依赖到一个可执行jar中,将外部依赖加入到classPath中

    首先说一下比较常用的两种打包方式 xff1a 前提 xff1a maven构建可执行jar包时 xff0c 如果项目依赖了pom中定义的dependency之外的外部jar包 xff0c maven jar plugin默认是不会把这 些额
  • postgresql数据库|数据库实操----表复制详解

    前言 xff1a 通常情况下 xff0c 我们对数据库的增删改查的时候 xff0c 为了确保数据的安全 xff0c 需要备份表 xff0c 那么 xff0c 一种方法是通过pg dump 这个工具做SQL转储操作 xff0c 此方法比较复杂
  • Centos7 配置防火墙 firewall

    一 firewall 1 从CentOS7开始 xff0c 默认使用firewall来配置防火墙 xff0c 没有安装iptables xff08 旧版默认安装 xff09 2 firewall的配置文件是以xml的格式 xff0c 存储在
  • Windows多媒体开发框架介绍

    Windows 多媒体开发框架介绍 欢迎来到 Windows 的多媒体开发世界2D 绘图 API1 GDI2 GDI 43 3 Direct2D 音频 API1 MME2 DirectSound3 Windows Core AudioCor
  • 【Ubuntu】在QT运行程序后无结果显示,只有终端运行的解决办法

    转自 http stackoverflow com questions 3255035 qt creator run in terminal https bugs launchpad net ubuntu 43 source qtcreat
  • 【蓝桥杯嵌入式】关于CT117E下载程序出问题解决方案(含keil mdk4和keil mdk5移植)

    废话 万事开头难 xff0c 然后中间难 xff0c 最后难 寒假刚开始 xff0c 我看到了蓝桥杯嵌入式 很快啊 xff01 报名 买板一气呵成 没想到这块CT117E板子它不讲武德 xff0c 来骗 xff0c 来偷袭我这个二十岁的小伙
  • c语言冒泡排序详解(分析每一步,附代码)

    冒泡排序 xff08 Bubble Sort xff09 xff0c 是一种计算机科学领域的较简单的排序算法 它重复地走访过要排序的元素列 xff0c 依次比较两个相邻的元素 xff0c 如果顺序 xff08 如从大到小 首字母从Z到A x
  • 解决maven update project 后项目jdk变成1.5

    一 问题描述 在Eclipse中新建了一个Maven工程 然后更改JDK版本为1 7 结果每次使用Maven gt Update project的时候JDK版本都恢复成1 5 二 原因分析 Maven官方文档有如下描述 xff1a 编译器插
  • C语言——整型和浮点型混合运算

    1 int和double混合运算 C语言int和double混合运算时 xff0c 会自动将int类型的数据转换为double类型的数据 xff0c 最后得到的结果也是double类型 如下例 xff1a double a 61 4 0 9
  • C语言——函数指针

    目录 1 函数指针概念 1 1 函数指针的声明 1 2 函数指针的定义 1 3 使用typedef定义函数指针的别名 1 4 将常数转换为函数指针 1 5 函数指针的调用 1 6 将函数指针作为函数的传入参数 2 简单的例子 1 函数指针概