【C语言入门】函数指针:定义与使用(指向函数的指针变量)

0. 前置知识:指针与函数的基础

要理解函数指针,需要先回顾两个基础概念:指针函数

(1)指针的本质

指针是一个变量,它存储的是内存地址。例如:

 

int a = 10;    // 变量a存储整数10,位于内存地址0x1000
int* p = &a;   // 指针p存储a的地址(0x1000),p的类型是“int*”(指向int的指针)

 

通过指针 p,可以访问或修改 a 的值(*p = 20; 等价于 a = 20;)。

(2)函数的本质

函数是一段有特定功能的代码块。例如:

int add(int a, int b) {  // 函数add的功能是计算a+b
    return a + b;
}

 

当程序运行到 add(3, 5) 时,计算机会跳转到 add 函数的代码位置,执行相加操作,然后返回结果。

(3)关键问题:函数有地址吗?

是的!函数的代码在内存中也有固定的存储位置,这个位置的 “门牌号” 就是函数的地址。可以用 & 运算符获取函数的地址(和取变量地址类似)。


1. 函数指针的定义与声明

(1)函数指针的语法格式

函数指针的声明需要明确三个信息:

  • 返回值类型:函数的返回值类型(如 intvoid 等)。
  • 参数类型列表:函数的参数类型和数量(如 int a, int b)。
  • 指针变量名:函数指针变量的名称(如 p_add)。

语法格式

返回值类型 (*指针变量名)(参数类型列表);

 

示例
声明一个指向 add 函数的指针:

int add(int a, int b) { return a + b; }  // 原函数

// 声明函数指针:返回值为int,参数为两个int的函数指针
int (*p_add)(int, int);  

(2)关键注意点

  • 函数指针的类型由 “返回值类型” 和 “参数类型列表” 共同决定。例如:
    int (*p1)(int, int) 和 int (*p2)(int) 是两种不同类型的函数指针(参数数量不同)。
  • 函数指针的名称是 *指针变量名 中的变量名(如 p_add),* 表示这是一个指针。

(3)如何初始化函数指针?

函数指针需要指向一个具体的函数,初始化方式有两种:

  • 直接赋值函数名(推荐):p_add = add;
  • 显式取函数地址:p_add = &add;

原因:在 C 语言中,函数名本身就代表函数的地址(类似数组名代表数组首地址),因此 add 和 &add 是等价的。

示例代码

#include <stdio.h>

// 原函数
int add(int a, int b) {
    return a + b;
}

int main() {
    // 声明函数指针
    int (*p_add)(int, int);  

    // 初始化:指向add函数(两种方式等价)
    p_add = add;       // 方式1:直接赋值函数名
    // p_add = &add;  // 方式2:显式取地址(效果相同)

    // 验证:打印函数地址(结果相同)
    printf("add的地址:%p\n", add);       // 输出:0x1000(示例地址)
    printf("&add的地址:%p\n", &add);     // 输出:0x1000(与add相同)
    printf("p_add的地址:%p\n", p_add);   // 输出:0x1000(与add相同)

    return 0;
}

2. 如何通过函数指针调用函数?

通过函数指针调用函数时,有两种语法形式:

  • 直接使用指针变量名:p_add(3, 5);
  • 解引用指针后调用:(*p_add)(3, 5);

原因:C 语言编译器对这两种写法做了等价处理,本质上都是通过函数指针存储的地址找到函数并执行。

示例代码

#include <stdio.h>

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

int main() {
    int (*p_add)(int, int) = add;  // 声明并初始化函数指针

    // 方式1:直接调用
    int result1 = p_add(3, 5);     // 等价于 add(3,5)
    printf("result1: %d\n", result1);  // 输出:8

    // 方式2:解引用后调用
    int result2 = (*p_add)(3, 5);  // 等价于 add(3,5)
    printf("result2: %d\n", result2);  // 输出:8

    return 0;
}

关键结论

两种调用方式完全等价,推荐直接使用指针变量名调用(更简洁)。


3. 函数指针的类型匹配规则

