百度360必应搜狗淘宝本站头条
当前位置:网站首页 > 技术分析 > 正文

C语言 - 指针之函数指针

liebian365 2025-03-03 19:16 12 浏览 0 评论

C语言-指针之函数指针

函数指针是 C 语言中一种非常强大且独特的特性,它体现了 C 语言的灵活性和底层控制能力。要理解函数指针的精华,我们需要从定义入手,再深入探讨其应用,并最终与其他语言的特性进行对比。

1. 函数指针的定义

在 C 语言中,函数 本身也会被加载到内存中,占据一块地址空间。函数名 本质上就代表了该函数在内存中的 入口地址,类似于数组名代表数组首元素的地址。而 函数指针,顾名思义,就是 指向函数指针变量

详细解释:

  • 数据指针 vs. 函数指针: 我们通常接触的数据指针,例如 int *p,指向的是存储数据的内存位置。而函数指针则不同,它指向的是存储 指令代码 的内存位置,即函数的代码段的起始地址。
  • 函数类型: 每个函数都有其特定的类型,由 返回值类型参数列表 共同决定。 函数指针的类型必须与其指向的函数类型 严格匹配
  • 声明函数指针: 声明函数指针的语法稍有特殊:
  • 返回值类型 (*指针变量名)(参数列表);
  • 例如:
  • int (*pFunc)(int, int);
    • int: 表示 pFunc 指针指向的函数返回值类型为 int
    • (*pFunc): * 表明 pFunc 是一个指针, () 表示它是一个指针,指向的是一个函数, pFunc 是指针变量名。
    • (int, int): 表示 pFunc 指针指向的函数接受两个 int 类型的参数。
  • 赋值函数指针: 函数指针变量需要赋值才能指向具体的函数。可以直接将 函数名 赋值给函数指针变量(函数名会被隐式转换为函数指针):
  • int add(int a, int b) {
    return a + b;
    }
    int main() {
    int (*pFunc)(int, int); // 声明函数指针
    pFunc = add; // 将 add 函数的地址赋值给 pFunc
    // ...
    return 0;
    }

2. 函数指针的惯用法

函数指针在 C 语言中应用非常广泛,主要体现在以下几个方面:

2.1 回调函数 (Callback Functions)

回调函数是函数指针最经典、最重要的应用场景之一。它允许我们将 函数作为参数 传递给另一个函数,在特定的时刻或条件满足时,被调函数可以 “回调” 执行作为参数传入的函数。

应用场景:

  • 事件处理: 在 GUI 编程、操作系统内核事件处理等场景中,当特定事件发生时(例如鼠标点击、按键按下),系统会回调预先注册好的处理函数。
  • 排序算法: 通用的排序函数(如 qsort)可以接受一个比较函数作为参数,用于自定义排序规则。
  • 异步操作: 在异步编程中,当异步操作完成时,通过回调函数通知程序处理结果。
  • 策略模式: 在设计模式中,可以使用函数指针实现策略模式,动态切换不同的算法或策略。

代码示例 (回调函数 - 排序):

 #include 
 #include 
 
 // 比较函数类型定义 (用于 qsort)
 typedef int (*CompareFunc)(const void *, const void *);
 
 // 升序比较函数
 int compareAscending(const void *a, const void *b) {
     return (*(int *)a - *(int *)b);
 }
 
 // 降序比较函数
 int compareDescending(const void *a, const void *b) {
     return (*(int *)b - *(int *)a);
 }
 
 int main() {
     int numbers[] = {5, 2, 8, 1, 9, 4};
     int size = sizeof(numbers) / sizeof(numbers[0]);
 
     printf("排序前: ");
     for (int i = 0; i < size; i++) {
         printf("%d ", numbers[i]);
     }
     printf("\n");
 
     // 使用 qsort 和升序比较函数进行排序
     qsort(numbers, size, sizeof(int), compareAscending);
     printf("升序排序后: ");
     for (int i = 0; i < size; i++) {
         printf("%d ", numbers[i]);
     }
     printf("\n");
 
     // 使用 qsort 和降序比较函数进行排序
     qsort(numbers, size, sizeof(int), compareDescending);
     printf("降序排序后: ");
     for (int i = 0; i < size; i++) {
         printf("%d ", numbers[i]);
     }
     printf("\n");
 
     return 0;
 }

