「C语言」指针函数和函数指针

指针函数,顾名思义,是一个函数,只不过这个函数的返回值为一个指针即返回的值为一个地址。

申明格式为:*类型标识符 函数名(参数表)。如:

我习惯把int和*写在一起,其实这样也更便于理解,fun为我们定义的一个函数,可传入两个int型的参数,这个函数的返回值为一个int型的指针。

来看一个demo:(求两个数的和)

sum这个函数的返回值为U8类型的指针即为一个U8类型的地址,c这个内存空间中存放的是输入的两个数的和,p为一个指针,存放的为存放两个数的和的存储空间的地址,将p返回即返回的为一个指针,在主函数中,重新定义的p为一个指针,该指针指向sum函数,*P为该地址中存放的值。

函数指针,顾名思义,这是一个指针,只不过这个指针是一个指向一个函数入口的指针。

声明格式:类型说明符 (*函数名) (参数)。如:

这是一个int型的指针,该指针指向的地址为一个函数的首地址即函数入口地址,该函数有两个形参。这个*是修饰这个函数的,故和fun写在一起,用括号括起来。

来看一个demo:(求两个数的和)

定义一个普通的求和函数add,在主函数里面定义一个函数指针,将该函数指针指向add函数,在传入实参,进行的也是求和运算了。其实函数指针也就是c++里面多态的理论基础了。

简单总结一下函数指针和指针函数:

函数指针:是一个指针,该指针指向函数的入口地址

指针函数:是一个函数,该函数的返回值为指针类型

C语言函数指针的强大及其应用

C语言中的函数指针是一种强大且灵活的特性,它允许程序员将函数作为参数传递给其他函数,或者在运行时动态选择和调用不同的函数。这种能力不仅增强了代码的动态性和可扩展性,还为实现复杂的编程模式提供了可能。本文将深入探讨函数指针的强大之处,并通过具体实例展示其在不同场景下的应用价值。

1. 函数指针的基础概念

在C语言中,函数名实际上是一个指向该函数入口地址的常量指针。因此,我们可以定义一个函数指针变量来存储这个地址,并通过该指针调用相应的函数。例如,定义一个接受两个int参数并返回int结果的函数指针:

这里,(*func_ptr)表示func_ptr是一个指针变量,括号不可或缺,用以清晰界定优先级;int指出所指向函数的返回值类型,紧随其后括号里的int, int则是该函数的参数类型列表。一旦我们有了这样的函数指针,就可以像普通函数一样调用它,例如:

这段代码展示了如何声明、初始化和使用函数指针来调用add函数,完成加法运算。

2. 动态选择与执行

函数指针的最大优势之一在于它能够在运行时动态地选择和执行不同的函数。这使得程序可以根据不同的条件或输入数据灵活调整行为,而无需编写大量的分支语句。例如,在一个简单的计算器程序中,我们可以使用函数指针数组来关联不同的数学运算符与其对应的运算函数:

在这个例子中,op_funcs数组依序存储了加法、减法、乘法、除法的函数指针,通过循环遍历数组,依索引调用不同函数完成对应运算,依操作符输出算式与结果,彰显了函数指针数组灵活编排函数调用顺序、适配多种业务逻辑的优势。

3. 回调机制的应用

回调机制是函数指针的经典应用场景之一,尤其是在库函数和事件驱动编程中。通过将自定义的比较函数作为参数传递给qsort库函数,可以实现对数组元素的个性化排序。qsort内部会适时回调传入的比较函数,依据返回值调整数组元素顺序,从而解耦排序算法与元素比较逻辑,提升代码复用性和扩展性:

在这个例子中,compare函数指针被传递给qsort,库函数内部根据需要调用它来决定数组元素的排序规则。

4. 复杂数据结构的支持

在构建复杂数据结构如链表、树时,函数指针可以巧妙地嵌入节点结构体,赋予节点处理自身数据的定制化行为。例如,在链表节点删除操作中,传统静态实现需在链表操作函数里写死删除逻辑;若用函数指针,则可以让节点结构体“携带”专属删除函数指针,不同类型节点(如存储整数、字符串等)各自定义适配的删除函数,实现差异化内存释放、数据清理,增强数据结构操作的灵活性和专业性。

5. 提升代码的抽象层次

函数指针不仅能够简化代码逻辑,还能提高代码的抽象层次。通过定义通用接口,可以使不同功能的具体实现细节对外部隐藏,只暴露必要的操作方法。这种方式有助于降低模块之间的耦合度,促进代码的模块化设计和维护。例如,在图形用户界面(GUI)开发中,可以通过注册事件处理器的方式来响应用户的交互操作,而无需关心具体的实现细节。

