强曰为道
与天地相似,故不违。知周乎万物,而道济天下,故不过。旁行而不流,乐天知命,故不忧.
文档目录

C/C++ Linux 开发教程(GCC + CMake) / 07 — 指针进阶(函数指针/多级指针)

指针进阶(函数指针/多级指针)

1. 函数指针

函数在内存中也有地址。函数指针就是指向函数入口地址的变量,允许我们像操作变量一样操作函数。

1.1 声明与赋值

#include <stdio.h>

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

int main(void)
{
    // 声明函数指针:指向接受两个 int、返回 int 的函数
    int (*op)(int, int);

    op = add;
    printf("add(3, 5) = %d\n", op(3, 5));

    op = sub;
    printf("sub(3, 5) = %d\n", op(3, 5));

    return 0;
}

⚠️ 注意: 声明时括号不可省略。int (*op)(int, int) 是函数指针,而 int *op(int, int) 是返回指针的函数。

1.2 函数指针类型别名

#include <stdio.h>

// 用 typedef 简化
typedef int (*BinaryOp)(int, int);

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

int compute(BinaryOp op, int a, int b)
{
    return op(a, b);
}

int main(void)
{
    printf("add: %d\n", compute(add, 3, 5));
    printf("mul: %d\n", compute(mul, 3, 5));
    return 0;
}

💡 提示: 使用 typedef 可以大幅简化复杂的函数指针声明。


2. 函数指针数组

#include <stdio.h>

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 divide(double a, double b) { return b != 0 ? a / b : 0; }

int main(void)
{
    // 函数指针数组
    double (*ops[])(double, double) = {add, sub, mul, divide};
    const char *names[] = {"+", "-", "*", "/"};

    double a = 10.0, b = 3.0;
    for (int i = 0; i < 4; i++) {
        printf("%.1f %s %.1f = %.2f\n", a, names[i], b, ops[i](a, b));
    }

    return 0;
}

输出:

10.0 + 3.0 = 13.00
10.0 - 3.0 = 7.00
10.0 * 3.0 = 30.00
10.0 / 3.0 = 3.33

3. 回调函数(Callback)

回调函数是通过函数指针传递的"待执行"函数,广泛用于事件处理、排序、遍历等场景。

#include <stdio.h>

// 遍历数组并对每个元素执行回调
void for_each(int *arr, size_t n, void (*callback)(int *))
{
    for (size_t i = 0; i < n; i++) {
        callback(&arr[i]);
    }
}

void double_value(int *x)  { *x *= 2; }
void negate(int *x)        { *x = -*x; }
void print_value(int *x)   { printf("%d ", *x); }

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

    printf("原始:     ");
    for_each(arr, n, print_value);
    printf("\n");

    for_each(arr, n, double_value);
    printf("翻倍后:   ");
    for_each(arr, n, print_value);
    printf("\n");

    for_each(arr, n, negate);
    printf("取反后:   ");
    for_each(arr, n, print_value);
    printf("\n");

    return 0;
}

3.1 带上下文的回调

#include <stdio.h>

// 带上下文指针的回调模式
typedef void (*DataCallback)(int value, void *ctx);

void process_data(int value, DataCallback cb, void *ctx)
{
    cb(value * value, ctx);
}

typedef struct {
    int sum;
    int count;
} Accumulator;

void accumulate(int value, void *ctx)
{
    Accumulator *acc = (Accumulator *)ctx;
    acc->sum += value;
    acc->count++;
}

int main(void)
{
    Accumulator acc = {0, 0};

    for (int i = 1; i <= 5; i++) {
        process_data(i, accumulate, &acc);
    }

    printf("总和: %d, 计数: %d, 平均: %.2f\n",
           acc.sum, acc.count, (double)acc.sum / acc.count);

    return 0;
}

4. qsort 使用函数指针排序

标准库的 qsort 是回调函数的经典应用:

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

// 整数比较函数
int cmp_int(const void *a, const void *b)
{
    return *(const int *)a - *(const int *)b;
}

// 降序排列
int cmp_int_desc(const void *a, const void *b)
{
    return *(const int *)b - *(const int *)a;
}

// 浮点数比较
int cmp_double(const void *a, const void *b)
{
    double da = *(const double *)a;
    double db = *(const double *)b;
    if (da < db) return -1;
    if (da > db) return 1;
    return 0;
}

// 字符串比较
int cmp_string(const void *a, const void *b)
{
    const char *sa = *(const char *const *)a;
    const char *sb = *(const char *const *)b;
    return strcmp(sa, sb);
}

// 结构体比较
typedef struct {
    char name[32];
    int score;
} Student;

int cmp_student_by_score(const void *a, const void *b)
{
    const Student *sa = (const Student *)a;
    const Student *sb = (const Student *)b;
    return sb->score - sa->score;  // 降序
}

