18. 函数与指针的强强联合

前面我们已经分别征服了函数(第十二篇)和指针(第十五、十六篇)。当时我还卖了个关子——函数本身也能被指针指向,而且这种“函数指针”能让你写出高度灵活、可插拔的代码。今天,我们就来把这最后一块拼图补上。

这篇文章会让你理解:如何把函数当作参数传给另一个函数(这正是回调的底层原理),如何用函数指针实现策略模式,以及那个让人头疼的“信号函数”声明到底怎么读。学完之后,你看很多 C 库的源码(比如 qsort、信号处理)就不会再一头雾水。


一、函数也有地址:指向函数的指针

和数组一样,函数在内存中也是一段连续的二进制指令,自然也有起始地址。函数名在表达式中会被转换成这个地址——就像数组名被转换成首元素地址一样。

#include <stdio.h>

int add(int a, int b) {
    return a + b;
}

int main(void) {
    printf("%p\n", (void*)add);   // 打印函数地址
    return 0;
}

既然函数有地址,就可以用指针来存它。这就是函数指针

声明函数指针

函数指针的声明格式,就是把函数声明中的函数名换成 (*指针名)

// 普通函数声明
int add(int a, int b);

// 对应的函数指针声明
int (*func_ptr)(int a, int b);   // func_ptr 是指向“返回int、接收两个int”函数的指针

读法:func_ptr 是一个指针,指向一个接收两个 int、返回 int 的函数。

让指针指向函数并调用:

int (*fp)(int, int);   // 声明
fp = add;               // 赋值(add 和 &add 等价)
// 或 fp = &add;

int result = fp(3, 5);  // 通过指针调用,等价于 add(3, 5)
printf("%d\n", result); // 8

函数指针可以像普通函数一样直接加括号调用,fp(3,5)(*fp)(3,5) 都可以。


二、函数指针作为参数:回调函数

函数指针真正的威力在于:把函数当作参数传给另一个函数。这样,被调用的函数可以在适当的时候“回调”你传进去的函数。这在需要通用化操作时极其有用。

回想第十篇,我们写过冒泡排序,但那个排序只能排 int,而且只能升序。如果我想让它能排 double,或者能由用户指定升序还是降序,就要用函数指针。

模拟 qsort 的冒泡排序:

#include <stdio.h>
#include <stdbool.h>

typedef bool (*compare_fn)(int, int);   // 用 typedef 简化类型名

bool ascending(int a, int b)  { return a > b; }   // a>b 说明需要交换
bool descending(int a, int b) { return a < b; }

void bubble_sort(int arr[], int n, compare_fn cmp) {
    for (int i = 0; i < n - 1; i++) {
        bool swapped = false;
        for (int j = 0; j < n - 1 - i; j++) {
            if (cmp(arr[j], arr[j+1])) {   // 调用回调函数决定是否交换
                int temp = arr[j];
                arr[j] = arr[j+1];
                arr[j+1] = temp;
                swapped = true;
            }
        }
        if (!swapped) break;
    }
}

int main(void) {
    int arr[] = {5, 2, 8, 1, 9};
    int n = sizeof(arr) / sizeof(arr[0]);

    bubble_sort(arr, n, ascending);
    printf("升序: ");
    for (int i = 0; i < n; i++) printf("%d ", arr[i]);
    printf("\n");

    bubble_sort(arr, n, descending);
    printf("降序: ");
    for (int i = 0; i < n; i++) printf("%d ", arr[i]);
    printf("\n");

    return 0;
}

输出:

升序: 1 2 5 8 9 
降序: 9 8 5 2 1 

同一个 bubble_sort,通过传入不同的比较函数,就能改变排序行为。这就是策略模式在 C 语言里的实现——函数指针就是策略的载体。

标准库的 qsort 正是这样做的:

void qsort(void *base, size_t nmemb, size_t size,
           int (*compar)(const void *, const void *));