函数指针的类型必须与所指向的函数的类型完全一致(包括返回值类型和参数类型列表),否则会导致编译错误。

(1)返回值类型必须匹配

如果函数指针声明的返回值类型与目标函数的返回值类型不同,会报错。

错误示例

// 目标函数返回值为int
int add(int a, int b) { return a + b; }

// 错误:函数指针声明的返回值为void(不匹配)
void (*p_add)(int, int) = add;  // 编译错误!

(2)参数类型和数量必须匹配

函数指针的参数类型列表必须与目标函数的参数类型列表完全一致(顺序、类型、数量)。

错误示例

// 目标函数有2个int参数
int add(int a, int b) { return a + b; }

// 错误1:参数数量不匹配(声明1个参数)
int (*p_add1)(int) = add;  // 编译错误!

// 错误2:参数类型不匹配(声明double参数)
int (*p_add2)(double, double) = add;  // 编译错误!

(3)严格匹配的意义

函数指针的类型匹配规则是 C 语言的 “类型安全” 要求。如果类型不匹配,编译器无法保证函数调用时的参数传递和返回值处理是正确的,可能导致程序崩溃或错误结果。


4. 函数指针的高级用法

(1)函数指针数组

函数指针数组是一个数组,数组的每个元素都是一个函数指针。它的作用是批量管理多个同类型的函数

语法格式

返回值类型 (*数组名[数组大小])(参数类型列表);

 

示例

#include <stdio.h>

// 三个同类型的函数
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 main() {
    // 声明函数指针数组(3个元素,每个元素是“int (*)(int, int)”类型)
    int (*funcs[3])(int, int) = {add, sub, mul};

    // 通过数组下标调用函数
    int result1 = funcs[0](3, 5);  // 调用add(3,5) → 8
    int result2 = funcs[1](3, 5);  // 调用sub(3,5) → -2
    int result3 = funcs[2](3, 5);  // 调用mul(3,5) → 15

    printf("加法结果: %d\n", result1);
    printf("减法结果: %d\n", result2);
    printf("乘法结果: %d\n", result3);

    return 0;
}

(2)指向函数指针的指针(二级函数指针)

二级函数指针是指向 “函数指针” 的指针,它存储的是函数指针的地址。

语法格式

返回值类型 (**二级指针名)(参数类型列表);

 

示例

#include <stdio.h>

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

int main() {
    int (*p_add)(int, int) = add;    // 一级函数指针(指向add)
    int (**pp_add)(int, int) = &p_add;  // 二级函数指针(指向p_add的地址)

    // 通过二级指针调用函数
    int result = (**pp_add)(3, 5);  // 等价于 (*p_add)(3,5) → 8
    printf("结果: %d\n", result);

    return 0;
}

(3)函数指针作为函数参数(回调函数)

函数指针最强大的用途之一是作为函数的参数,实现 “回调机制”。回调函数允许我们在一个函数中动态指定另一个函数的行为。

典型场景

  • 排序算法(如 C 标准库的 qsort 函数需要传递比较函数)。
  • 事件处理(如 GUI 程序中点击按钮触发自定义回调函数)。

示例:用函数指针实现通用计算器

#include <stdio.h>

// 定义函数指针类型(方便后续使用)
typedef int (*CalcFunc)(int, int);

// 加法函数
int add(int a, int b) { return a + b; }

// 减法函数
int sub(int a, int b) { return a - b; }

// 通用计算器函数(接收函数指针作为参数)
int calculate(int a, int b, CalcFunc func) {
    return func(a, b);  // 调用传入的函数
}

int main() {
    int a = 10, b = 5;

    // 用加法函数作为回调
    int sum = calculate(a, b, add);
    printf("加法结果: %d\n", sum);  // 输出:15

    // 用减法函数作为回调
    int diff = calculate(a, b, sub);
    printf("减法结果: %d\n", diff);  // 输出:5

    return 0;
}

 

关键价值:通过函数指针作为参数,calculate 函数不再固定实现一种计算逻辑,而是可以动态接收不同的计算函数(加法、减法、乘法等),大大提高了代码的灵活性和复用性。


5. 函数指针与普通指针的区别

