C: 指针进阶

指针就像地址条,指向指针的指针就是写了地址条的地址条——听起来绕,但生活里"寄存包裹的柜子号"就是这样一层套一层。

1. 指向指针的指针

指针变量本身也是变量,存放在内存中,也有地址。用一个指针指向另一个指针,就得到了"指向指针的指针"。

C
int x = 42;
int *p = &x;
int **pp = &p;

通过 **pp 可以间接修改 x 的值:

C
**pp = 100;
printf("%d\n", x);
TEXT 📖 仅展示
100

二级指针最常见的用途是"在函数内部修改指针本身",比如让函数为指针分配内存:

C
void alloc_buf(char **ptr, int size) {
    *ptr = (char *)malloc(size);
}

int main(void) {
    char *buf = NULL;
    alloc_buf(&buf, 128);
    if (buf) {
        strcpy(buf, "hello");
        printf("%s\n", buf);
        free(buf);
    }
    return 0;
}
TEXT 📖 仅展示
hello
💡 如果只传一级指针 char *ptr,函数内修改的只是副本,外面的指针不会变。要改指针本身,必须传指针的地址。


2. 指针数组与数组指针

这两个概念容易混淆,关键看 *[] 的优先级。

(1) 指针数组

int *arr[4]——先和 [] 结合,是数组,每个元素是 int *

C
int a = 10, b = 20, c = 30;
int *arr[3] = {&a, &b, &c};
for (int i = 0; i < 3; i++) {
    printf("%d ", *arr[i]);
}
TEXT 📖 仅展示
10 20 30

典型应用:字符串数组。

C
const char *names[3] = {"Alice", "Bob", "Carol"};
for (int i = 0; i < 3; i++) {
    printf("%s\n", names[i]);
}
TEXT 📖 仅展示
Alice
Bob
Carol

(2) 数组指针

int (*p)[4]——先和 * 结合,是指针,指向一个含 4 个 int 的数组。常用于二维数组传参。

C
int matrix[3][4] = {
    {1, 2, 3, 4},
    {5, 6, 7, 8},
    {9, 10, 11, 12}
};
int (*p)[4] = matrix;
printf("%d\n", p[1][2]);
TEXT 📖 仅展示
7

▶ 示例

C
#include <stdio.h>

void print_matrix(int (*m)[4], int rows) {
    for (int i = 0; i < rows; i++) {
        for (int j = 0; j < 4; j++) {
            printf("%3d", m[i][j]);
        }
        printf("\n");
    }
}

int main(void) {
    int matrix[2][4] = {
        {10, 20, 30, 40},
        {50, 60, 70, 80}
    };
    print_matrix(matrix, 2);

    const char *fruits[3] = {"apple", "banana", "cherry"};
    for (int i = 0; i < 3; i++) {
        printf("%s ", fruits[i]);
    }
    printf("\n");
    return 0;
}
▶ 试一试
TEXT 📖 仅展示
 10 20 30 40
 50 60 70 80
apple banana cherry
⚠️ 记忆口诀:int *a[4] 是指针数组(数组里装指针),int (*a)[4] 是数组指针(指针指向数组)。


3. 函数指针与回调

函数名就是函数的入口地址。函数指针可以保存这个地址,实现"延迟调用"或"回调"。

(1) 函数指针声明

C
int (*pf)(int, int);

表示 pf 指向一个接收两个 int、返回 int 的函数。

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

int main(void) {
    int (*pf)(int, int) = add;
    printf("%d\n", pf(3, 5));
    pf = sub;
    printf("%d\n", pf(10, 4));
    return 0;
}
TEXT 📖 仅展示
8
6

(2) 回调机制

把函数指针作为参数传给另一个函数,让后者在合适的时机"回调"它。

C
void process(int *arr, int len, int (*transform)(int)) {
    for (int i = 0; i < len; i++) {
        arr[i] = transform(arr[i]);
    }
}

int double_it(int n) { return n * 2; }
int negate(int n) { return -n; }

int main(void) {
    int data[4] = {1, 2, 3, 4};
    process(data, 4, double_it);
    for (int i = 0; i < 4; i++) printf("%d ", data[i]);

    printf("\n");
    process(data, 4, negate);
    for (int i = 0; i < 4; i++) printf("%d ", data[i]);
    return 0;
}
TEXT 📖 仅展示
2 4 6 8
-2 -4 -6 -8