它使用 void* 实现通用类型,再配合函数指针完成比较操作,是函数指针最经典的应用。


三、typedef 简化函数指针类型

函数指针的语法写起来有点啰嗦,每次写 int (*)(int, int) 容易出错。typedef 是解决这个问题的利器

typedef int (*operation_fn)(int, int);  // operation_fn 是一个类型名

operation_fn fp = add;   // 简洁!

这比 int (*fp)(int, int) = add; 好读得多。以后凡是见到 typedef 返回类型 (*类型名)(参数列表); 的写法,就知道是在给函数指针类型取别名。

再看一个更直观的例子——简单计算器:

#include <stdio.h>

typedef int (*calc_fn)(int, int);

int add(int a, int b) { return a + b; }
int sub(int a, int b) { return a - b; }
int mul(int a, int b) { return a * b; }
int div(int a, int b) { return (b != 0) ? a / b : 0; }

int calculate(int x, int y, calc_fn op) {
    return op(x, y);
}

int main(void) {
    printf("10 + 5 = %d\n", calculate(10, 5, add));
    printf("10 - 5 = %d\n", calculate(10, 5, sub));
    printf("10 * 5 = %d\n", calculate(10, 5, mul));
    printf("10 / 5 = %d\n", calculate(10, 5, div));
    return 0;
}

calculate 完全不知道具体运算是什么,它只负责在合适的时机调用传入的函数指针。核心逻辑和具体操作彻底解耦。


四、函数指针数组:函数表的雏形

如果有一组同类型的函数,可以放进函数指针数组,用下标来选择执行哪个。这在实现菜单驱动、状态机、命令解析时非常常见。

#include <stdio.h>

typedef void (*command_fn)(void);

void cmd_new(void)   { printf("新建文件\n"); }
void cmd_open(void)  { printf("打开文件\n"); }
void cmd_save(void)  { printf("保存文件\n"); }
void cmd_quit(void)  { printf("退出程序\n"); }

int main(void) {
    command_fn menu[] = {cmd_new, cmd_open, cmd_save, cmd_quit};
    int choice;
    printf("0-新建 1-打开 2-保存 3-退出\n请输入选择: ");
    scanf("%d", &choice);

    if (choice >= 0 && choice <= 3) {
        menu[choice]();   // 通过下标调用对应的函数
    } else {
        printf("无效选择\n");
    }
    return 0;
}

这种“函数表”的设计,让添加新命令变得极为简单——只需写新函数,再把它加入数组即可,不用改任何 if-elseswitch 逻辑。


五、常见错误与陷阱

1. 函数指针类型不匹配

int add(int a, int b) { return a + b; }
double (*fp)(double, double) = add;  // 错误!返回类型和参数类型都不匹配

函数指针赋值时,类型必须完全一致。如果确实需要转换,必须显式强制转换(且后果自负)。

2. 忘记写括号

int *fp(int, int);        // 这是函数声明,返回 int*,不是函数指针!
int (*fp)(int, int);      // 这才是函数指针

括号是函数指针声明的灵魂,丢了它语义就彻底变了。

3. 对函数指针使用 sizeof

sizeof(add);   // 非法!不能对函数名使用 sizeof

sizeof 只能用于对象类型,不能用于函数。

4. 解引用函数指针时的困惑

int (*fp)(int, int) = add;
fp(3, 4);      // ✅ 可以直接调用
(*fp)(3, 4);   // ✅ 也可以解引用后调用
(**fp)(3, 4);  // ✅ 甚至这样也行!

因为函数名本身就是地址,解引用后还是函数类型,又会自动转回地址。所以怎么写都能跑。不用纠结形式,直接用指针名加括号调用最简洁。


六、小结与下篇预告

今天我们把函数和指针结合了起来。核心收获:

  • 函数在内存中有地址,函数名就是地址。
  • 函数指针声明格式:返回类型 (*指针名)(参数类型列表);
  • typedef 简化函数指针类型,告别复杂声明。
  • 函数指针作为参数,实现回调,是策略模式和解耦的利器。
  • 函数指针数组可以用来构建简单的函数表,避免冗长的条件分支。

