资讯动态

【C语言程序设计】第31篇:指针与函数

发布时间:2026/8/4 20:20:38 来源:尧图企业网站定制
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语言的参数传递默认是值传递——函数得到的是实参的副本。这意味着函数内部无法修改实参的值。cvoid 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 避免大型结构体的拷贝当需要传递大型结构体时传指针可以避免拷贝开销ctypedef 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修饰表明函数不会修改它指向的数据cvoid 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返回类型 (*指针变量名)(参数类型列表);cint (*p)(int, int); /* p 是一个指针指向一个返回 int、接受两个 int 参数的函数 */注意括号(*p)是必需的否则int *p(int, int)会被解释为“返回 int* 的函数”。3.3 函数指针的赋值cint 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 函数指针的语法错误cint *p(int, int); /* 这是一个返回 int* 的函数声明不是函数指针 */ int (*p)(int, int); /* 这才是函数指针 */6.2 忘记检查空指针cvoid execute(void (*func)(void)) { if (func ! NULL) { /* 总是检查 */ func(); } }6.3 类型不匹配cint 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 用函数指针调用时括号错误cint (*p)(int, int) add; int r1 p(3, 5); /* 正确 */ int r2 *p(3, 5); /* 错误* 的优先级低于函数调用 */ int r3 (*p)(3, 5); /* 正确显式解引用 */6.5 返回指向局部函数的指针cint (*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. 常见错误混淆函数指针声明和函数声明忘记检查空指针类型不匹配调用时括号错误

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价