代码解释:

  • typedef int (*CompareFunc)(const void *, const void *); 定义了一个函数指针类型 CompareFunc,用于表示比较函数的类型。
  • compareAscendingcompareDescending 是两个具体的比较函数,分别实现了升序和降序的比较逻辑。
  • qsort 函数是 C 标准库提供的通用排序函数,它的第四个参数就接受一个 CompareFunc 类型的函数指针,用于指定排序规则。
  • main 函数中,我们分别将 compareAscendingcompareDescending 函数的函数名(即函数指针)传递给 qsort 函数,实现了动态选择排序规则的效果。

2.2 函数表 (Jump Table / Dispatch Table)

函数表是一个 函数指针数组,它将多个函数指针存储在一个数组中。通过索引访问函数表,可以实现 高效地根据条件调用不同的函数,避免使用大量的 if-elseswitch-case 语句,提高代码的效率和可维护性。

应用场景:

  • 命令解析器: 根据不同的命令字符串或命令编号,从函数表中查找并调用相应的处理函数。
  • 状态机: 在状态机中,可以根据当前状态,从函数表中选择并执行相应的状态处理函数。
  • 设备驱动: 设备驱动程序可以使用函数表来管理不同设备的 IO 操作函数。

代码示例 (函数表 - 简易计算器):

 #include 
 #include 
 
 // 定义函数指针类型:接受两个 int 参数,返回 int 结果
 typedef int (*Operation)(int, int);
 
 // 加法函数
 int add(int a, int b) { return a + b; }
 
 // 减法函数
 int subtract(int a, int b) { return a - b; }
 
 // 乘法函数
 int multiply(int a, int b) { return a * b; }
 
 // 除法函数
 int divide(int a, int b) {
     if (b == 0) {
         fprintf(stderr, "Error: Division by zero!\n");
         return 0; // 错误处理,避免程序崩溃
     }
     return a / b;
 }
 
 int main() {
     // 定义函数指针数组 (函数表)
     Operation operations[4] = {add, subtract, multiply, divide};
     char operators[] = {'+', '-', '*', '/'};
 
     int num1, num2;
     int choice;
 
     printf("简易计算器:\n");
     printf("请输入两个整数: ");
     if (scanf("%d %d", &num1, &num2) != 2) {
         fprintf(stderr, "输入错误!\n");
         return 1;
     }
 
     printf("选择运算符 (0:+, 1:-, 2:*, 3:/): ");
     if (scanf("%d", &choice) != 1 || choice < 0 || choice > 3) {
         fprintf(stderr, "运算符选择错误!\n");
         return 1;
     }
 
     if (choice >= 0 && choice <= 3) {
         int result = operations[choice](num1, num2); // 通过函数表调用函数
         printf("%d %c %d = %d\n", num1, operators[choice], num2, result);
     }
 
     return 0;
 }

代码解释:

  • Operation operations[4] = {add, subtract, multiply, divide}; 定义了一个 Operation 类型的函数指针数组 operations,并将 add, subtract, multiply, divide 四个函数的函数名(函数指针)初始化到数组中,构建了函数表。
  • 通过用户输入的 choice 变量作为索引,可以直接访问函数表 operations[choice],获取对应的函数指针,并调用 operations[choice](num1, num2) 执行相应的运算。
  • 使用函数表,可以简洁高效地实现根据不同选择调用不同函数的功能,代码结构更清晰,易于扩展和维护。

2.3 用于实现抽象数据类型和面向对象编程的某些特性 (在 C 语言中)

