1 引言
考虑这样一个需求:编写一个函数,同时返回两个数的和与差。C语言的函数只能有一个返回值,如何实现?
c
#include <stdio.h>
/* 使用指针参数实现“输出参数” */
void add_sub(int a, int b, int *sum, int *diff)
{
*sum = a + b;
*diff = a - b;
}
int main(void)
{
int x = 10, y = 3;
int s, d;
add_sub(x, y, &s, &d);
printf("和:%d,差:%d\n", s, d);
return 0;
}
这就是指针作为函数参数的典型应用------通过指针让函数能够"返回"多个值。本章我们将深入探讨指针与函数结合的各种用法。
2 指针作为函数参数
2.1 为什么需要指针参数
C语言的参数传递默认是值传递------函数得到的是实参的副本。这意味着函数内部无法修改实参的值。
c
void swap_bad(int a, int b) /* 值传递,无法交换实参 */
{
int temp = a;
a = b;
b = temp;
}
void swap_good(int *pa, int *pb) /* 指针参数,可以修改实参 */
{
int temp = *pa;
*pa = *pb;
*pb = temp;
}
2.2 输出参数
通过指针参数,函数可以向调用者"输出"多个值:
c
#include <stdio.h>
/* 计算数组的统计信息 */
void array_stats(const int arr[], int size,
int *min, int *max, double *avg)
{
if (size <= 0) return;
*min = *max = arr[0];
int sum = arr[0];
for (int i = 1; i < size; i++) {
if (arr[i] < *min) *min = arr[i];
if (arr[i] > *max) *max = arr[i];
sum += arr[i];
}
*avg = (double)sum / size;
}
int main(void)
{
int data[] = {5, 2, 8, 1, 9, 3};
int size = sizeof(data) / sizeof(data[0]);
int min, max;
double avg;
array_stats(data, size, &min, &max, &avg);
printf("最小值:%d,最大值:%d,平均值:%.2f\n", min, max, avg);
return 0;
}
2.3 输入/输出参数
有些参数既是输入也是输出------函数读取它指向的值,也可能修改它:
c
#include <stdio.h>
/* 将整数转换为字符串,并返回处理后的指针 */
char* int_to_string(int n, char *buffer, size_t size)
{
snprintf(buffer, size, "%d", n);
return buffer;
}
/* 递增计数器,并返回旧值 */
int increment(int *counter)
{
int old = *counter;
(*counter)++; /* 注意括号:++ 优先级高于 * */
return old;
}
int main(void)
{
int count = 5;
printf("旧值:%d,新值:%d\n", increment(&count), count);
char buf[20];
printf("转换结果:%s\n", int_to_string(123, buf, sizeof(buf)));
return 0;
}
2.4 避免大型结构体的拷贝
当需要传递大型结构体时,传指针可以避免拷贝开销:
c
typedef struct {
char name[100];
int scores[100];
/* ... 更多成员 */
} Student;
/* 传值:拷贝整个结构体,开销大 */
void print_student_value(Student s) { /* ... */ }
/* 传指针:只拷贝地址(4/8字节),效率高 */
void print_student_ptr(const Student *ps) { /* ... */ }
2.5 const 保护
对于只读的指针参数,用 const 修饰表明函数不会修改它指向的数据:
c
void print_array(const int *arr, int size) /* arr 指向的内容不会被修改 */
{
for (int i = 0; i < size; i++) {
printf("%d ", arr[i]);
}
}
3 函数指针
3.1 函数也有地址
函数被编译后,也存储在内存中,因此也有地址。函数名就是函数的入口地址。
c
#include <stdio.h>
int add(int a, int b)
{
return a + b;
}
int main(void)
{
printf("add 函数的地址:%p\n", add);
printf("add 函数的地址:%p\n", &add); /* 和上面一样,取地址符可省略 */
return 0;
}
3.2 函数指针的定义
函数指针的定义语法比较特殊:
c
返回类型 (*指针变量名)(参数类型列表);
c
int (*p)(int, int); /* p 是一个指针,指向一个返回 int、接受两个 int 参数的函数 */
注意 :括号 (*p) 是必需的,否则 int *p(int, int) 会被解释为"返回 int* 的函数"。
3.3 函数指针的赋值
c
int add(int a, int b) { return a + b; }
int sub(int a, int b) { return a - b; }
int (*p)(int, int); /* 定义函数指针 */
p = add; /* p 指向 add 函数 */
p = &add; /* 和上面等价,取地址符可省略 */
p = sub; /* 可以指向不同的函数 */
3.4 函数指针的调用
通过函数指针调用函数有两种等价方式:
c
#include <stdio.h>
int add(int a, int b) { return a + b; }
int main(void)
{
int (*p)(int, int) = add;
/* 方式1:直接使用指针名(会被自动转换为函数调用) */
int r1 = p(3, 5);
/* 方式2:显式解引用(更清楚地表明是指针调用) */
int r2 = (*p)(3, 5);
printf("r1 = %d, r2 = %d\n", r1, r2);
return 0;
}
3.5 函数指针数组
可以把多个函数指针放在数组中,实现函数的分发:
c
#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 ? a / b : 0; }
int main(void)
{
/* 函数指针数组 */
int (*ops[])(int, int) = {add, sub, mul, divide};
char *names[] = {"加法", "减法", "乘法", "除法"};
int a = 10, b = 3;
for (int i = 0; i < 4; i++) {
printf("%s: %d\n", names[i], ops[i](a, b));
}
return 0;
}
4 函数指针作为回调函数
4.1 什么是回调函数
回调函数(Callback Function)是指通过函数指针调用的函数。你把一个函数的地址传给另一个函数,当特定事件发生时,后者会调用前者。
这种机制实现了控制反转------调用者不是自己执行某个操作,而是把操作的具体实现交给被调用者。
4.2 示例:通用排序函数
c
#include <stdio.h>
#include <string.h>
/* 比较函数的类型 */
typedef int (*CompareFunc)(const void*, const void*);
/* 通用的冒泡排序 */
void bubble_sort(void *arr, int size, int elem_size, CompareFunc cmp)
{
char *base = (char*)arr; /* 按字节操作 */
for (int i = 0; i < size - 1; i++) {
for (int j = 0; j < size - 1 - i; j++) {
char *p1 = base + j * elem_size;
char *p2 = base + (j + 1) * elem_size;
if (cmp(p1, p2) > 0) { /* 需要交换 */
for (int k = 0; k < elem_size; k++) {
char temp = p1[k];
p1[k] = p2[k];
p2[k] = temp;
}
}
}
}
}
/* 整数比较函数 */
int cmp_int(const void *a, const void *b)
{
int ia = *(const int*)a;
int ib = *(const int*)b;
return ia - ib;
}
/* 字符串比较函数 */
int cmp_string(const void *a, const void *b)
{
const char **sa = (const char**)a;
const char **sb = (const char**)b;
return strcmp(*sa, *sb);
}
int main(void)
{
/* 测试整数排序 */
int nums[] = {5, 2, 8, 1, 9, 3};
int n = sizeof(nums) / sizeof(nums[0]);
bubble_sort(nums, n, sizeof(int), cmp_int);
for (int i = 0; i < n; i++) {
printf("%d ", nums[i]);
}
printf("\n");
/* 测试字符串排序 */
const char *fruits[] = {"banana", "apple", "orange", "grape"};
int m = sizeof(fruits) / sizeof(fruits[0]);
bubble_sort(fruits, m, sizeof(char*), cmp_string);
for (int i = 0; i < m; i++) {
printf("%s ", fruits[i]);
}
printf("\n");
return 0;
}
4.3 示例:数值积分计算
c
#include <stdio.h>
#include <math.h>
/* 函数类型:接收 double,返回 double */
typedef double (*MathFunc)(double);
/* 用梯形法求定积分 */
double integral(MathFunc f, double a, double b, int n)
{
double h = (b - a) / n;
double sum = (f(a) + f(b)) / 2.0;
for (int i = 1; i < n; i++) {
sum += f(a + i * h);
}
return sum * h;
}
double square(double x) { return x * x; }
double cube(double x) { return x * x * x; }
double my_sin(double x) { return sin(x); }
int main(void)
{
printf("x^2 在 [0,1] 上的积分:%f\n", integral(square, 0, 1, 1000));
printf("x^3 在 [0,1] 上的积分:%f\n", integral(cube, 0, 1, 1000));
printf("sin(x) 在 [0,π] 上的积分:%f\n", integral(my_sin, 0, M_PI, 1000));
return 0;
}
4.4 示例:菜单系统
c
#include <stdio.h>
#include <stdlib.h>
/* 菜单项结构 */
typedef struct {
const char *name;
void (*func)(void); /* 函数指针,指向无参数无返回值的函数 */
} MenuItem;
void new_file(void) { printf("新建文件\n"); }
void open_file(void) { printf("打开文件\n"); }
void save_file(void) { printf("保存文件\n"); }
void exit_prog(void) { printf("退出程序\n"); exit(0); }
int main(void)
{
MenuItem menu[] = {
{"新建", new_file},
{"打开", open_file},
{"保存", save_file},
{"退出", exit_prog}
};
int n = sizeof(menu) / sizeof(menu[0]);
int choice;
while (1) {
printf("\n=== 菜单 ===\n");
for (int i = 0; i < n; i++) {
printf("%d. %s\n", i + 1, menu[i].name);
}
printf("请选择:");
scanf("%d", &choice);
if (choice >= 1 && choice <= n) {
menu[choice - 1].func(); /* 通过函数指针调用 */
} else {
printf("无效选择\n");
}
}
return 0;
}
5 函数指针的 typedef
用 typedef 可以简化函数指针的定义:
c
/* 定义函数指针类型 */
typedef int (*Operation)(int, int);
/* 现在可以用 Operation 像普通类型一样使用 */
Operation p; /* 等价于 int (*p)(int, int) */
int add(int a, int b) { return a + b; }
p = add;
int result = p(3, 5);
这在复杂代码中非常有用,特别是当函数指针作为参数或返回值时。
6 常见错误与注意事项
6.1 函数指针的语法错误
c
int *p(int, int); /* 这是一个返回 int* 的函数声明,不是函数指针! */
int (*p)(int, int); /* 这才是函数指针 */
6.2 忘记检查空指针
c
void execute(void (*func)(void))
{
if (func != NULL) { /* 总是检查 */
func();
}
}
6.3 类型不匹配
c
int int_func(int a) { return a; }
double double_func(double a) { return a; }
int (*p1)(int) = int_func; /* 正确 */
int (*p2)(int) = double_func; /* 错误!类型不匹配 */
6.4 用函数指针调用时括号错误
c
int (*p)(int, int) = add;
int r1 = p(3, 5); /* 正确 */
int r2 = *p(3, 5); /* 错误!* 的优先级低于函数调用 */
int r3 = (*p)(3, 5); /* 正确,显式解引用 */
6.5 返回指向局部函数的指针
c
int (*get_func(void))(int, int)
{
int add(int a, int b) { return a + b; } /* 局部函数(非标准) */
return add; /* 危险!add 的作用域结束 */
}
C语言不支持函数内部定义函数(嵌套函数),这是 GCC 扩展,不应依赖。
7 本章小结
本章系统介绍了指针与函数的结合:
1. 指针作为函数参数
-
实现"输出参数",让函数"返回"多个值
-
避免大型结构体的拷贝开销
-
用
const保护只读参数
2. 函数指针
-
函数名就是函数的入口地址
-
定义:
返回类型 (*指针名)(参数类型列表) -
赋值:
p = func_name -
调用:
p(args)或(*p)(args)
3. 函数指针数组
-
存储多个函数指针
-
实现函数分发、状态机
4. 回调函数
-
把函数指针作为参数传给另一个函数
-
实现通用算法(如排序、积分)
-
实现事件驱动、菜单系统
-
用
typedef简化复杂声明
5. 常见错误
-
混淆函数指针声明和函数声明
-
忘记检查空指针
-
类型不匹配
-
调用时括号错误