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 * 的典型用途:
malloc返回void *,赋值给任何类型的指针qsort的比较函数参数用const void *- 通用数据结构的实现(如泛型链表)
⚠️ 不能对
void * 解引用,编译器不知道指向的数据有多大。必须先转换成具体类型的指针。
❓ 常见问题
Q 指针数组和数组指针怎么快速区分?
A 看变量名先和谁结合——先和
[] 结合是数组(指针数组),先和 * 结合是指针(数组指针),用括号改变优先级即可。Q 函数指针和函数名有什么区别?
A 函数名就是函数地址,是常量;函数指针是变量,可以保存不同函数的地址,实现运行时切换调用目标。
Q
const int *p 和 int const *p 一样吗?A 完全一样,都表示指向 const int 的指针,不能通过 p 修改数据,但 p 可以改指向。
Q void 指针能做指针运算吗?
A 不能。编译器不知道 void 指向的数据大小,
p+1 步长不确定,必须先转换成具体类型再运算。📖 小节
- 二级指针
int **pp用于在函数中修改指针本身 - 指针数组
int *a[N]是装指针的数组;数组指针int (*p)[N]是指向数组的指针 - 函数指针
int (*pf)(int,int)保存函数地址,配合回调实现灵活设计 const在*左边修饰数据,在*右边修饰指针本身void *是通用指针,使用前须强制类型转换
📝 作业
- 编写函数
void swap_ptr(int **a, int **b),交换两个指针的指向,在 main 中验证交换后各自指向的值 - 声明一个函数指针数组
int (*ops[4])(int,int),分别存放加、减、乘、除函数,根据用户输入的下标调用对应运算 - 编写通用打印函数
void print_any(void *data, char type),type 为 'i' 打印 int,'d' 打印 double,'s' 打印字符串