6. 实现多态性

尽管C语言不是面向对象的语言,但通过函数指针可以模拟面向对象编程中的多态特性。例如,在处理不同类型的数据时,可以定义一组虚函数表(vtable),每个类型都有自己的实现版本,当调用这些函数时,实际执行的是对应类型的特定实现。这种方法虽然增加了少量的间接开销,但却极大地提高了代码的灵活性和可扩展性。

7. 函数指针在软件分层设计中的作用

函数指针对于实现软件分层设计至关重要,它可以帮助开发者构建层次清晰、职责分明的系统架构。例如,在操作系统或嵌入式系统的开发中,上层应用程序可能需要调用下层提供的API接口,但同时又希望保持对下层实现细节的隔离。通过使用函数指针作为回调机制,可以在不违反“依赖倒置原则”的前提下,让下层模块调用上层定义的特定功能。这不仅增强了系统的可扩展性,还使得各层之间的耦合度大大降低,便于未来的维护和升级。

实例:操作系统钩子函数

许多现代操作系统提供了所谓的“钩子”(hook)机制,允许开发者注册自定义的事件处理器。当特定事件发生时,系统会自动调用这些处理器以执行相应的操作。例如,在Windows操作系统中,可以通过SetWindowsHookEx函数安装一个键盘或鼠标钩子,拦截并处理用户输入事件。类似地,在Linux内核中,也可以利用函数指针实现类似的钩子功能,从而实现在不影响现有逻辑的情况下添加新的行为。

8. 函数指针在库开发中的重要性

在编写通用库时,函数指针同样发挥着不可忽视的作用。库的设计者往往无法预知所有可能的应用场景,因此他们倾向于提供高度抽象且灵活的接口,让用户能够根据自身需求定制化某些行为。比如,在一个基于链表的数据结构库中,库的作者可以定义一个搜索函数的原型,并允许用户传递具体的比较函数来决定如何匹配目标元素。这种方式不仅简化了库的实现,也为使用者带来了极大的便利。

实例:链表库中的查找函数

9. 函数指针在嵌入式系统中的应用

嵌入式系统通常具有严格的资源限制,因此优化代码效率和减小体积是至关重要的。在这种环境下,函数指针不仅可以帮助减少重复代码量,还可以用来引用那些预先编译并固化在ROM中的系统级函数。例如,微控制器厂商可能会在其产品中内置一些用于Flash存储器管理的功能,如擦除、写入等。由于这些函数已经在硬件层面实现了,直接调用它们将节省宝贵的RAM空间。然而,由于这些函数的具体实现细节对外界保密,程序员只能通过函数指针的方式访问它们。

实例:调用ROM中的Flash擦除函数

10. 表驱动法与函数指针的结合

表驱动法是一种常见的编程技巧,它通过将一组相关联的操作封装在一个数组或其他容器中,然后根据索引或键值来选择执行哪个操作。这种方法不仅可以提高程序的运行效率,还能使代码更加简洁易读。特别是在处理大量相似但又有所区别的任务时,表驱动法的优势尤为明显。例如,在命令行解析器或状态机的设计中,可以创建一个包含多个函数指针的表格,每个条目对应一种可能的状态转换或命令处理逻辑。

实例:命令行解析器

11. 函数指针与泛型编程

尽管C语言本身并不支持泛型编程,但我们仍然可以通过巧妙运用函数指针来实现一定程度上的类型无关性。例如,在实现排序算法时,可以通过传递适当的比较函数来适应不同类型的数据。这样做的好处是可以编写一套通用的排序代码,而不需要为每种数据类型单独实现一遍。此外,还可以利用void *指针来表示任意类型的参数,进一步增强代码的通用性。

实例:通用排序函数

结论

综上所述,C语言中的函数指针不仅是实现动态行为和灵活编程的强大工具,还在促进代码复用、提高系统可扩展性以及简化复杂逻辑方面展现了巨大的潜力。无论是用于构建高效的库函数、实现事件驱动的编程模型,还是优化嵌入式系统的性能,函数指针都能为开发者提供必要的手段来应对各种挑战。掌握好函数指针的使用方法,无疑将极大提升C语言编程的能力,帮助我们写出更加优雅、高效且易于维护的代码。通过上述丰富的实例可以看出,函数指针不仅仅是C语言的一个特性,更是连接理论与实践、概念与应用的重要桥梁,值得每一位C语言开发者深入学习和探索。

