函数指针和函数指针数组

2023-11-06

函数指针:指向函数的指针。

函数指针数组:一个数组里面存的类型是函数指针。

一、函数指针的声明和调用

1.指针声明:

        函数指针顾名思义就是一个指向函数的指针。

int Add(int x,int y)
{
    return x+y;
}
char* Sub(char*)
{
    
}
//声明函数指针并初始化
int (*Add)(int ,int ) ptr=&Add;

char* (*Sub)(char*) prt_char=⋐

        这里声明了两个函数指针 ptr和ptr_char指向了函数的地址,这里会发现Add取地址(&ADD),同样是原类型加上一个*,就是 int(*Add)(int,int),注意不是int *Add(int,int)。

        对于一个返回指针的函数来说也是一样,Sub取地址(&Sub)原类型加上一个*,就是char*(*Sub)(char*),注意不是char* *Sub(int,int);

PS:声明指针的时候,函数的返回值和参数类型、个数都要对的上。

对于函数来说取地址Add  (&Add),和直接写上本身是一样的(Add)。

所以初始化的时候也可以写成

int Add(int x,int y)
{
    return x+y;
}

int (*Add)(int,int)pf=Add;

 这种写法和上面等价。

 2.指针函数的调用:

可以通过使用函数指针调用函数。

int Add(int x,int y){
    return x+y;
}
int (*pf)(int,int)=&Add;
int ret=(*pf)(2,3);    //等价于int ret=Add(2,3);
printf("%d\n",ret);

 这里用指针调用函数的结果,跟直接使用函数的结果是一样的。

        因为上面&Add和Add的地址是一样的,所以这里导致了,不管解引用了几次pf,得到的结果都是函数Add。

二、函数指针数组

        声明一个数组,里面存储的类型是,指向函数的指针。

声明:

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 (*pf1)(int,int)=Add;    //声明初始化函数指针
int (*pf2)(int,int)=Sub;
int (*pf3)(int,int)=Mul;
int (*pf4)(int,int)=Div;
//函数指针数组
int (*pf[4])(int,int)={Add,Sub,Mul,Div};    //初始化函数指针数组
//调用
for(int i=0;i<4;i++){    //通过数组元素调用函数
    pf[i](2,5);
}

        这里pf先跟[4]结合,说明了pf是个数组,然后去掉pf[4],剩下的int(*)(int,int)就是数组里面存储的类型,这个类型就是函数指针。通过调用数组不同的元素就能调用到不同的函数。 

既然是个数组,当然也可以定义一个指针,指向这个数组,一个指向函数数组的指针

        类型的写法就是取地址pf  (&pf),原来的类型加上一个*,就是int  (  *( *pf[4] ) )(int ,int)。注意不是int* ( *pf[4] )(int,int)

int *(pf[4])(int,int);
int (*(*ptr[4]))(int,int)=&pf;

PS:要注意函数指针数组取地址得到的就不是函数本身了,而是整个数组的地址。 

三.回调函数 

1.定义

        回调函数就是利用函数指针,来在函数内部调用不同的函数。

void test()
{
	printf("hehe!\n");
}

void print_hehe(void (*pfun)())  //这个函数就叫回调函数
{
    if(1)
        pfun();
}

int main()
{
    print_hehe(test);
}

        print_hehe的形参是一个函数指针,只要给函数不同的函数地址实参,就能调用不同的函数。

而这里print_hehe就是一个回调函数。

 2.实例:

//简单的计算器实现
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;
}

void calc(int(*fun)(int,int))
{
    int x,y;
    scanf("%d%d",&x,&y);
    int ret=fun(x,y);
    printf("%d\n",ret);
}

int main()
{
	int input=0;
    scanf("%d",&input);
    while(input){
        switch(input){
            case 1:
                calc(Add);
                break;
            case 2:
                calc(Sub);
                break;
            case 3:
                calc(Mul);
                break;
            case 4:
                calc(Div);
                break;
    	}
    }
    return 0;
}

         这种写法不仅能减少函数冗余,提高代码复用性,而且每次修改和添加代码增加删减功能的时候,修改的地方少,减少了函数代码的耦合度。

 函数指针数组的写法:

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;
}

void menu()
{
    printf("**** 1.加法   2.减法   ****\n");
    printf("**** 3.乘法   4.除法   ****\n");
    printf("**** 0.退出   ****\n");
}