虽然 C 语言本身不是面向对象编程语言,但通过结合 结构体函数指针,可以在 C 语言中模拟实现一些面向对象编程的特性,例如 抽象数据类型 (ADT)多态性

应用场景:

  • 抽象数据类型 (ADT) 实现: 可以使用结构体封装数据和操作这些数据的函数指针,将数据和操作绑定在一起,实现数据抽象和封装。
  • 模拟多态性: 通过在结构体中定义函数指针,并根据不同的对象或条件指向不同的函数实现,可以模拟实现多态的行为。

代码示例 (ADT - 模拟简单的“形状”抽象):

 #include 
 #include 
 
 // 定义函数指针类型:计算面积
 typedef double (*AreaFunction)(void *);
 
 // 定义结构体类型:Shape (抽象形状)
 typedef struct Shape {
     char name[20];
     AreaFunction getArea; // 函数指针:计算面积
 } Shape;
 
 // 圆形结构体
 typedef struct Circle {
     Shape base; // 继承 Shape 结构体 (模拟继承)
     double radius;
 } Circle;
 
 // 矩形结构体
 typedef struct Rectangle {
     Shape base; // 继承 Shape 结构体 (模拟继承)
     double width;
     double height;
 } Rectangle;
 
 // 计算圆形面积的函数
 double circleArea(void *shape) {
     Circle *circle = (Circle *)shape;
     return M_PI * circle->radius * circle->radius;
 }
 
 // 计算矩形面积的函数
 double rectangleArea(void *shape) {
     Rectangle *rectangle = (Rectangle *)shape;
     return rectangle->width * rectangle->height;
 }
 
 // 创建圆形对象
 Circle* createCircle(double radius) {
     Circle *circle = (Circle*)malloc(sizeof(Circle));
     if (circle == NULL) return NULL;
     strcpy(circle->base.name, "Circle");
     circle->base.getArea = circleArea; // 初始化函数指针
     circle->radius = radius;
     return circle;
 }
 
 // 创建矩形对象
 Rectangle* createRectangle(double width, double height) {
     Rectangle *rectangle = (Rectangle*)malloc(sizeof(Rectangle));
     if (rectangle == NULL) return NULL;
     strcpy(rectangle->base.name, "Rectangle");
     rectangle->base.getArea = rectangleArea; // 初始化函数指针
     rectangle->width = width;
     rectangle->height = height;
     return rectangle;
 }
 
 int main() {
     Shape *shapes[2]; // 形状指针数组
 
     shapes[0] = (Shape*)createCircle(5.0);
     shapes[1] = (Shape*)createRectangle(4.0, 6.0);
 
     for (int i = 0; i < 2; i++) {
         printf("Shape: %s, Area: %.2f\n", shapes[i]->name, shapes[i]->getArea(shapes[i])); // 通过函数指针调用
     }
 
     free(shapes[0]);
     free(shapes[1]);
 
     return 0;
 }

代码解释:

  • 定义了 Shape 结构体作为抽象基类,包含 name 成员和 getArea 函数指针成员。
  • CircleRectangle 结构体 “继承” Shape 结构体(通过结构体嵌套模拟继承),并分别添加了圆形和矩形特有的属性 (radius, width, height)。
  • circleArearectangleArea 是计算圆形和矩形面积的具体函数,它们的函数指针被赋值给 CircleRectangle 结构体实例的 getArea 成员。
  • main 函数中,创建了 Shape 指针数组,存储了 CircleRectangle 对象的指针。
  • 通过 shapes[i]->getArea(shapes[i]),使用函数指针 getArea 调用了不同形状对象的面积计算函数,实现了多态的效果。

3. C 语言函数指针的独特特点与精华