「C语言」指针进阶第五站:函数指针

函数也有自己的地址,函数名/&函数名 就是函数的地址

在 数组指针的学习中我们了解到

指针变量pa的类型是int(*)[5]

那么函数指针的形式是怎样的呢?

pt的类型是void (*)(char*)

下面哪个代码有能力存放函数的地址呢?

答:pfun1可以存放

pfun1先和*结合,说明pfun1是指针,指针指向的是一个函数,指向的函数无 参数,返回值类型为void

pfun2先和()结合,判断为一个返回值为int*类型的函数

那么,如何书写一个函数指针呢?

以Add函数为例,它有两个int类型的形参,返回类型是int

所对应的函数指针就是int(*)(int,int)类型

依据以下几步就能正确写出函数指针

(1)确定函数的返回类型

(2)确定函数的参数类型和个数

(3)把函数参数类型里的变量名去掉,放入括号里

(int x,int y)去掉x、y,即(int,int)

(4)在前面加上函数的返回类型

(5)最后加上(*),以及函数指针变量名

需要注意的是,(*pf)的括号不能省略,否则编译器会报错

去掉括号之后就相当于函数声明,无法赋值

如下图所示,当我们定义了一个函数指针后

就可以通过指针来访问原函数

这时候(*pf)其实就相当于my_test

我们可以通过函数指针来调用上面提到过的Add函数

可以看到,sum和sum1两种形式都正确调用了该函数

因为我们已经把Add的地址转给了pf指针,函数名Add和指针pf实际上是等价的

所以在使用函数指针的时候,可以不带*使用。但是带*的时候一定要加括号!

奇葩代码1

这里的0仅为示例,我们在正常使用的时候并不能访问0的地址

看到这个代码的时候,是不是有点懵?

别急,让我们来慢慢分析一波!

奇葩代码2

说人话就是,signal函数内传入了一个void(*)(int)的函数指针,返回值也是一个void(*)(int)的函数指针!

“这个代码2是真的奇葩,就没有什么办法把他变成人话吗?(简化一下)”

当然有!那就是用typedef函数来给void(*)(int)指针起一个新名字!

这样我们的代码就能得到简化

这样是不是就更容易分辨了?

既然函数指针也是一个指针类型,那我们就可以用指针数组来存放它

前提:这些函数的参数类型、返回类型一致

相比于分开写多次函数调用

函数指针数组可以让我们以使用数组的形式来访问每个函数

这样也简化了我们的代码

目的:实现一个计算器

菜单:用数字来选择运算类型

方法:以switch/case语句来实现函数调用

结束:用do/while实现多组输入,以及结束程序

这种方式需要写非常多的重复代码,而且代码长度很长????

我们可以使用函数指针对它进行优化

这样就避免了我们在每个case语句里都写上输入提示、scanf和不同的函数调用所导致的代码冗余了

运行试试吧!

函数指针数组是一个数组,数组可以用数组指针来存放地址

指向函数指针数组的指针:是一个指针

该指针指向一个数组,数组的每个元素都是一个函数指针

定义一个函数指针,指向的函数有两个int形参并且返回一个函数指针,返回的指针指向一个有一个int形参且返回int的函数?下面哪个是正确的?

一步步分析题目的要求

该函数指针指向的函数有两个int类型,即(int,int),ABCD都有,无法排除

仔细看看,D的类型没有写全,直接排除

返回一个函数指针,该指针指向一个有一个int形参且返回int的函数

B是一个函数指针,返回类型是int,错误

C的返回值是int*类型,错误

A选项去掉函数指针F后,剩下int (*)(int),符合题意

你学废了吗?

———————————–

为了帮助大家,轻松,高效学习C语言/C++,给大家分享我收集的资源,从最零基础开始的,帮助大家在学习C语言的道路上披荆斩棘!

编程学习书籍分享:

编程学习视频分享:

整理分享(多年学习的源码、项目实战视频、项目笔记,基础入门教程)

对于C/C++感兴趣可以关注小编在后台私信我:【编程交流】一起来学习哦!可以领取一些C/C++的项目学习视频资料哦!已经设置好了关键词自动回复,自动领取就好了!

本文作者及来源:Renderbus瑞云渲染农场https://www.renderbus.com

点赞 0
收藏 0

文章为作者独立观点不代本网立场,未经允许不得转载。