一、指针数组与数组指针:两个容易搞混的兄弟
说实话,指针数组和数组指针这两个概念,是我在教C语言时被问得最多的问题。很多初学者甚至工作两三年的工程师,一提到这两个词就开始犯迷糊。我自己刚入行时也踩过这个坑——有一次在嵌入式项目中,因为把数组指针声明错了,导致整个数据采集系统跑飞,排查了整整一个下午。
今天我们就彻底把它们掰扯清楚。
1.1 指针数组:数组里装的是指针
先看指针数组。说白了,它就是一个数组,只不过数组里的每个元素都是指针。
声明方式:
int *arr[5]; // 指针数组:arr是一个数组,有5个元素,每个元素都是int*类型
怎么理解?你想想看,[]的优先级比*高,所以arr先和[5]结合,说明它是一个数组。数组的元素类型是int *,也就是指向整型的指针。
我在项目中遇到过这样一个场景:需要管理多个动态分配的缓冲区,每个缓冲区大小不同。用指针数组就特别方便:
#include <stdio.h>
#include <stdlib.h>
int main() {
char *str_arr[3]; // 指针数组,每个元素指向一个字符串
str_arr[0] = (char *)malloc(20);
str_arr[1] = (char *)malloc(30);
str_arr[2] = (char *)malloc(10);
sprintf(str_arr[0], "Hello");
sprintf(str_arr[1], "Embedded World");
sprintf(str_arr[2], "C");
for (int i = 0; i < 3; i++) {
printf("%s\n", str_arr[i]);
free(str_arr[i]);
}
return 0;
}
核心记忆点:指针数组的本质是「数组」,里面存的是指针。
1.2 数组指针:指向数组的指针
数组指针就反过来了。它是一个指针,指向的是一个数组。
声明方式:
int (*ptr)[5]; // 数组指针:ptr是一个指针,指向一个包含5个int元素的数组
注意看,这里加了括号。(*ptr)说明ptr首先是一个指针,然后它指向的类型是int [5],也就是一个长度为5的整型数组。
为什么会这样?因为[]优先级高,如果不加括号,int *ptr[5]就成了指针数组。所以括号是关键。
我曾经在调试一个二维数组的传参问题时,就靠数组指针搞定的。来看个例子:
#include <stdio.h>
void print_matrix(int (*matrix)[3], int rows) {
for (int i = 0; i < rows; i++) {
for (int j = 0; j < 3; j++) {
printf("%d ", matrix[i][j]);
}
printf("\n");
}
}
int main() {
int arr[2][3] = {{1, 2, 3}, {4, 5, 6}};
// arr是二维数组,arr[0]的类型是int (*)[3],即数组指针
print_matrix(arr, 2);
return 0;
}
核心记忆点:数组指针的本质是「指针」,它指向一个数组。
1.3 对比表格:一目了然
| 概念 | 声明 | 本质 | sizeof结果 | 典型用途 |
|---|---|---|---|---|
| 指针数组 | int *p[5] |
数组,元素是指针 | 5 * sizeof(int*) | 字符串数组、动态缓冲区管理 |
| 数组指针 | int (*p)[5] |
指针,指向数组 | sizeof(int*) | 二维数组传参、矩阵操作 |
小技巧:读声明时从变量名开始,向右看遇到[]就是数组,向左看遇到*就是指针。比如int *p[5],从p向右看到[5],所以是数组。
1.4 应用场景:什么时候用哪个?
指针数组的应用场景:
- 命令行参数处理:
main(int argc, char *argv[])中的argv就是指针数组,每个元素指向一个参数字符串。 - 字符串查找表:比如错误码对应的错误信息,用指针数组存起来,通过索引快速访问。
- 动态对象管理:多个动态分配的对象,用指针数组统一管理,方便遍历和释放。
数组指针的应用场景:
- 二维数组传参:函数需要接收一个二维数组时,用数组指针作为参数,明确指定列数。
- 矩阵运算:处理固定大小的矩阵时,数组指针能让代码更清晰。
- 多维数组的逐行处理:通过数组指针遍历二维数组的每一行。
避坑指南:我曾经在项目里把int (*p)[5]写成了int *p[5],结果编译器没报错,但运行时数据全乱了。因为前者p+1跳过一个数组(20字节),后者p+1只跳过一个指针(4或8字节)。这个区别在嵌入式开发中尤其致命。
1.5 知识体系图
下面这张图帮你理清指针数组和数组指针的关系:
1.6 一个综合例子
最后,我写一个综合例子,把指针数组和数组指针都用上。这个例子模拟了一个简单的成绩管理系统:
#include <stdio.h>
// 用数组指针接收二维数组
void print_scores(int (*scores)[4], int students) {
for (int i = 0; i < students; i++) {
printf("学生%d: ", i+1);
for (int j = 0; j < 4; j++) {
printf("%d ", scores[i][j]);
}
printf("\n");
}
}
int main() {
// 二维数组:3个学生,每人4门课成绩
int scores[3][4] = {
{85, 90, 78, 92},
{88, 76, 95, 89},
{91, 82, 87, 94}
};
// 指针数组:存储每个学生的名字
char *names[3] = {"张三", "李四", "王五"};
printf("成绩单:\n");
for (int i = 0; i < 3; i++) {
printf("%s: ", names[i]);
}
printf("\n\n");
// 调用函数,传入数组指针
print_scores(scores, 3);
return 0;
}
嗯,这个例子虽然简单,但把两个概念都用上了。names是指针数组,存了三个字符串指针;print_scores的参数scores是数组指针,指向一个4列的二维数组。
我个人习惯在写代码时,遇到这种容易混淆的地方,就加个注释。比如:
int *p[5]; // 指针数组:p是数组
int (*q)[5]; // 数组指针:q是指针
这样过几个月回头看,也不会搞混。
总结一下:指针数组和数组指针,一个存指针,一个指数组。记住「先看变量名右边」这个原则,你就能轻松区分。实际开发中,指针数组常用于管理多个独立的数据块,数组指针则擅长处理二维数组的传参和遍历。
公众号:蓝海资料掘金营,微信deep3321