与其他编程语言相比,C 语言的函数指针具有以下无可替代的特点和精华:

  • 极致的底层控制力: C 语言函数指针直接操作 内存地址,这与 C 语言的整体设计哲学一脉相承,即提供对硬件和内存的 最大程度的控制。 这使得 C 语言在系统编程、嵌入式开发等对性能和底层控制要求极高的领域具有无可比拟的优势。 其他高级语言,例如 Java、Python 等,虽然也可能有所谓的“回调”机制,但通常是基于更高级的抽象,底层实现和控制力远不如 C 语言的函数指针直接和强大。
  • 高效和灵活: 函数指针的使用可以 避免大量的条件判断语句,通过函数表等方式实现高效的分发和调用,提高代码的运行效率。同时,函数指针又非常灵活,可以在运行时 动态地改变函数的行为,实现高度定制化的功能。
  • 与 C 语言的精髓完美契合: 函数指针是 C 语言 类型系统指针机制 的完美结合,充分体现了 C 语言的精髓:
    • 类型系统: C 语言是强类型语言,函数指针的类型定义强调类型匹配,保证了类型安全。
    • 指针机制: 函数指针充分利用了指针的灵活性和效率,实现了对函数代码段地址的直接操作。
  • 更接近硬件和底层: 函数指针的概念和使用方式,与计算机 底层的函数调用机制 非常接近。在汇编语言层面,函数调用本质上就是跳转到函数的入口地址执行代码。C 语言的函数指针是对这种底层机制的一种高级抽象,但仍然保留了对底层操作的直接性。