int main()
{
    int (*p[])(int, int) = { 0,Add,Sub,Mul,Div };
    int input = 1, x = 0, y = 0;
    menu();
    printf("请选择:>\n");
    while (input) {
        scanf("%d", &input);
        printf("请输入要计算的两个数:>\n");
        scanf("%d %d", &x, &y);
        int ret = p[input](x, y);
        printf("%d\n", ret);
    }
    
    return 0;
}

四.qsort 函数的解析

        此函数是基于快速排序的算法来进行排序的库函数,为了能够排序各种类型的数据,在函数参数部分留了一个函数指针,给使用者来自己定义排序的规制。

        函数的参数依次为数组地址,数组长度,数组元素的宽度,和函数指针。

struct stu        
{
  char name[20];
  int age;
  double score;
};

//结构体排序规则 根据年龄排序
int com_by_age(const void* e1,const void* e2)
{
    return ((stu*)e1)->age - ((stu*)e2)->age;
}
//结构体排序规则 根据姓名排序
int com_by_name(const void* e1,const void* e2)
{
    return strcmp(((stu*)e1)->name,((stu*)e2)->name);
}

stu arr[]={{"张三",15,88.5},{"李四",14,98.5},{"王五",18,55.5}}; //结构体数组
int sz=sizeof(arr)/sizeof(arr[0]);
qsort(arr,sz,sizeof(arr[0]),com_by_name);

         这里两个自定义函数里面,一个排序的规制是根据年龄来排序,一个是根据名字来排序,注意这里字符串比较要用strcmp函数。

        函数默认是进行升序排序,如果要进行降序排序,改变一下定义排序规则的逻辑就可以

int com_by_age(const void* e1,const void* e2)
{
    return ((stu*)e2)->age - ((stu*)e1)->age;
}
int com_by_name(const void* e1,const void* e2)
{
    strcmp(((stu*)e2)->name,((stu*)e1)->name);
}

         从这里可以发现,实现降序的话,调转  return ((stu*)e2)->age - ((stu*)e1)->age;
         这里表达的意思就是 如果后面的元素 > 前面的元素就交换,就把两个元素交换,就把小的元素放后面了。

五.模拟qsort函数的逻辑实现一个冒泡排序函数

void Swap(char *buf1,char* buf2,width)
{
    //因为这里buf1和buf2解引用是一个字节,但是把所有字节都交换位置,相当于交换了两个数
    for(int i=0;i<width;i++){
        char temp=*buf1;
        *buf1=*buf2;
        *buf2=temp;
        buf1++;
        buf2++;
    }
}

void bubble_sort(void *arr,    //冒泡排序
                 size_t num,
                 size_t width,
                 int (*cmp)(const void*e1,const void*e2))
{
    for(int i=0;i<sz-1;i++){
        
        for(int j=0;j<sz-1-i;j++){
            //强制转换为char*再加上宽度,就能找出元素
            if(	cmp((char*)arr+(j)*width,(char*)arr+(j+1)*width)>0 ){	
                //这里>0(升序),<0(降序) 可以改变排序规则实现降序或者升序的排序。
                //但是修改了函数,建议还是在函数外边更改排序逻辑
                Swap((char*)arr+(j)*width,(char*)arr+(j+1)*width,width);
            }
        }
    }
    
}

int comp_int(const void* e1,const void* e2)
{
    return *(int*)e1-*(int*)e2;   //e1-e2>0 符合条件就会排序,把e1的元素往数组后面排
}

int main()
{
	int arr[]={9,8,7,6,5,4,3,2,1};
    int sz=sizeof(arr)/sizeof(arr[0]);
    bubble_sort(arr,sz,sizeof(arr[0]),comp_int)
}

代码解析: 

         首先,因为以后要使用的时候,现在不明确具体的要排序的是什么类型的数据,这里第一个参数,用一个void*arr的指针来接受数组的首地址。因为只有void*类型才能接收各种类型的指针

        因为排序要遍历数组,为了方便遍历数组,这里第二个、第三个参数传入数组的长度和数组元素的宽度

         因为不知道要排序的类型,所以要把排序的规制交给使用者来定义,第四个参数给一个函数指针,int (*cmp)(const void*e1,const void*e2),这里函数指针的参数类型也是不确定的,也要用void*的指针来接收每次排序元素的地址。

        这里使用这个函数来排序一个整形类型的数组,那么既然用的是void*的指针来接收数组元素,那么怎么让每次比较都是一个整形的元素呢?其实这里传入的元素的宽度的就起了作用。

        首先元素的类型我们是不知道的,但是我们知道元素的宽度,那么就可以先把void*的数组arr强制转换成char*类型,因为char*类型的步长固定是一个字节。假设这里排序的int类型的元素,一个int形的元素宽度是4,那么arr每次跳过4个字节就能找到下一个元素,那么 if 的判断 就能写成