int main(void)
{
    // 排序整数
    int nums[] = {5, 2, 8, 1, 9, 3};
    int n = sizeof(nums) / sizeof(nums[0]);
    qsort(nums, n, sizeof(int), cmp_int);
    printf("升序: ");
    for (int i = 0; i < n; i++) printf("%d ", nums[i]);
    printf("\n");

    // 排序字符串
    const char *fruits[] = {"banana", "apple", "cherry", "date"};
    int fn = sizeof(fruits) / sizeof(fruits[0]);
    qsort(fruits, fn, sizeof(char *), cmp_string);
    printf("字母序: ");
    for (int i = 0; i < fn; i++) printf("%s ", fruits[i]);
    printf("\n");

    // 排序结构体
    Student students[] = {
        {"Alice", 85}, {"Bob", 92}, {"Charlie", 78}, {"Diana", 95}
    };
    int sn = sizeof(students) / sizeof(students[0]);
    qsort(students, sn, sizeof(Student), cmp_student_by_score);
    printf("成绩排名:\n");
    for (int i = 0; i < sn; i++) {
        printf("  %s: %d\n", students[i].name, students[i].score);
    }

    return 0;
}

编译运行:

gcc -Wall -std=c17 -o qsort_demo qsort_demo.c && ./qsort_demo
qsort 参数说明
base数组首地址
nmemb元素个数
size每个元素的大小(字节)
compar比较函数指针

💡 提示: 比较函数必须返回负数(a < b)、零(a == b)或正数(a > b)。


5. 二级指针(char **argv)

二级指针是指向指针的指针。main 函数的 argv 就是典型用例:

#include <stdio.h>

int main(int argc, char *argv[])
{
    // argv 是 char ** 类型
    // argv[0] → 第一个字符串(程序名)
    // argv[1] → 第二个字符串(第一个参数)
    printf("程序名: %s\n", argv[0]);
    for (int i = 1; i < argc; i++) {
        printf("参数 %d: %s\n", i, argv[i]);
    }

    // 指针的指针
    int x = 42;
    int *p = &x;
    int **pp = &p;

    printf("\nx   = %d\n", x);
    printf("*p  = %d\n", *p);
    printf("**pp = %d\n", **pp);

    // 通过二级指针修改值
    **pp = 100;
    printf("修改后 x = %d\n", x);

    return 0;
}

5.1 二级指针与动态二维数组

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

int main(void)
{
    int rows = 3, cols = 4;

    // 分配:数组的数组
    int **matrix = malloc(rows * sizeof(int *));
    for (int i = 0; i < rows; i++) {
        matrix[i] = calloc(cols, sizeof(int));
    }

    // 填充
    for (int i = 0; i < rows; i++) {
        for (int j = 0; j < cols; j++) {
            matrix[i][j] = i * cols + j;
        }
    }

    // 打印
    for (int i = 0; i < rows; i++) {
        for (int j = 0; j < cols; j++) {
            printf("%4d", matrix[i][j]);
        }
        printf("\n");
    }

    // 释放:逆序释放
    for (int i = 0; i < rows; i++) {
        free(matrix[i]);
    }
    free(matrix);

    return 0;
}

6. 指针数组 vs 数组指针

声明名称含义
int *arr[5]指针数组5 个 int* 组成的数组
int (*arr)[5]数组指针指向一个含 5 个 int 的数组
#include <stdio.h>

int main(void)
{
    // 指针数组 —— 每个元素都是指针
    int a = 1, b = 2, c = 3;
    int *ptr_arr[3] = {&a, &b, &c};
    for (int i = 0; i < 3; i++) {
        printf("*ptr_arr[%d] = %d\n", i, *ptr_arr[i]);
    }

    // 数组指针 —— 指向整个数组
    int matrix[2][3] = {{1, 2, 3}, {4, 5, 6}};
    int (*row_ptr)[3] = matrix;  // 指向第一行(含 3 个 int 的数组)

    for (int i = 0; i < 2; i++) {
        for (int j = 0; j < 3; j++) {
            printf("%d ", row_ptr[i][j]);  // row_ptr[i] 等价于 matrix[i]
        }
        printf("\n");
    }

    return 0;
}

💡 提示: 从右向左读:int *arr[5] — arr 是数组([5]),元素类型是 int*int (*arr)[5] — arr 是指针(*),指向 int[5]


7. 动态数组(指针 + malloc)

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

int main(void)
{
    int n;
    printf("输入数组大小: ");
    scanf("%d", &n);

    // 动态分配数组
    int *arr = malloc(n * sizeof(int));
    if (!arr) {
        perror("malloc");
        return 1;
    }

    // 初始化
    for (int i = 0; i < n; i++) {
        arr[i] = i * i;
    }

    // 遍历(与静态数组用法完全相同)
    for (int i = 0; i < n; i++) {
        printf("arr[%d] = %d\n", i, arr[i]);
    }

    // 动态调整大小
    int new_n = n * 2;
    int *new_arr = realloc(arr, new_n * sizeof(int));
    if (new_arr) {
        arr = new_arr;
        for (int i = n; i < new_n; i++) {
            arr[i] = 0;  // 初始化新元素
        }
        printf("扩容到 %d 个元素\n", new_n);
    }

    free(arr);
    return 0;
}

8. 复杂指针声明解读(右左法则)

“右左法则”:从变量名开始,先向右看,再向左看,交替进行。