特性普通指针(指向变量)函数指针(指向函数)
指向的内容变量的内存地址(存储数据)函数的内存地址(存储代码)
解引用操作*p 访问变量的值(*p)(...) 调用函数
类型决定因素指向的数据类型(如 int*函数的返回值类型和参数列表
算术运算支持指针加减(如 p++ 移动到下一个变量)不支持指针加减(函数地址是连续的代码块)
用途动态访问或修改变量值动态调用函数、实现回调机制

6. 常见错误与注意事项

(1)未初始化的函数指针

函数指针未初始化时,其值是随机的(野指针),直接调用会导致程序崩溃。

错误示例

int (*p_add)(int, int);  // 未初始化
p_add(3, 5);  // 崩溃!p_add指向未知地址

 

解决方案:必须先初始化函数指针,使其指向一个有效的函数。

(2)类型不匹配的函数指针赋值

函数指针的类型必须与目标函数完全一致,否则编译报错。

错误示例

void print(int a) { printf("%d\n", a); }

// 错误:函数指针声明的返回值是int,而目标函数返回void
int (*p_print)(int) = print;  // 编译错误!

(3)误将函数名作为普通变量使用

函数名代表函数的地址,但不能像普通变量一样修改其值(函数地址是只读的)。

错误示例

int add(int a, int b) { return a + b; }
add = 0x1000;  // 错误!函数名是常量,不能赋值

(4)函数指针数组的越界访问

函数指针数组的下标不能超过数组大小,否则会访问到非法内存。

错误示例

int (*funcs[2])(int, int) = {add, sub};
funcs[2](3, 5);  // 错误!数组大小为2,下标最大是1

7. 实际应用场景

(1)C 标准库中的 qsort 函数

qsort 是 C 语言标准库的排序函数,它需要接收一个比较函数指针,用于定义排序规则(升序或降序)。

函数原型

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

 

参数说明

  • base:待排序数组的起始地址。
  • nitems:数组元素个数。
  • size:单个元素的大小(字节)。
  • compar:比较函数指针(返回值为 int,参数为两个 const void* 类型)。

示例:用 qsort 排序整数数组

#include <stdio.h>
#include <stdlib.h>

// 比较函数:升序排序(a < b 返回-1,a == b 返回0,a > b 返回1)
int compare_asc(const void *a, const void *b) {
    int num1 = *(const int *)a;
    int num2 = *(const int *)b;
    return (num1 > num2) - (num1 < num2);  // 等价于 num1 - num2(但更安全)
}

// 比较函数:降序排序
int compare_desc(const void *a, const void *b) {
    int num1 = *(const int *)a;
    int num2 = *(const int *)b;
    return (num2 > num1) - (num2 < num1);  // 等价于 num2 - num1
}

int main() {
    int arr[] = {3, 1, 4, 2, 5};
    int n = sizeof(arr) / sizeof(arr[0]);

    // 升序排序(传递compare_asc函数指针)
    qsort(arr, n, sizeof(int), compare_asc);
    printf("升序结果: ");
    for (int i = 0; i < n; i++) {
        printf("%d ", arr[i]);  // 输出:1 2 3 4 5
    }
    printf("\n");

    // 降序排序(传递compare_desc函数指针)
    qsort(arr, n, sizeof(int), compare_desc);
    printf("降序结果: ");
    for (int i = 0; i < n; i++) {
        printf("%d ", arr[i]);  // 输出:5 4 3 2 1
    }
    printf("\n");

    return 0;
}

(2)嵌入式系统中的中断处理

在嵌入式系统中,硬件中断(如定时器中断、串口中断)需要注册中断处理函数。通过函数指针可以动态绑定不同的中断处理逻辑。

示例伪代码

// 定义中断处理函数指针类型
typedef void (*InterruptHandler)(void);

// 注册中断处理函数的函数(接收函数指针)
void register_interrupt_handler(InterruptHandler handler) {
    // 将handler存储到中断向量表中
    interrupt_vector_table[TIMER_IRQ] = handler;
}

// 自定义的中断处理函数
void timer_irq_handler(void) {
    printf("定时器中断触发!\n");
}

int main() {
    // 注册中断处理函数(传递函数指针)
    register_interrupt_handler(timer_irq_handler);

    // 启动定时器,触发中断...
    return 0;
}

(3)动态插件系统

函数指针可以实现 “插件化” 架构:主程序定义接口(函数指针类型),插件实现具体功能并注册到主程序中。

示例

#include <stdio.h>

// 定义插件函数指针类型(返回字符串描述)
typedef const char* (*PluginFunc)(void);

// 主程序:存储插件的数组
PluginFunc plugins[10] = {0};
int plugin_count = 0;

// 注册插件的函数(接收函数指针)
void register_plugin(PluginFunc plugin) {
    if (plugin_count < 10) {
        plugins[plugin_count++] = plugin;
    }
}

// 运行所有插件
void run_plugins() {
    for (int i = 0; i < plugin_count; i++) {
        printf("插件%d: %s\n", i+1, plugins[i]());
    }
}

// 插件1:实现具体功能
const char* plugin1_info(void) {
    return "我是插件1,功能:数据统计";
}

// 插件2:实现具体功能
const char* plugin2_info(void) {
    return "我是插件2,功能:图表绘制";
}

int main() {
    // 注册插件(传递函数指针)
    register_plugin(plugin1_info);
    register_plugin(plugin2_info);

    // 运行插件
    run_plugins();
    /* 输出:
       插件1: 我是插件1,功能:数据统计
       插件2: 我是插件2,功能:图表绘制
    */

    return 0;
}

8. 总结

函数指针是 C 语言中最灵活、最强大的特性之一,它的核心价值是让函数可以像变量一样被传递和调用。通过函数指针,我们可以实现回调函数、动态功能切换、插件系统等高级功能。

对于初学者,掌握函数指针的关键是:

  1. 理解函数指针的类型由 “返回值类型 + 参数列表” 决定。
  2. 掌握函数指针的声明、初始化和调用语法。
  3. 学会在实际场景中应用函数指针(如回调函数)。

 

 

 

 

用 “电影票” 类比,轻松理解函数指针

你可以把函数指针想象成一张 “电影票”—— 它的核心作用是“指向一个函数”,就像电影票上的 “座位号” 指向电影院里的一个具体座位。我们一步步拆解这个类比:

1. 先理解 “函数” 是什么?

函数是一段有特定功能的代码块。比如你写了一个函数 int add(int a, int b) { return a + b; },它的功能是 “计算两个数的和”。
这就像电影院里的 “放映厅”—— 每个放映厅(函数)有固定的 “功能”(放特定电影),比如 1 号厅放《流浪地球》,2 号厅放《星际穿越》。

2. 什么是 “函数的地址”?

当你在代码中写一个函数时,编译器会把这段代码翻译成机器指令,并把这些指令存放在内存的某个位置。这个内存位置的 “门牌号” 就是函数的地址
就像每个放映厅(函数)在电影院(内存)里有一个唯一的 “房间号”(地址),比如 1 号厅在 3 楼 501 室,2 号厅在 3 楼 502 室。

3. 函数指针:指向函数地址的 “电影票”

函数指针是一个变量,它的作用是存储函数的地址。你可以把它想象成一张 “电影票”—— 票上印着 “3 楼 502 室”(函数地址),拿着这张票(函数指针),你就能找到对应的放映厅(函数),并 “看电影”(调用函数)。

4. 如何 “用函数指针调用函数”?

假设你有一张电影票(函数指针)指向 2 号厅(函数 add),那么 “用票看电影” 的过程就是:通过票上的地址找到放映厅,然后触发放映(调用函数)。
对应到代码里,就是通过函数指针存储的地址,找到函数的机器指令,然后执行这些指令(计算两个数的和)。

总结:函数指针的本质

函数指针 = 存储函数地址的变量 = 指向 “代码块位置” 的指针。
它的核心价值是:让函数像变量一样被传递和调用,这为 C 语言实现 “灵活的代码逻辑”(比如回调函数、动态功能切换)提供了可能。

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值