与其他语言的对比:

  • C++: C++ 虽然也支持函数指针(C++ 中称为 函数指针),但更多地使用 函数对象 (functors)lambda 表达式 来实现类似的回调和策略模式。C++ 的函数对象和 lambda 表达式提供了更强大的功能和类型安全性,但也牺牲了一些 C 语言函数指针的直接性和底层控制力。 C++ 中也存在成员函数指针,用于指向类的成员函数,进一步增加了复杂性。
  • Java, Python, JavaScript 等高级语言: 这些高级语言通常没有像 C 语言那样直接的函数指针概念。它们通常使用 接口 (Interfaces)委托 (Delegates) (C#) 、 闭包 (Closures)lambda 表达式 等机制来实现类似的功能,例如回调、事件处理等。 这些机制更加高级、抽象,易于使用,但也隐藏了底层的实现细节,不如 C 语言函数指针那样直接和透明。 这些语言更侧重于 面向对象函数式编程 的范式,函数指针在它们的语言设计哲学中并不占据核心地位。

总结:C 语言函数指针的精华

C 语言的函数指针是其语言精华的重要组成部分,它体现了 C 语言的以下特点:

  • 强大而灵活: 函数指针赋予了 C 语言极高的灵活性和动态性,能够实现各种复杂的设计模式和编程技巧。
  • 高效而底层: 函数指针直接操作内存地址,效率极高,并且能够进行底层硬件编程。
  • 精巧而内敛: 函数指针的语法简洁,但功能强大,与 C 语言整体的简洁、高效的设计风格一致。
  • 理解计算机底层运作的关键: 学习和掌握函数指针,有助于深入理解计算机的函数调用机制、内存管理以及程序运行的本质。

掌握函数指针,是深入理解 C 语言,并充分发挥 C 语言强大能力的关键一步。 虽然函数指针的概念相对抽象,但只要认真学习、多加实践,一定能够领悟其精髓,并在实际编程中灵活运用。

参考

相关推荐

“版本末期”了?下周平衡补丁!国服最强5套牌!上分首选

明天,酒馆战棋就将迎来大更新,也聊了很多天战棋相关的内容了,趁此机会,给兄弟们穿插一篇构筑模式的卡组推荐!老规矩,我们先来看10职业胜率。目前10职业胜率排名与一周前基本类似,没有太多的变化。平衡补丁...

VS2017 C++ 程序报错“error C2065:“M_PI”: 未声明的标识符&quot;

首先,程序中头文件的选择,要选择头文件,在文件中是没有对M_PI的定义的。选择:项目——>”XXX属性"——>配置属性——>C/C++——>预处理器——>预处理器定义,...

东营交警实名曝光一批酒驾人员名单 88人受处罚

齐鲁网·闪电新闻5月24日讯酒后驾驶是对自己和他人生命安全极不负责的行为,为守护大家的平安出行路,东营交警一直将酒驾作为重点打击对象。5月23日,东营交警公布最新一批饮酒、醉酒名单。对以下驾驶人醉酒...

Qt界面——搭配QCustomPlot(qt platform)

这是我第一个使用QCustomPlot控件的上位机,通过串口精确的5ms发送一次数据,再将读取的数据绘制到图表中。界面方面,尝试卡片式设计,外加QSS简单的配了个色。QCustomPlot官网:Qt...

大话西游2分享赢取种族坐骑手办!PK趣闻录由你书写

老友相聚,仗剑江湖!《大话西游2》2021全民PK季4月激燃打响,各PK玩法鏖战齐开,零门槛参与热情高涨。PK季期间,不仅各种玩法奖励丰厚,参与PK趣闻录活动,投稿自己在PK季遇到的趣事,还有机会带走...

测试谷歌VS Code AI 编程插件 Gemini Code Assist

用ClaudeSonnet3.7的天气测试编码,让谷歌VSCodeAI编程插件GeminiCodeAssist自动编程。生成的文件在浏览器中的效果如下:(附源代码)VSCode...

顾爷想知道第4.5期 国服便利性到底需优化啥?

前段时间DNF国服推出了名为“阿拉德B计划”的系列改版计划,截至目前我们已经看到了两项实装。不过关于便利性上,国服似乎还有很多路要走。自从顾爷回归DNF以来,几乎每天都在跟我抱怨关于DNF里面各种各样...

掌握Visual Studio项目配置【基础篇】

1.前言VisualStudio是Windows上最常用的C++集成开发环境之一,简称VS。VS功能十分强大,对应的,其配置系统较为复杂。不管是对于初学者还是有一定开发经验的开发者来说,捋清楚VS...

还嫌LED驱动设计套路深?那就来看看这篇文章吧

随着LED在各个领域的不同应用需求,LED驱动电路也在不断进步和发展。本文从LED的特性入手,推导出适合LED的电源驱动类型,再进一步介绍各类LED驱动设计。设计必读:LED四个关键特性特性一:非线...

Visual Studio Community 2022(VS2022)安装图文方法

直接上步骤:1,首先可以下载安装一个VisualStudio安装器,叫做VisualStudioinstaller。这个安装文件很小,很快就安装完成了。2,打开VisualStudioins...

Qt添加MSVC构建套件的方法(qt添加c++11)

前言有些时候,在Windows下因为某些需求需要使用MSVC编译器对程序进行编译,假设我们安装Qt的时候又只是安装了MingW构建套件,那么此时我们该如何给现有的Qt添加一个MSVC构建套件呢?本文以...

Qt为什么站稳c++GUI的top1(qt c)

为什么现在QT越来越成为c++界面编程的第一选择,从事QT编程多年,在这之前做C++界面都是基于MFC。当时为什么会从MFC转到QT?主要原因是MFC开发界面想做得好看一些十分困难,引用第三方基于MF...

qt开发IDE应该选择VS还是qt creator

如果一个公司选择了qt来开发自己的产品,在面临IDE的选择时会出现vs或者qtcreator,选择qt的IDE需要结合产品需求、部署平台、项目定位、程序猿本身和公司战略,因为大的软件产品需要明确IDE...

Qt 5.14.2超详细安装教程,不会来打我

Qt简介Qt(官方发音[kju:t],音同cute)是一个跨平台的C++开库,主要用来开发图形用户界面(GraphicalUserInterface,GUI)程序。Qt是纯C++开...

Cygwin配置与使用(四)——VI字体和颜色的配置

简介:VI的操作模式,基本上VI可以分为三种状态,分别是命令模式(commandmode)、插入模式(Insertmode)和底行模式(lastlinemode),各模式的功能区分如下:1)...

取消回复欢迎 发表评论: