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

C语言 - 指针之函数指针_c语言中指针函数

haoteby 2025-02-21 13:26 34 浏览

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 语言强大能力的关键一步。 虽然函数指针的概念相对抽象,但只要认真学习、多加实践,一定能够领悟其精髓,并在实际编程中灵活运用。

参考

相关推荐

一日一技:用Python程序将十进制转换为二进制

用Python程序将十进制转换为二进制通过将数字连续除以2并以相反顺序打印其余部分,将十进制数转换为二进制。在下面的程序中,我们将学习使用递归函数将十进制数转换为二进制数,代码如下:...

十进制转化成二进制你会吗?#数学思维

六年级奥赛起跑线:抽屉原理揭秘。同学们好,我是你们的奥耀老师。今天一起来学习奥赛起跑线第三讲二进制计数法。例一:把十进制五十三化成二进制数是多少?首先十进制就是满十进一,二进制就是满二进一。二进制每个...

二进制、十进制、八进制和十六进制,它们之间是如何转换的?

在学习进制时总会遇到多种进制转换的时候,学会它们之间的转换方法也是必须的,这里分享一下几种进制之间转换的方法,也分享两个好用的转换工具,使用它们能够大幅度的提升你的办公和学习效率,感兴趣的小伙伴记得点...

c语言-2进制转10进制_c语言 二进制转十进制

#include<stdio.h>intmain(){charch;inta=0;...

二进制、八进制、十进制和十六进制数制转换

一、数制1、什么是数制数制是计数进位的简称。也就是由低位向高位进位计数的方法。2、常用数制计算机中常用的数制有二进制、八进制、十进制和十六进制。...

二进制、十进制、八进制、十六进制间的相互转换函数

二进制、十进制、八进制、十六进制间的相互转换函数1、输入任意一个十进制的整数,将其分别转换为二进制、八进制、十六进制。2、程序代码如下:#include<iostream>usingna...

二进制、八进制、十进制和十六进制等常用数制及其相互转换

从大学开始系统的接触计算机专业,到现在已经过去十几年了,今天整理一下基础的进制转换,希望给还在上高中的表妹一个入门的引导,早日熟悉这个行业。一、二进制、八进制、十进制和十六进制是如何定义的?二进制是B...

二进制如何转换成十进制?_二进制如何转换成十进制例子图解

随着社会的发展,电器维修由继电器时代逐渐被PLC,变频器,触摸屏等工控时代所替代,特别是plc编程,其数据逻辑往往涉及到数制二进制,那么二进制到底是什么呢?它和十进制又有什么区别和联系呢?下面和朋友们...

二进制与十进制的相互转换_二进制和十进制之间转换

很多同学在刚开始接触计算机语言的时候,都会了解计算机的世界里面大多都是二进制来表达现实世界的任何事物的。当然现实世界的事务有很多很多,就拿最简单的数字,我们经常看到的数字大多都是十进制的形式,例如:我...

十进制如何转换为二进制,二进制如何转换为十进制

用十进制除以2,除的断的,商用0表示;除不断的,商用1表示余0时结束假如十进制用X表示,用十进制除以2,即x/2除以2后为整数的(除的断的),商用0表示;除以2除不断的,商用1表示除完后的商0或1...

十进制数如何转换为二进制数_十进制数如何转换为二进制数举例说明

我们经常听到十进制数和二进制数,电脑中也经常使用二进制数来进行计算,但是很多人却不清楚十进制数和二进制数是怎样进行转换的,下面就来看看,十进制数转换为二进制数的方法。正整数转二进制...

二进制转化为十进制,你会做吗?一起来试试吧

今天孩子问把二进制表示的110101改写成十进制数怎么做呀?,“二进制”简单来说就是“满二进一”,只用0和1共两个数字表示,同理我们平常接触到的“十进制”是“满十进一”,只用0-9共十个数字表示。如果...

Mac终于能正常打游戏了!苹果正逐渐淘汰Rosetta转译

Mac玩家苦转译久矣!WWDC2025苹果正式宣判Rosetta死刑,原生游戏时代终于杀到。Metal4光追和AI插帧技术直接掀桌,连Steam都连夜扛着ARM架构投诚了。看到《赛博朋克2077》...

怎么把视频的声音提出来转为音频?音频提取,11款工具实测搞定

想把视频里的声音单独保存为音频文件(MP3/AAC/WAV/FLAC)用于配音、播客、听课或二次剪辑?本文挑出10款常用工具,给出实测可复现的操作步骤、优缺点和场景推荐。1)转换猫mp3转换器(操作门...

6个mp4格式转换器测评:转换速度与质量并存!

MP4视频格式具有兼容性强、视频画质高清、文件体积较小、支持多种编码等特点,适用于网络媒体传播。如果大家想要将非MP4格式的视频转换成MP4的视频格式的话,可以使用MP4格式转换器更换格式。本文分别从...