理解函数指针,你就推开了通往高阶 C 编程的一扇大门。标准库的 qsortbsearchsignal 都依赖它,大型项目中的插件架构、中断向量表也离不开它。

到目前为止,我们所有的数据——数组也好、字符串也罢——都是在编译时就定好大小的。但如果程序运行时才知道需要多少内存呢?比如用户的输入长度不确定,或者要根据数据库查询结果动态创建数据结构。这就是下一篇的主题:动态内存分配mallocfreecallocrealloc,以及堆与栈的根本区别。那是 C 语言又一块充满力量与危险的领域,准备好你的指针知识,我们将长驱直入。


课后小练习

  1. 写一个函数 apply(int arr[], int n, int (*fn)(int)),对数组的每个元素调用 fn,将返回值写回原位置。测试:写一个 square 函数,将数组元素变成它的平方。
  2. 用函数指针数组实现一个简单的加减乘除计算器,用户输入两个数和运算符(+、-、*、/),程序通过查表调用对应函数输出结果。
  3. 解释以下声明的含义:
    int  (*p1)(int, int);
    int  *p2(int, int);
    int  (*p3[4])(int, int);
    int  (**p4)(int, int);
    
    提示:用右左法则,或从变量名向外读。
  4. (小挑战)自己实现一个简化版的 qsort,名字叫 my_qsort,支持对 int 数组排序,接收一个比较函数指针作为参数。用你自己的排序算法(冒泡或选择排序),体验回调函数的实际运用。

我们下期见!

