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

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. 常见错误

  • 混淆函数指针声明和函数声明

  • 忘记检查空指针

  • 类型不匹配

  • 调用时括号错误

相关推荐
kaikaile19951 小时前
庞加莱截面计算MATLAB程序
开发语言·matlab
酬勤-人间道1 小时前
自研软件模型处理全流程|个人开发经验分享
c++·经验分享·计算机·计算机图形学·桩号·开挖·回填
Frostnova丶1 小时前
LeetCode 3070. 元素和小于等于 k 的子矩阵数目
算法·leetcode·矩阵
郝学胜-神的一滴1 小时前
算法奇旅:探寻3/5/7素因子之第k特殊数——优雅的多路指针解法全解析
数据结构·c++·算法·职场和发展
ECT-OS-JiuHuaShan2 小时前
朱梁万有递归元定理,解构西方文明中心论幻觉
开发语言·人工智能·php
Aubrey-J2 小时前
练习开发Skill——网页内容抓取Skill(website-content-fetch)
开发语言·人工智能
handler012 小时前
基础算法:分治
c语言·开发语言·c++·笔记·学习·算法·深度优先
Yzzz-F2 小时前
Problem - D2 - Codeforces [插入计数]
算法
图图的点云库2 小时前
点云深度学习算法概述
人工智能·深度学习·算法