承接上文:上一节我们入门指针,搞懂了内存地址、指针变量、指针步长机制,也初步接触数组名隐式转换。本节专门深挖指针和数组容易混淆的底层差异,理清二者不是等价关系,只是存在转换规则。讲解数组内存排布、指针偏移遍历、数组退化为指针的完整场景,拆解sizeof经典陷阱,夯实底层内存认知,为后续函数传参、动态内存打好基础。
学习说明
绝大多数新手会产生一个误区:数组就是指针,指针就是数组。 数组和指针可以互相转换,但它们本质不同。数组是一块连续分配的内存空间;指针是单独的变量,仅用来存放地址。只有在特定表达式场景下,数组名才会隐式转换成首元素指针。本节核心就是区分二者本质差异,看懂内存布局。
本节学习目标
一、一维数组底层内存布局
数组在内存中,元素是连续紧密排列的,中间没有空隙。
#include <iostream>
using namespace std;
int main()
{
int arr[4] = {10,20,30,40};
cout << "arr首地址:" << arr << endl;
cout << "&arr[0] = " << &arr[0] << endl;
cout << "&arr[1] = " << &arr[1] << endl;
cout << "&arr[2] = " << &arr[2] << endl;
cout << "&arr[3] = " << &arr[3] << endl;
return 0;
}
文字内存布局可视化:
地址 元素
0x1000 10 arr[0]
0x1004 20 arr[1]
0x1008 30 arr[2]
0x100C 40 arr[3]
int占4字节,相邻元素地址相差4字节。
数组下标本质:arr[i] = *(arr + i),此处 arr 隐式转换为首元素指针,依靠指针步长定位元素。
二、数组名隐式转换规则(重点)
数组名在大部分表达式场景,会自动隐式转换为数组首元素的地址(指针)。 但是有两个例外场景,数组名不会发生转换:
#include <iostream>
using namespace std;
int main()
{
int arr[4] = {10,20,30,40};
cout << "arr(隐式转换后为首元素地址):" << arr << endl;
cout << "&arr[0](首元素地址):" << &arr[0] << endl;
cout << "&arr(整个数组地址):" << &arr << endl;
cout << "sizeof(arr):" << sizeof(arr) << endl;
// 取消注释下面一行,观察编译器报错信息,可以验证类型不匹配
// int* p = &arr; // 错误:无法将 int(*)[4] 转换为 int*
return 0;
}
运行现象:arr 和 &arr 打印出来地址数值相同,但类型完全不一样。
- arr隐式转换后类型:int*,首元素地址;arr+1,向后偏移4字节,跳到下一个int元素
- &arr类型:int (*)[4] 数组指针;&arr+1,一次性偏移整个数组大小(16字节)
地址数值一样,但是类型不同,指针运算步长完全不同,这是高频考点。
三、数组作为函数参数:数组退化(衰减)成指针
这是工程代码最常见的场景:数组传入函数后,不再是数组,退化成指针变量。
#include <iostream>
using namespace std;
void test(int arr[])
{
// 函数内arr已经不是数组,是指针变量!
cout << "函数内sizeof(arr) = " << sizeof(arr) << endl;
}
int main()
{
int arr[4] = {10,20,30,40};
cout << "main中sizeof(arr) = " << sizeof(arr) << endl;
test(arr);
return 0;
}
输出(64位系统):
main中sizeof(arr) = 16
函数内sizeof(arr) = 8
说明: 函数形参写法 int arr[] 和 int* arr 完全等价。 数组传参时,只会把首元素地址拷贝进去,数组长度信息丢失。 👉 工程解决方案:额外增加一个形参,手动传入数组长度。
void test(int arr[], int len)
{
for(int i = 0; i < len; i++)
{
cout << arr[i] << " ";
}
}
四、指针遍历数组多种写法
#include <iostream>
using namespace std;
int main()
{
int arr[4] = {10,20,30,40};
int len = sizeof(arr)/sizeof(arr[0]);
int* p = arr;
//写法1:下标访问
for(int i=0;i<len;i++){
cout << arr[i] << " ";
}
cout << endl;
//写法2:指针偏移解引用
for(int i=0;i<len;i++){
cout << *(p+i) << " ";
}
cout << endl;
//写法3:指针自增正向遍历
p = arr;
for(int i=0;i<len;i++){
cout << *p << " ";
p++;
}
cout << endl;
//写法4:指针从后往前反向遍历
for(int* q = arr + len – 1; q >= arr; q–){
cout << *q << " ";
}
return 0;
}
四种写法底层等价。p++ 依靠指针步长自动跳到下一个元素。
五、易混概念初步区分:指针数组 vs 数组指针
只做入门区分,避免名字混淆,不深挖复杂用法:
记忆技巧:看括号。[]优先级高于*。
- 不带括号:先和[]结合 → 数组,存放指针
- 带括号:先和*结合 → 指针,指向数组
int arr[4] = {1,2,3,4};
int (*p)[4] = &arr; //数组指针,接收整个数组地址
cout << (*p)[0] << endl; // 输出:1
cout << (*p)[3] << endl; // 输出:4
数组指针最常见的工程场景:作为函数参数接收二维数组的行。
六、本节核心坑点总结
思考题
int arr[5] = {1,2,3,4,5};
int* p = arr;
cout << sizeof(arr) << endl;
cout << sizeof(p) << endl;
请问两行输出分别是什么?
下期预告
下一节学习:指针作为函数参数的工程实战用法


