3、指针与数组:数组名作为指针、指针遍历数组、指针与数组的区别、数组指针与指针数组
数组和指针,这对冤家在C语言里纠缠了几十年。我刚开始学的时候,总觉得它们就是一回事。后来在项目里踩了坑才明白——嗯,它们长得像,但骨子里完全不同。
这一节,咱们就把这层窗户纸捅破。
3.1 数组名就是指针吗?
很多人说「数组名就是指针」。这话对,也不对。
先看个例子:
int arr[5] = {1, 2, 3, 4, 5};
int *p = arr; // 这行能编译通过
你看,arr可以直接赋值给int*。所以数组名确实可以当作指针用。但别急着下结论——试试这个:
printf("sizeof(arr) = %zu\n", sizeof(arr)); // 输出 20(5个int)
printf("sizeof(p) = %zu\n", sizeof(p)); // 输出 8(64位系统)
为什么会这样?
因为arr是数组名,sizeof(arr)返回整个数组的大小。而p是指针,sizeof(p)只返回指针本身的大小。
核心结论:数组名在大多数表达式中会「退化」为指向首元素的指针。但有两个例外——sizeof和取地址运算符&。
我个人习惯把数组名理解成「带有数组大小信息的指针常量」。它不能自增自减,不能重新赋值。你试试arr++?编译器直接报错。
3.2 用指针遍历数组
既然数组名能当指针用,那用指针遍历数组就顺理成章了。
int arr[] = {10, 20, 30, 40, 50};
int *p = arr;
for (int i = 0; i < 5; i++) {
printf("arr[%d] = %d, *(p+%d) = %d\n", i, arr[i], i, *(p + i));
}
输出结果完全一样。但指针方式有个好处——灵活。比如你想从中间开始遍历:
int *mid = arr + 2; // 指向 arr[2]
printf("%d\n", *mid); // 输出 30
我在项目中遇到过一种场景:需要把数组的一部分传给函数处理。用指针就特别方便:
void process(int *start, int count) {
for (int i = 0; i < count; i++) {
start[i] *= 2; // 这里用下标操作,可读性更好
}
}
// 调用:只处理后3个元素
process(arr + 2, 3);
小技巧:用指针遍历时,我习惯同时维护一个「结束指针」而不是计数器。这样代码更简洁:
int *end = arr + 5;
for (int *p = arr; p < end; p++) {
printf("%d ", *p);
}
3.3 指针与数组的区别
说白了,数组是「一块连续的内存」,指针是「一个存放地址的变量」。它们的内存布局完全不同。
| 对比项 | 数组 | 指针 |
|---|---|---|
| 内存分配 | 编译时分配,固定大小 | 运行时分配,可指向任意位置 |
| 存储内容 | 实际数据 | 地址值 |
| 赋值操作 | 不能整体赋值 | 可以重新指向 |
| sizeof结果 | 整个数组字节数 | 指针本身字节数 |
| 作为函数参数 | 退化为指针 | 传递指针副本 |
我曾经在代码里写过这样的bug:
// 错误示范
void init_array(int arr[]) {
arr = malloc(10 * sizeof(int)); // 这改不了外面的arr!
}
数组作为函数参数时,已经退化成指针了。你修改形参的值,影响不了实参。要改,得传指针的指针。
避坑指南:我曾经在嵌入式项目里用sizeof(arr)计算数组长度,结果arr是函数参数,sizeof返回了指针大小(8字节),而不是数组大小。从那以后,我只要把数组传给函数,就同时传一个长度参数。
3.4 数组指针 vs 指针数组
这两个概念,名字就差一个字,意思天差地别。你想想看:
- 指针数组:一个数组,里面存的是指针。先有数组,元素是指针。
- 数组指针:一个指针,指向一个数组。先有指针,指向的是数组。
看声明方式就明白了:
int *p_arr[5]; // 指针数组:5个int*类型的元素
int (*arr_p)[5]; // 数组指针:指向一个包含5个int的数组
优先级规则:[]优先级高于*,所以int *p_arr[5]先被解释成数组,元素类型是int*。而(*arr_p)加了括号,先是指针,再指向int[5]。
指针数组的典型用法:
char *fruits[] = {"apple", "banana", "cherry"};
for (int i = 0; i < 3; i++) {
printf("%s\n", fruits[i]); // 每个元素是字符串指针
}
数组指针的典型用法——指向二维数组的行:
int matrix[3][4] = {{1,2,3,4}, {5,6,7,8}, {9,10,11,12}};
int (*row)[4] = matrix; // row指向第一行(4个int)
for (int i = 0; i < 3; i++) {
for (int j = 0; j < 4; j++) {
printf("%2d ", row[i][j]); // 用行指针访问
}
printf("\n");
}
记忆口诀:「指针数组」——数组里存指针;「数组指针」——指针指向数组。看最后两个字,就是核心。
3.5 知识结构图
下面这张图帮你理清本章的核心逻辑:
这张图把四个知识点串起来了。你从上往下看,先理解数组名的退化规则,再学会用指针遍历,然后搞清楚数组和指针的本质区别,最后攻克数组指针和指针数组这对易混概念。
嗯,这一节的内容就到这里。记住一句话:数组不是指针,但数组名可以当指针用。搞清楚了这一点,后面再学动态内存和多级指针,就会轻松很多。