cmp((char*)arr+(j)*width,(char*)arr+(j+1)*width);

 这样使用自定义比较规则函数的时候就能找到  元素和下一个元素。

int comp_int(const void* e1,const void* e2)
{
    return *(int*)e1-*(int*)e2;
}

PS:写 比较函数 的时候,元素的类型就确定了。这里使用整形变量,把传过来的char*类型的指针强制转换成int*,再解引用,因为上面传过来的是每个元素的首地址,解引用就能找到整个元素

        然后再进行元素的交换,因为不同的数据类型之间有差异,不能直接通过第三个变量来交换,并且这里也不知道具体要交换的类型。但是如果两个元素,每个字节都做了交换,就相当于这两个元素进行了交换。

        那么就可以声明一个交换函Swap(char *buf1,char* buf2,width),来交换每个元素的字节,并用元素宽度来做判断。

void Swap(char *buf1,char* buf2,width)
{
    for(int i=0;i<width;i++){
        char temp=*buf1;
        *buf1=*buf2;
        *buf2=temp;
        buf1++;
        buf2++;
    }
}

         这里就完成了一个基于冒泡排序算法,可以排序各种类型的排序函数。


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

函数指针和函数指针数组 的相关文章

  • 如何在MVVM中管理多个窗口

    我知道有几个与此类似的问题 但我还没有找到明确的答案 我正在尝试深入研究 MVVM 并尽可能保持纯粹 但不确定如何在坚持模式的同时启动 关闭窗口 我最初的想法是向 ViewModel 发送数据绑定命令 触发代码来启动一个新视图 然后通过 X
  • 检查两个数是否是彼此的排列?

    给定两个数字 a b 使得 1 例如 123 是 312 的有效排列 我也不想对数字中的数字进行排序 如果您指的是数字的字符 例如 1927 和 9721 则 至少 有几种方法 如果允许排序 一种方法是简单地sprintf将它们放入两个缓冲
  • 如何使 Windows 窗体的关闭按钮不关闭窗体但使其不可见?

    该表单有一个 NotifyIcon 对象 当用户单击 关闭 按钮时 我希望表单不关闭而是变得不可见 然后 如果用户想再次查看该表单 可以双击系统托盘中的图标 如果用户想关闭表单 可以右键单击该图标并选择 关闭 有人可以告诉我如何使关闭按钮不
  • 访问私人成员[关闭]

    Closed 这个问题是基于意见的 help closed questions 目前不接受答案 通过将类的私有成员转换为 void 指针 然后转换为结构来访问类的私有成员是否合适 我认为我无权修改包含我需要访问的数据成员的类 如果不道德 我
  • UML类图:抽象方法和属性是这样写的吗?

    当我第一次为一个小型 C 项目创建 uml 类图时 我在属性方面遇到了一些麻烦 最后我只是将属性添加为变量 lt
  • 如何在列表框项目之间画一条线

    我希望能够用水平线分隔列表框中的每个项目 这只是我用于绘制项目的一些代码 private void symptomsList DrawItem object sender System Windows Forms DrawItemEvent
  • C++ 子字符串返回错误结果

    我有这个字符串 std string date 20121020 我正在做 std cout lt lt Date lt lt date lt lt n std cout lt lt Year lt lt date substr 0 4 l
  • 实时服务器上的 woff 字体 MIME 类型错误

    我有一个 asp net MVC 4 网站 我在其中使用 woff 字体 在 VS IIS 上运行时一切正常 然而 当我将 pate 上传到 1and1 托管 实时服务器 时 我得到以下信息 网络错误 404 未找到 http www co
  • 将目录压缩为单个文件的方法有哪些

    不知道怎么问 所以我会解释一下情况 我需要存储一些压缩文件 最初的想法是创建一个文件夹并存储所需数量的压缩文件 并创建一个文件来保存有关每个压缩文件的数据 但是 我不被允许创建许多文件 只能有一个 我决定创建一个压缩文件 其中包含有关进一步
  • Json.NET - 反序列化接口属性引发错误“类型是接口或抽象类,无法实例化”

    我有一个类 其属性是接口 public class Foo public int Number get set public ISomething Thing get set 尝试反序列化Foo使用 Json NET 的类给我一条错误消息
  • C# 中的递归自定义配置

    我正在尝试创建一个遵循以下递归结构的自定义配置部分
  • Discord.net 无法在 Linux 上运行

    我正在尝试让在 Linux VPS 上运行的 Discord net 中编码的不和谐机器人 我通过单声道运行 但我不断收到此错误 Unhandled Exception System Exception Connection lost at
  • 将 unsigned char * (uint8_t *) 转换为 const char *

    我有一个带有 uint8 t 参数的函数 uint8 t ihex decode uint8 t in size t len uint8 t out uint8 t i hn ln for i 0 i lt len i 2 hn in i
  • 将 xml 反序列化为类,list<> 出现问题

    我有以下 XML
  • 插入记录后如何从SQL Server获取Identity值

    我在数据库中添加一条记录identity价值 我想在插入后获取身份值 我不想通过存储过程来做到这一点 这是我的代码 SQLString INSERT INTO myTable SQLString Cal1 Cal2 Cal3 Cal4 SQ
  • C++ fmt 库,仅使用格式说明符格式化单个参数

    使用 C fmt 库 并给定一个裸格式说明符 有没有办法使用它来格式化单个参数 example std string str magic format 2f 1 23 current method template
  • WCF:将随机数添加到 UsernameToken

    我正在尝试连接到用 Java 编写的 Web 服务 但有些东西我无法弄清楚 使用 WCF 和 customBinding 几乎一切似乎都很好 除了 SOAP 消息的一部分 因为它缺少 Nonce 和 Created 部分节点 显然我错过了一
  • 为什么 C# Math.Ceiling 向下舍入?

    我今天过得很艰难 但有些事情不太对劲 在我的 C 代码中 我有这样的内容 Math Ceiling decimal this TotalRecordCount this PageSize Where int TotalRecordCount
  • const、span 和迭代器的问题

    我尝试编写一个按索引迭代容器的迭代器 AIt and a const It两者都允许更改容器的内容 AConst it and a const Const it两者都禁止更改容器的内容 之后 我尝试写一个span
  • C 中的异或运算符

    在进行按位操作时 我在确定何时使用 XOR 运算符时遇到一些困难 按位与和或非常简单 当您想要屏蔽位时 请使用按位 AND 常见用例是 IP 寻址和子网掩码 当您想要打开位时 请使用包含或 然而 XOR 总是让我明白 我觉得如果在面试中被问

