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 *p | p 是指向 int 的指针 |
int **p | p 是指向 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;
}