内容概要:本文提出了一种基于非合作博弈理论的居民负荷分层调度模型,并结合双层鲸鱼优化算法(Two-level Whale Optimization Algorithm)进行高效求解,模型算法均通过Matlab代码实现。研究针对电力系统中居民侧用电负荷的复杂调度问题,引入非合作博弈机制刻画各用户之间的利益竞争关系,实现负荷的分层优化分配;同时设计双层优化架构,上层优化资源配置,下层模拟用户自主决策行为,提升了模型的实用性合理性。通过智能优化算法求解多层级、非凸非线性的博弈模型,有效提高了调度方案的收敛性全局寻优能力,适用于现代智能电网中的需求侧管理能源优化场景。; 适合人群:具备电力系统基础理论知识和Matlab编程能力,从事智能电网、能源优化调度、需求侧管理、博弈论应用等方向的科研人员、高校研究生及工程技术人员。; 使用场景及目标:①应用于居民区电力负荷的分层优化调度系统设计仿真分析;②为非合作博弈在多主体能源系统建模中的应用提供方法论支持;③利用双层鲸鱼算法解决具有嵌套结构的复杂双层优化问题,提升求解效率调度方案的可行性。; 阅读建议:建议读者结合提供的Matlab代码深入理解模型构建逻辑算法实现流程,重点关注博弈模型的效用函数设计、纳什均衡求解思路以及双层优化结构的迭代机制,宜配合实际用电数据开展复现实验以验证模型有效性鲁棒性。
内容概要:本文围绕基于自适应神经模糊推理系统(ANFIS)智能控制器的可再生能源微电网功率管理系统展开研究,结合Simulink仿真实现,深入探讨了微电网中功率的智能调控经济机组组合调度问题。通过引入ANFIS控制器,有效应对风能、光伏等可再生能源出力的波动性不确定性,提升系统运行的稳定性电能质量。研究内容涵盖微电网多源协调控制策略、功率平衡管理、优化调度模型构建及仿真验证,实现了对分布式电源、储能系统和负荷的协同优化,兼顾经济性可靠性目标,并通过仿真平台验证了所提方法的有效性优越性。; 适合人群:具备电力系统、自动化或新能源相关专业背景,熟悉Matlab/Simulink仿真环境,从事微电网能量管理、智能控制、能源优化等领域研究的研究生、科研人员及工程技术人员。; 使用场景及目标:①用于高比例可再生能源接入场景下的微电网能量管理系统研发教学实践;②为实现微电网功率稳定控制经济高效运行提供先进的智能控制解决方案;③支撑高水平学术论文复现、科研课题攻关及实际工程项目的仿真验证方案优化。; 阅读建议:建议结合提供的Simulink模型相关代码进行动手实践,重点关注ANFIS控制器的设计流程、规则库构建参数调优方法,并通过传统PID或MPC控制策略的对比实验,深入理解其在动态响应鲁棒性方面的优势。同时可进一步拓展文中提出的优化调度逻辑,应用于多目标、多约束的复杂实际应用场景中。
内容概要:本文档聚焦于“直流电机双闭环控制Matlab仿真”,系统阐述了基于Matlab/Simulink平台实现直流电机双闭环控制系统(主要包括速度环电流环)的设计仿真全过程。通过构建直流电机的数学模型,结合PI控制器进行调控,实现对电机转速和电枢电流的高精度动态控制,验证控制策略的稳定性响应性能。文档详细介绍了仿真模型的搭建流程、关键参数的整定方法、系统动态波形的分析手段以及仿真结果的有效性验证,体现了经典自动控制理论在实际电机系统中的工程应用,是电机控制电力电子技术相结合的典型研究案例。; 适合人群:具备自动控制原理、电机拖动基础、电力电子技术和Matlab/Simulink仿真能力的电气工程、自动化、机电一体化等专业的本科生、研究生及从事电机驱动系统研发的工程技术人员。; 使用场景及目标:①作为高校课程设计或实验教学材料,帮助学生深入理解双闭环调速系统的工作机理工程实现;②服务于科研项目,为新型电机控制算法(如滑模、模糊PID等)的开发性能对比提供基础仿真验证平台;③作为工业界产品前期设计的仿真工具,用于评估不同控制策略在动态响应、抗干扰能力和稳态精度方面的可行性。; 阅读建议:建议读者在学习过程中紧密结合自动控制理论知识,亲手在Simulink环境中搭建完整的双闭环仿真模型,通过反复调整PI控制器的比例积分参数,观察并分析转速、电流的阶跃响应曲线,从而深刻理解反馈控制的本质、系统稳定性条件以及参数整定对动态性能的影响,进而掌握电机控制系统的设计精髓。
内容概要:本文研究了基于Benders分解输电网运营商(TSO)和配电网运营商(DSO)协调机制的不确定环境下输配电网双层优化模型,旨在提升高比例可再生能源接入背景下电网系统的协调性鲁棒性。模型上层以系统整体经济性为目标进行优化调度,下层采用Benders分解实现TSODSO之间的信息交互协同决策,通过引入割平面迭代机制保障求解的收敛性全局最优性。研究充分考虑新能源出力负荷需求的不确定性,构建了具有强适应性的双层优化框架,并基于Matlab完成了模型的编程实现仿真验证,有效解决了多主体、多层级、多不确定性因素耦合下的电力系统优化调度难题。; 适合人群:具备电力系统分析、运筹学优化理论基础,熟悉Matlab编程环境,从事智能电网、能源互联网、分布式能源集成、电力市场等方向的研究生、科研人员及工程技术人员。; 使用场景及目标:①研究高渗透率可再生能源条件下输配电网协同优化调度策略;②掌握Benders分解在电力系统双层优化建模中的应用方法实现技巧;③构建TSO-DSO多主体协调机制,实现跨层级电网资源的高效互动决策解耦;④提升对不确定性建模、分解算法设计及大规模优化问题求解能力。; 阅读建议:建议读者结合Matlab代码逐模块剖析模型构建流程,重点理解Benders割的生成逻辑、主从问题的信息传递机制及收敛判据设定,推荐在标准IEEE测试系统上复现实验以深入掌握模型特性算法性能。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值