在C语言中,函数名本质上是一个指向函数代码的指针(函数入口地址)。函数指针允许我们将函数作为参数传递、存储在数组中、或者在运行时动态决定调用哪个函数。 回调函数是通过函数指针实现的一种设计模式,允许我们将用户自定义的行为注入到通用算法中。 函数指针是实现多态、事件驱动编程、库函数扩展的重要手段。本章将系统讲解函数指针的声明、使用及回调函数的典型应用。
函数指针的声明形式为:返回类型 (*指针名)(参数类型列表);。注意括号不能省略,否则变成返回指针的函数。
#include <stdio.h>
// 定义一个普通函数
int add(int a, int b) {
return a + b;
}
int main() {
// 声明一个函数指针,指向返回int、接收两个int参数的函数
int (*funcPtr)(int, int);
// 将函数地址赋给指针(函数名就是地址)
funcPtr = add;
// 通过函数指针调用函数(两种写法等效)
int result1 = funcPtr(3, 5);
int result2 = (*funcPtr)(3, 5);
printf("结果: %d\n", result1); // 输出8
return 0;
}
typedef 简化函数指针类型的声明。
#include <stdio.h>
// 定义函数指针类型别名
typedef int (*Operation)(int, int);
int add(int a, int b) { return a + b; }
int sub(int a, int b) { return a - b; }
int main() {
Operation op = add;
printf("add: %d\n", op(10, 5));
op = sub;
printf("sub: %d\n", op(10, 5));
return 0;
}
将函数指针作为参数传递给另一个函数,被调用的函数就是回调函数。这种模式使得通用函数可以处理不同的具体行为。
#include <stdio.h>
// 回调函数类型
typedef void (*Callback)(int);
// 通用函数,接收回调
void processArray(int arr[], int n, Callback cb) {
for (int i = 0; i < n; i++) {
cb(arr[i]); // 调用回调函数处理每个元素
}
}
// 具体的回调函数
void printSquare(int x) {
printf("%d ", x * x);
}
void printDouble(int x) {
printf("%d ", x * 2);
}
int main() {
int nums[] = {1, 2, 3, 4, 5};
int n = sizeof(nums) / sizeof(nums[0]);
printf("平方: ");
processArray(nums, n, printSquare);
printf("\n");
printf("两倍: ");
processArray(nums, n, printDouble);
printf("\n");
return 0;
}
C语言标准库中的 qsort 函数是回调的经典应用,它通过比较函数指针实现对任意类型数组的排序。
#include <stdio.h>
#include <stdlib.h>
// 比较整数(升序)
int cmpInt(const void *a, const void *b) {
int ia = *(const int *)a;
int ib = *(const int *)b;
return ia - ib; // 升序
// return ib - ia; // 降序
}
// 比较浮点数(注意精度)
int cmpFloat(const void *a, const void *b) {
float fa = *(const float *)a;
float fb = *(const float *)b;
if (fa < fb) return -1;
if (fa > fb) return 1;
return 0;
}
// 比较字符串(按字典序)
int cmpString(const void *a, const void *b) {
const char **sa = (const char **)a;
const char **sb = (const char **)b;
return strcmp(*sa, *sb);
}
int main() {
int intArr[] = {34, 7, 23, 32, 5, 62};
int n = sizeof(intArr) / sizeof(intArr[0]);
qsort(intArr, n, sizeof(int), cmpInt);
printf("排序后的整数: ");
for (int i = 0; i < n; i++) printf("%d ", intArr[i]);
printf("\n");
float floatArr[] = {3.14, 1.41, 2.71, 0.58};
n = sizeof(floatArr) / sizeof(floatArr[0]);
qsort(floatArr, n, sizeof(float), cmpFloat);
printf("排序后的浮点数: ");
for (int i = 0; i < n; i++) printf("%.2f ", floatArr[i]);
printf("\n");
const char *strArr[] = {"banana", "apple", "cherry", "date"};
n = sizeof(strArr) / sizeof(strArr[0]);
qsort(strArr, n, sizeof(const char *), cmpString);
printf("排序后的字符串: ");
for (int i = 0; i < n; i++) printf("%s ", strArr[i]);
printf("\n");
return 0;
}
qsort 的比较函数需要返回负数、0或正数来表示小于、等于或大于。参数类型为 const void *,使用时需要先转换为实际类型。
可以将多个函数指针存入数组,实现类似多态的分发机制。常见于状态机、菜单系统等。
#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 divide(int a, int b) { return b != 0 ? a / b : 0; }
int main() {
// 函数指针数组,存放四个运算函数
int (*ops[])(int, int) = {add, sub, mul, divide};
const char *names[] = {"加法", "减法", "乘法", "除法"};
int a = 10, b = 3;
for (int i = 0; i < 4; i++) {
printf("%s: %d %s %d = %d\n", names[i], a, names[i], b, ops[i](a, b));
}
return 0;
}
模仿 qsort,实现一个简单的冒泡排序通用版本,使用回调函数比较元素。
#include <stdio.h>
#include <string.h>
// 通用冒泡排序
void bubbleSort(void *base, size_t nmemb, size_t size,
int (*compar)(const void *, const void *)) {
char *arr = (char *)base; // 按字节操作
for (size_t i = 0; i < nmemb - 1; i++) {
for (size_t j = 0; j < nmemb - i - 1; j++) {
// 获取第j个和第j+1个元素的指针
void *elem1 = arr + j * size;
void *elem2 = arr + (j + 1) * size;
if (compar(elem1, elem2) > 0) {
// 交换两个元素
char temp[size];
memcpy(temp, elem1, size);
memcpy(elem1, elem2, size);
memcpy(elem2, temp, size);
}
}
}
}
// 比较整数
int intCmp(const void *a, const void *b) {
return *(const int *)a - *(const int *)b;
}
int main() {
int arr[] = {64, 34, 25, 12, 22, 11, 90};
int n = sizeof(arr) / sizeof(arr[0]);
bubbleSort(arr, n, sizeof(int), intCmp);
printf("排序结果: ");
for (int i = 0; i < n; i++) printf("%d ", arr[i]);
printf("\n");
return 0;
}
有时回调函数需要访问外部数据,可以通过 void * 参数传递上下文。
#include <stdio.h>
// 回调函数类型,接收一个额外上下文参数
typedef void (*ProcessFunc)(int, void *);
// 遍历数组,为每个元素调用回调,并传递上下文
void processArray(int arr[], int n, ProcessFunc func, void *ctx) {
for (int i = 0; i < n; i++) {
func(arr[i], ctx);
}
}
// 回调:计算累加和
void sumCallback(int val, void *ctx) {
int *sum = (int *)ctx;
*sum += val;
}
// 回调:打印并输出倍数(额外参数为倍数)
void printMultipleCallback(int val, void *ctx) {
int multiplier = *(int *)ctx;
printf("%d ", val * multiplier);
}
int main() {
int nums[] = {1, 2, 3, 4, 5};
int n = sizeof(nums) / sizeof(nums[0]);
int sum = 0;
processArray(nums, n, sumCallback, &sum);
printf("总和: %d\n", sum);
int factor = 3;
printf("乘以 %d: ", factor);
processArray(nums, n, printMultipleCallback, &factor);
printf("\n");
return 0;
}
函数指针的声明有时会很复杂,下面是一些常见形式的解析技巧。
int (*p)(int, int); —— p是指向函数的指针,该函数返回int,接收两个int。int (*arr[5])(int); —— arr是一个数组,包含5个函数指针,每个指针指向返回int、接收一个int的函数。int (*func(int, int))(double); —— func是一个函数,它返回一个指向函数的指针,该返回的函数接收double返回int。int (*(*p)(int))(double); —— p是指针,指向一个函数,该函数接收int,返回一个指向函数的指针,该返回的函数接收double返回int。解析规则:从最左边的标识符开始,先看括号内的部分,按照“右左法则”理解。
typedef 进行分层定义,提高可读性。
funcPtr = add; 正确;funcPtr = add(); 是调用函数,将返回值赋给指针,类型错误。#include <stdio.h>
#include <stdlib.h>
// 定义操作函数类型
typedef double (*Operation)(double, double);
// 具体操作
double add(double a, double b) { return a + b; }
double sub(double a, double b) { return a - b; }
double mul(double a, double b) { return a * b; }
double divi(double a, double b) { return b != 0 ? a / b : 0; }
// 操作映射
typedef struct {
char op;
Operation func;
} OpMapping;
OpMapping ops[] = {
{'+', add},
{'-', sub},
{'*', mul},
{'/', divi}
};
const int opCount = sizeof(ops) / sizeof(ops[0]);
Operation getOperation(char op) {
for (int i = 0; i < opCount; i++) {
if (ops[i].op == op) return ops[i].func;
}
return NULL;
}
int main() {
double a, b;
char op;
printf("输入表达式 (如 3.5 + 2.1): ");
scanf("%lf %c %lf", &a, &op, &b);
Operation func = getOperation(op);
if (func != NULL) {
printf("结果: %.2f\n", func(a, b));
} else {
printf("无效运算符\n");
}
return 0;
}
函数指针是C语言实现灵活性和扩展性的重要工具,尤其在库设计、框架开发中不可或缺。 通过回调机制,我们可以将通用算法与具体业务解耦。掌握函数指针,将为后续学习函数式编程思想、高级数据结构打下坚实基础。