欢迎光临
我们一直在努力

第14节 指针与数组的关系与底层内存布局

承接上文:上一节我们入门指针,搞懂了内存地址、指针变量、指针步长机制,也初步接触数组名隐式转换。本节专门深挖指针和数组容易混淆的底层差异,理清二者不是等价关系,只是存在转换规则。讲解数组内存排布、指针偏移遍历、数组退化为指针的完整场景,拆解sizeof经典陷阱,夯实底层内存认知,为后续函数传参、动态内存打好基础。

学习说明

绝大多数新手会产生一个误区:数组就是指针,指针就是数组。 数组和指针可以互相转换,但它们本质不同。数组是一块连续分配的内存空间;指针是单独的变量,仅用来存放地址。只有在特定表达式场景下,数组名才会隐式转换成首元素指针。本节核心就是区分二者本质差异,看懂内存布局。

本节学习目标

  • 掌握一维数组连续内存布局,看懂元素地址排布
  • 彻底理解数组名隐式转换规则,分清什么时候数组不会转指针
  • 深入理解数组作为函数参数时,退化(衰减)成指针的底层现象
  • 熟练使用指针遍历数组,巩固指针步长运算
  • 再次强化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 隐式转换为首元素指针,依靠指针步长定位元素。


    二、数组名隐式转换规则(重点)

    数组名在大部分表达式场景,会自动隐式转换为数组首元素的地址(指针)。 但是有两个例外场景,数组名不会发生转换:

  • 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(整个数组地址):" << &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 *p[4]; —— 指针数组:数组,数组里每一个元素都是int*指针
  • int (*p)[4]; —— 数组指针:指针,这个指针专门指向一个大小为4的int数组
  • 记忆技巧:看括号。[]优先级高于*。

    • 不带括号:先和[]结合 → 数组,存放指针
    • 带括号:先和*结合 → 指针,指向数组

    int arr[4] = {1,2,3,4};
    int (*p)[4] = &arr; //数组指针,接收整个数组地址
    cout << (*p)[0] << endl; // 输出:1
    cout << (*p)[3] << endl; // 输出:4

    数组指针最常见的工程场景:作为函数参数接收二维数组的行。


    六、本节核心坑点总结

  • ❌ 错误:数组就是指针。✅正确:数组是连续内存块;指针是存放地址的独立变量。仅特定场景数组名隐式转为指针。
  • sizeof(arr)只有在数组原生定义作用域,才能获取数组总字节;数组传进函数退化为指针,sizeof拿到指针大小。
  • arr(隐式转换后为int*)和 &arr(类型为int(*)[4])地址数值相同,但类型不同,+1步长差异巨大。
  • 函数形参int arr[]本质就是int* arr,长度信息丢失,必须手动传长度。
  • arr[i] 等价于 *(arr+i),下标只是语法糖,底层依靠指针运算。

  • 思考题

  • 数组和指针本质区别是什么?什么时候数组名不会隐式转换为指针?
  • 同样执行+1操作,arr和&arr偏移的字节数为什么不一样?
  • 数组传递给函数之后,为什么无法在函数内部用sizeof计算数组长度?
  • 辨析:int *p[5] 和 int (*p)[5] 分别是什么?
  • 实战题:写函数,接收数组与长度,使用指针遍历,将数组所有元素翻倍。
  • 代码阅读题:
  • int arr[5] = {1,2,3,4,5};
    int* p = arr;
    cout << sizeof(arr) << endl;
    cout << sizeof(p) << endl;

    请问两行输出分别是什么?

    下期预告

    下一节学习:指针作为函数参数的工程实战用法

    赞(0)
    未经允许不得转载:171主机测评 » 第14节 指针与数组的关系与底层内存布局
    分享到: 更多 (0)

    评论 抢沙发

    • 昵称 (必填)
    • 邮箱 (必填)
    • 网址