3. 指针与数组:数组名的指针含义、通过指针遍历数组、指针与数组名的区别
数组和指针,在C语言里就像一对双胞胎。长得像,性格却不同。很多初学者在这上面栽跟头,说实话,我当年也迷糊过一阵子。
今天咱们就把这事儿彻底掰扯清楚。
3.1 数组名到底是个啥?
先看一段最基础的代码:
int arr[5] = {10, 20, 30, 40, 50};
printf("%p\n", arr); // 打印数组名
printf("%p\n", &arr[0]); // 打印首元素地址
你猜怎么着?这两个值一模一样。
数组名,说白了就是数组首元素的地址。它是一个地址常量,不是变量。这一点非常关键。
核心结论:arr 等价于 &arr[0],类型是 int*(指向 int 的指针)。
但注意,有例外情况。当数组名被 sizeof 操作符包围时,它代表整个数组:
int arr[5] = {10, 20, 30, 40, 50};
printf("%zu\n", sizeof(arr)); // 输出 20(5个int × 4字节)
printf("%zu\n", sizeof(&arr[0])); // 输出 8(指针大小,64位系统)
嗯,这里要注意:sizeof(arr) 返回的是整个数组占用的字节数,而不是指针的大小。这是数组名和指针最明显的区别之一。
3.2 通过指针遍历数组
我个人习惯用指针遍历数组,尤其是在嵌入式开发中。为什么?因为指针操作更底层,效率更高,而且能让你对内存布局有更清晰的认识。
来看一个典型的例子:
int arr[5] = {10, 20, 30, 40, 50};
int *p = arr; // p指向数组首元素
for (int i = 0; i < 5; i++) {
printf("arr[%d] = %d\n", i, *(p + i));
}
这里 *(p + i) 等价于 p[i],也等价于 arr[i]。你想想看,指针的加减运算其实就是在内存中"跳"字节。
我在项目中遇到过这样一个场景:需要从一个环形缓冲区中读取数据。用指针遍历比用数组下标方便得多:
uint8_t buffer[256];
uint8_t *read_ptr = buffer;
uint8_t *write_ptr = buffer;
// 写入数据
*write_ptr++ = data_byte;
if (write_ptr >= buffer + 256) {
write_ptr = buffer; // 绕回
}
// 读取数据
uint8_t value = *read_ptr++;
if (read_ptr >= buffer + 256) {
read_ptr = buffer;
}
你看,指针的自增和比较操作,让环形缓冲区的实现变得非常自然。
3.3 指针与数组名的区别
很多人觉得数组名就是指针,其实不对。我列个表,一目了然:
| 特性 | 数组名 | 指针变量 |
|---|---|---|
| 本质 | 地址常量 | 地址变量 |
| 能否赋值 | 不能(arr = something 是错的) | 可以(p = something 是对的) |
| sizeof 含义 | 整个数组的大小 | 指针本身的大小(4或8字节) |
| 取地址操作 | &arr 得到整个数组的地址(类型不同) | &p 得到指针变量的地址 |
| 作为函数参数 | 退化为指针 | 就是指针 |
这里有个坑,我曾经踩过:
我曾经在代码里写过 arr++,想着让数组名"移动"到下一个元素。结果编译器直接报错。数组名是常量,不能自增。你得用指针变量才行。
正确的做法是:
int arr[5] = {1, 2, 3, 4, 5};
int *p = arr; // 把数组名赋给指针变量
p++; // 指针可以自增,现在指向 arr[1]
3.4 二维数组与指针
二维数组的数组名,指向的是第一行的地址。说白了,它是一个"数组的数组"。
int matrix[3][4] = {
{1, 2, 3, 4},
{5, 6, 7, 8},
{9, 10, 11, 12}
};
// matrix 的类型是 int (*)[4],指向一个包含4个int的数组
int (*row)[4] = matrix; // row指向第一行
for (int i = 0; i < 3; i++) {
for (int j = 0; j < 4; j++) {
printf("%2d ", *(*(row + i) + j));
}
printf("\n");
}
这里 *(*(row + i) + j) 等价于 matrix[i][j]。你想想看,row + i 跳过了 i 行,*(row + i) 得到第 i 行的首地址,再加 j 就是第 j 个元素。
我在项目中处理图像数据时经常这么用。一张 640×480 的灰度图,用二维数组表示,指针操作比下标快不少。
3.5 避坑指南
最后,分享几个我实际工作中遇到的坑:
- 数组作为函数参数会退化为指针:在函数内部用 sizeof 得到的是指针大小,不是数组大小。所以通常要额外传一个长度参数。
- 不要对数组名使用 && 操作:&arr 得到的是整个数组的地址,虽然数值上等于 arr,但类型不同。&arr + 1 会跳过整个数组。
- 指针加减的单位是元素大小:int* p 加 1,地址增加 4 字节(假设 int 占 4 字节)。不是加 1 个字节。
小技巧:如果你不确定指针加减后指向哪里,可以用 printf 打印地址值来验证。我调试指针相关 bug 时,这招屡试不爽。
3.6 知识结构图
下面这张图总结了本章的核心逻辑,建议你多看几遍:
好了,关于指针和数组的关系,就讲到这里。记住一句话:数组名是地址常量,指针是地址变量。搞懂这个,后面再学指针数组、数组指针、函数指针什么的,就轻松多了。
公众号:蓝海资料掘金营,微信deep3321