▶ 示例

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

int cmp_asc(const void *a, const void *b) {
    return *(int *)a - *(int *)b;
}

int cmp_desc(const void *a, const void *b) {
    return *(int *)b - *(int *)a;
}

void sort_and_print(int *arr, int n, int (*cmp)(const void *, const void *)) {
    qsort(arr, n, sizeof(int), cmp);
    for (int i = 0; i < n; i++) {
        printf("%d ", arr[i]);
    }
    printf("\n");
}

int main(void) {
    int nums[5] = {42, 7, 19, 3, 55};
    sort_and_print(nums, 5, cmp_asc);

    int nums2[5] = {42, 7, 19, 3, 55};
    sort_and_print(nums2, 5, cmp_desc);
    return 0;
}
▶ 试一试
TEXT 📖 仅展示
3 7 19 42 55
55 42 19 7 3
🔥 标准库的 qsort 就是函数指针回调的经典应用——你只需提供比较规则,排序算法不用管。


4. const 与指针的三种组合

const 和指针搭配有三种位置,语义各不相同。

(1) 指向 const 的指针

C
const int *p;
int const *p;

不能通过 p 修改指向的值,但 p 本身可以指向别处。

C
int a = 10, b = 20;
const int *p = &a;
printf("%d\n", *p);
p = &b;
printf("%d\n", *p);
TEXT 📖 仅展示
10
20

(2) const 指针

C
int * const p = &a;

p 不能改指向,但可以通过 p 修改值。

C
int a = 10;
int * const p = &a;
*p = 99;
printf("%d\n", a);
TEXT 📖 仅展示
99

(3) 指向 const 的 const 指针

C
const int * const p = &a;

既不能改指向,也不能通过 p 改值——最严格的只读。

💡 速记方法:看 const* 左边还是右边。左边修饰数据,右边修饰指针本身。


5. void 指针

void * 是"通用指针",可以指向任意类型,但使用前必须强制转换。

C
int a = 42;
double b = 3.14;
void *p;

p = &a;
printf("%d\n", *(int *)p);

p = &b;
printf("%.2f\n", *(double *)p);
TEXT 📖 仅展示
42
3.14

void * 的典型用途:

⚠️ 不能对 void * 解引用,编译器不知道指向的数据有多大。必须先转换成具体类型的指针。

❓ 常见问题

Q 指针数组和数组指针怎么快速区分?
A 看变量名先和谁结合——先和 [] 结合是数组(指针数组),先和 * 结合是指针(数组指针),用括号改变优先级即可。
Q 函数指针和函数名有什么区别?
A 函数名就是函数地址,是常量;函数指针是变量,可以保存不同函数的地址,实现运行时切换调用目标。
Q const int *pint const *p 一样吗?
A 完全一样,都表示指向 const int 的指针,不能通过 p 修改数据,但 p 可以改指向。
Q void 指针能做指针运算吗?
A 不能。编译器不知道 void 指向的数据大小,p+1 步长不确定,必须先转换成具体类型再运算。

📖 小节

📝 作业

  1. 编写函数 void swap_ptr(int **a, int **b),交换两个指针的指向,在 main 中验证交换后各自指向的值
  2. 声明一个函数指针数组 int (*ops[4])(int,int),分别存放加、减、乘、除函数,根据用户输入的下标调用对应运算
  3. 编写通用打印函数 void print_any(void *data, char type),type 为 'i' 打印 int,'d' 打印 double,'s' 打印字符串
Web-Tutorial.com

Web-Tutorial 技术团队

由多位开发者共同维护的编程教程平台。每篇教程由对应领域的开发者编写和审核,确保内容准确可靠。如发现任何问题,欢迎向我们反馈。

100%

🙏 帮我们做得更好

我们是刚上线的编程教程站,几个人的小团队,精力有限。页面虽经检查,难免还有疏漏——链接失效、排版错乱、内容有误、语言生硬……

如果您发现了,麻烦告诉我们,我们会在收到反馈后第一时间进行修复,再次感谢您的光临 🙏