int (*func)(int, int);
// func → 向右: (int, int) 是函数参数 → 向左: 返回 int
// 结论: func 是函数指针

int (*arr[10])(int);
// arr → 向右: [10] 是数组 → 向左: * 是指针 → 向右: (int) 是函数参数
// 结论: arr 是函数指针数组

int *(*fp)(int *);
// fp → 向右: (int *) 是函数参数 → 向左: * 返回指针 → 向左: int
// 结论: fp 是函数指针,该函数接受 int*,返回 int*

void (*signal(int sig, void (*handler)(int)))(int);
// signal 是函数,接受 (int, 函数指针),返回函数指针
声明含义
int *pp 是指向 int 的指针
int **pp 是指向 int 指针的指针
int *arr[10]arr 是含 10 个 int 指针的数组
int (*arr)[10]arr 是指向含 10 个 int 的数组的指针
int (*fp)(int)fp 是函数指针
int (*arr[5])(int)arr 是含 5 个函数指针的数组

💡 提示: 实在看不懂的声明,可以用 cdecl.org 在线工具自动解读。


9. 函数指针实现多态

虽然 C 没有类和虚函数,但可以用函数指针实现类似多态的效果:

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

// "基类":形状
typedef struct Shape Shape;
struct Shape {
    const char *name;
    double (*area)(Shape *self);
    double (*perimeter)(Shape *self);
    void (*destroy)(Shape *self);
};

// "派生类":圆形
typedef struct {
    Shape base;
    double radius;
} Circle;

double circle_area(Shape *self)
{
    Circle *c = (Circle *)self;
    return M_PI * c->radius * c->radius;
}

double circle_perimeter(Shape *self)
{
    Circle *c = (Circle *)self;
    return 2 * M_PI * c->radius;
}

Circle *circle_create(double radius)
{
    Circle *c = malloc(sizeof(Circle));
    c->base.name = "Circle";
    c->base.area = circle_area;
    c->base.perimeter = circle_perimeter;
    c->base.destroy = NULL;
    c->radius = radius;
    return c;
}

// "派生类":矩形
typedef struct {
    Shape base;
    double width, height;
} Rectangle;

double rect_area(Shape *self)
{
    Rectangle *r = (Rectangle *)self;
    return r->width * r->height;
}

double rect_perimeter(Shape *self)
{
    Rectangle *r = (Rectangle *)self;
    return 2 * (r->width + r->height);
}

Rectangle *rect_create(double w, double h)
{
    Rectangle *r = malloc(sizeof(Rectangle));
    r->base.name = "Rectangle";
    r->base.area = rect_area;
    r->base.perimeter = rect_perimeter;
    r->base.destroy = NULL;
    r->width = w;
    r->height = h;
    return r;
}

// 多态调用
void print_shape_info(Shape *s)
{
    printf("%s: 面积=%.2f, 周长=%.2f\n",
           s->name, s->area(s), s->perimeter(s));
}

int main(void)
{
    Shape *shapes[] = {
        (Shape *)circle_create(5.0),
        (Shape *)rect_create(4.0, 6.0),
        (Shape *)circle_create(3.0),
    };

    for (int i = 0; i < 3; i++) {
        print_shape_info(shapes[i]);
    }

    for (int i = 0; i < 3; i++) {
        free(shapes[i]);
    }

    return 0;
}

编译运行:

gcc -Wall -std=c17 -lm -o polymorphism polymorphism.c && ./polymorphism

输出:

Circle: 面积=78.54, 周长=31.42
Rectangle: 面积=24.00, 周长=20.00
Circle: 面积=28.27, 周长=18.85

10. 实际场景:事件驱动系统

#include <stdio.h>
#include <string.h>

#define MAX_HANDLERS 10

typedef void (*EventHandler)(const char *event, void *data);

typedef struct {
    const char *event_name;
    EventHandler handler;
} HandlerEntry;

typedef struct {
    HandlerEntry entries[MAX_HANDLERS];
    int count;
} EventBus;

void event_bus_init(EventBus *bus) { bus->count = 0; }

void event_bus_register(EventBus *bus, const char *event, EventHandler handler)
{
    if (bus->count < MAX_HANDLERS) {
        bus->entries[bus->count].event_name = event;
        bus->entries[bus->count].handler = handler;
        bus->count++;
    }
}

void event_bus_emit(EventBus *bus, const char *event, void *data)
{
    for (int i = 0; i < bus->count; i++) {
        if (strcmp(bus->entries[i].event_name, event) == 0) {
            bus->entries[i].handler(event, data);
        }
    }
}

// 事件处理器
void on_click(const char *event, void *data)
{
    printf("[%s] 按钮被点击: %s\n", event, (const char *)data);
}

void on_hover(const char *event, void *data)
{
    printf("[%s] 鼠标悬停在: %s\n", event, (const char *)data);
}

int main(void)
{
    EventBus bus;
    event_bus_init(&bus);

    event_bus_register(&bus, "click", on_click);
    event_bus_register(&bus, "hover", on_hover);

    event_bus_emit(&bus, "click", "提交按钮");
    event_bus_emit(&bus, "hover", "导航栏");

    return 0;
}

11. 扩展阅读