随机推荐

  • C和C++安全编码笔记:并发

    并发是一种系统属性 它是指系统中几个计算同时执行 并可能彼此交互 一个并发程序通常使用顺序线程和 或 进程的一些组合来执行计算 其中每个线程和进程执行可以在逻辑上并行执行的计算 这些进程和 或 线程可以在单处理器系统上使用分时抢占式的方式
  • 解决"taglib definition not consistent with specification version"

    1 问题描述 从tomcat 6 迁移到tomcat 7 时 运行web项目时出现 taglib definition not consistent with specification version 从上面可以看出 这是taglib的定
  • 相似性度量的方法分类

    相似性度量的方法分类 一 变换域 DTW ERP都是不设置阈值 直接计算其欧氏距离 EDR LCSS都是设置一个绝对阈值 满足阈值变成0或者1 CATS 设置一个阈值 不满足阈值取0 满足阈值缩放到 0 1 区间中 Frechet 不设置阈
  • Python 实现弹球游戏

    源代码 from tkinter import 来源于python的标准库 GUI import random import time 创建Ball类 对小球进行定义 class Ball def init self canvas padd
  • Sharding-jdbc实现读写分离、分库分表

    一 简介 Sharding jdbc官网 http shardingsphere apache org 1 概述 a Sharding jdbc是一个开源的分布式的关系型数据库中间件 b Sharding jdbc是客户端代理模式 c 定位
  • MySQL数据库-表索引-隐藏和删除索引

    隐藏索引 MySQL8开始支持隐藏索引 隐藏索引提供了更人性化的数据库操作 隐藏索引 顾名思义 让索引暂时不可见 不会被优化器使用 默认情况下索引时可见的 隐藏索引可以用来测试索引的性能 验证索引的必要性时不需要删除索引 可以先将索引隐藏
  • HbuilderX编译运行uni-app项目时报错:HBuilderX 安装目录不能包括 ( 等特殊字符

    HbuilderX编译运行uni app项目时报错 HBuilderX 安装目录不能包括 等特殊字符 这是因为HBuilderX 被安装在了含有 等特殊的目录中 例如 D Program Files x86 HBuilderX 其中有包含了
  • 数字图像处理matlab实践

    目录 一 任务描述 3 二 设计思路 3 三 功能模块 3 1 图像灰度化 3 2 图像二值化 4 3 图像叠加 5 4 图像目标检测 5 5 图像对数变换 6 6 图像指数变换 7 7 图像直方图均衡化 8 8图像添加噪声 平滑处理 9
  • 单链表详解

    目录 一 什么是链表 1 数据结构概要 2 功能 二 实现链表 1 定义一个具有结点特征的结构体 2 定义一个头指针 3 实现尾插 尾插代码实现如下 4 结点的创建 创建节点代码实现 5 实现尾删 尾删代码实现 6 实现头插 头插代码实现
  • xml模式文档(xml:Schema)详解

    XML Schema 是基于 XML 的 DTD 替代者 XML Schema 描述 XML 文档的结构 XML Schema 语言也称作 XML Schema 定义 XML Schema Definition XSD 下面的例子是一个XM
  • EA12 显示用例图中use关系线的箭头

    默认情况下在Enterprise Architect EA 的用例中use关系线是没有箭头指示的 这样有时候看着挺别扭的 不过这个是可以设置的 步骤如下 点击菜单栏TOOLS 点击Options 选择Links 勾选Show Uses ar
  • win10编译 Fast R-CNN 所需的setup.py(rotate) tensorflow版

    问题描述 Fast R CNN rotate 原版提供的 setup py 是在linux中使用的 在linux里可以直接编译 而在windows下需要修改 setup py 解决方案 先提供思路 最后附上代码 1 修改 setup py
  • 5款类蝉妈妈抖音数据工具推荐

    担心蝉妈妈数据不准怎么办 作为一个在电商培训行业摸爬滚打10多年的老兵 大头今天给大家推荐其它五个类似蝉妈妈的抖音数据分析网站 一 飞瓜数据 飞瓜比蝉妈妈做的更早 抖快 B站 小红书 淘宝 微博等主流平台均有产品线 规模很大 飞瓜比较注重产
  • 二叉树的路径之和

    题目 给定一个二叉树与正数sum 找出所有从根节点到叶节点的路径 这些路径上的节点值累加和为sum include
  • 图机器学习课程笔记4

    维生素C吃多了会上火 个人CSDN博文目录 cs224w 图机器学习 2021冬季课程学习笔记集合 目录 1 思维大纲 2 中文笔记 1 思维大纲 2 中文笔记 笔记4 提取码 8888
  • 论文检索号查询

    大家好 在我们学术研究过程中 当你取得一份好的学术成果 成功发表一篇期刊论文后 往往会在不同的场合用到 论文检索号 今天我给大家分享一下什么是论文检索号以及如何查询论文检索号 什么是论文检索号 论文检索号是在论文数据库中检索论文的具有唯一性
  • 查找相似网站的方法和地址

    描述 查找相似网站的方法和地址 网址 https www similarsites com
  • macbook 15 自动重启,一天十几次

    重启后提示消息中包含 GPUPanic cpp 127 google了一下 可能是显卡的问题 macbook 15 2010年版本 机器里面有两个显卡 一个是integrated Intel显卡 一个是NVIDA显卡 mac os在切换显卡
  • python 列表排序方法

    本文将讨论的是 如何将一个字符串组成的列表 比如 abc cba bac 按照特定的条件 比如首字母 尾字母 或者长度 灵活的排序 目录 直接排序 由一些字符串组成的 list sort 方法可以直接用来对字符串排序 a John Smit
  • 函数指针和函数指针数组

    函数指针 指向函数的指针 函数指针数组 一个数组里面存的类型是函数指针 一 函数指针的声明和调用 1 指针声明 函数指针顾名思义就是一个指向函数的指针 int Add int x int y return x y char Sub char