C语言一维数组、指针数组、数组指针有何区别?

99ANYc3cd6
预计阅读时长 24 分钟
位置: 首页 C语言 正文

把每个概念看作一个“盒子”,我们要搞清楚这个盒子里装的是什么,以及如何使用这个盒子。

c语言 一维数组指针数组指针数组指针
(图片来源网络,侵删)

一维数组

这是最基础的概念。

  • 定义:一组相同类型的数据的集合,在内存中是连续存放的。
  • 声明类型 数组名[大小];
  • 本质:一个变量集合,数组名 arr 在大多数情况下会“衰变”为其首元素的地址,但它本身也代表整个数组。

示例代码:

#include <stdio.h>
int main() {
    // 声明一个包含5个int元素的一维数组
    int arr[5] = {10, 20, 30, 40, 50};
    // 1. 通过下标访问元素
    printf("arr[2] = %d\n", arr[2]); // 输出: 30
    // 2. 通过指针访问元素 (数组名arr会衰变为指向首元素的指针)
    int *p = arr; // 等同于 int *p = &arr[0];
    printf("*p = %d\n", *p);      // 输出: 10
    printf("*(p+1) = %d\n", *(p+1)); // 输出: 20
    // 3. arr和&p的区别 (关键!)
    // arr: 代表数组,&arr代表整个数组的地址
    // arr的值和&arr[0]的值相同,但类型和含义不同
    printf("arr 的地址: %p\n", (void*)arr);
    printf("&arr[0] 的地址: %p\n", (void*)&arr[0]);
    printf("&arr 的地址: %p\n", (void*)&arr);
    // 指针运算验证
    printf("arr + 1 的地址: %p\n", (void*)(arr + 1)); // 地址 + sizeof(int)
    printf("&arr + 1 的地址: %p\n", (void*)(&arr + 1)); // 地址 + sizeof(整个数组)
    return 0;
}

小结: 一维数组就是一个固定大小的、连续的内存块。


指针数组

这个名字有点误导性,它不是一个数组的指针,而是一个存放指针的数组

c语言 一维数组指针数组指针数组指针
(图片来源网络,侵删)
  • 定义:一个数组,它的每个元素都是一个指针。
  • 声明*数组名[大小]; 注意:[]的优先级高于,所以是先和数组名结合成一个数组,再由说明其元素是指针。
  • 本质:一个指针的集合,它是一个数组,所以它的元素在内存中是连续的,但每个指针指向的内存地址可以是任意的。

用途:常用于处理多个字符串(字符串字面量就是char*类型),或者管理一组不同位置的同类型变量。

示例代码:

#include <stdio.h>
int main() {
    // 声明一个包含3个int指针的数组
    int *ptr_arr[3];
    int a = 100;
    int b = 200;
    int c = 300;
    // 让数组的每个指针指向一个int变量
    ptr_arr[0] = &a;
    ptr_arr[1] = &b;
    ptr_arr[2] = &c;
    // 遍历指针数组,通过指针获取它指向的值
    for (int i = 0; i < 3; i++) {
        printf("*(ptr_arr[%d]) = %d\n", i, *(ptr_arr[i]));
    }
    // 更直观的写法
    printf("*(ptr_arr[0]) = %d\n", *ptr_arr[0]); // 输出: 100
    return 0;
}

内存模型示意图:

      +-------+      +-------+      +-------+
      |  100  |      |  200  |      |  300  |  <-- a, b, c 变量所在的内存
      +-------+      +-------+      +-------+
          ^               ^               ^
          |               |               |
      +-------+      +-------+      +-------+
      | &a    |      | &b    |      | &c    |  <-- 指针数组 ptr_arr 的元素
      +-------+      +-------+      +-------+
          ^               ^               |
          |               |               |
      +-----------------------------------+
      | ptr_arr[0] | ptr_arr[1] | ptr_arr[2] |  <-- 指针数组 ptr_arr 本身(连续内存)
      +-----------------------------------+

小结: 指针数组是一个数组,装满了指针。

c语言 一维数组指针数组指针数组指针
(图片来源网络,侵删)

数组指针

这个名字也容易混淆,它不是一个指针数组,而是一个指向数组的指针

  • 定义:一个指针,它指向一个完整的数组。
  • 声明(*指针名)[大小]; *注意:是必须的,它强制`先和指针名结合,说明这是一个指针,再[大小]`说明它指向的数组类型。**
  • 本质:一个指针,这个指针的类型是“指向一个包含N个XX类型元素的数组的指针”。

用途:在函数参数中传递多维数组,或者需要以“数组”为单位进行操作时使用。

示例代码:

#include <stdio.h>
int main() {
    int arr[5] = {10, 20, 30, 40, 50};
    // 声明一个数组指针,它指向一个包含5个int元素的数组
    int (*p_arr)[5] = &arr; // 注意是 &arr,取整个数组的地址
    // 使用数组指针访问元素
    // p_arr 指向整个数组,*p_arr 就代表这个数组本身
    // (*p_arr)[i] 就是对数组 arr[i] 的访问
    printf("(*p_arr)[2] = %d\n", (*p_arr)[2]); // 输出: 30
    // 指针运算
    // p_arr + 1 会跳过整个数组,指向下一个(不存在的)5个int的数组
    // (p_arr + 1) 的地址 = &arr 的地址 + sizeof(整个数组)
    printf("p_arr 的地址: %p\n", (void*)p_arr);
    printf("(p_arr + 1) 的地址: %p\n", (void*)(p_arr + 1));
    // 对比普通指针
    int *p = arr;
    printf("p 的地址: %p\n", (void*)p);
    printf("(p + 1) 的地址: %p\n", (void*)(p + 1)); // 地址 + sizeof(int)
    return 0;
}

小结: 数组指针是一个指针,它指向一个完整的数组。


指针数组指针

这是最复杂的一个,但只要拆解开来,就不难理解。

  • 定义:一个指针,它指向一个“指针数组”。
  • 声明*(*指针名)[大小]; 或者 (*指针名)[大小] 再加一个,我们可以分两步看。
  • 本质:一个指针,这个指针的类型是“指向一个包含N个XX类型指针的数组的指针”。

用途:相对少见,通常出现在更复杂的结构体嵌套或函数指针返回复杂类型时,一个典型场景是处理一个指针数组的数组,比如一个字符串数组的数组。

示例代码:

#include <stdio.h>
int main() {
    // 1. 先定义一个指针数组 (一个装满了char*的数组)
    char *strs[3] = {
        "Hello",
        "World",
        "C Language"
    };
    // 2. 再定义一个指针,让它指向这个指针数组
    // strs 是一个 char*[3] 类型的数组
    // &strs 是一个 char*(*)[3] 类型的地址(一个指向3个char指针的数组的指针)
    char *(*p_strs)[3] = &strs;
    // 3. 使用这个“指针数组指针”来访问内容
    // *p_strs 解引用后,就得到了 strs 数组本身
    // (*p_strs)[i] 就得到了 strs[i],也就是一个 char* (字符串)
    // (*p_strs)[i][j] 就得到了字符串的第j个字符
    printf("(*p_strs)[1] = %s\n", (*p_strs)[1]);      // 输出: World
    printf("*(*p_strs)[1] = %c\n", *(*p_strs)[1]);    // 输出: W
    // 通过指针运算访问第二个字符串
    // p_strs + 1 会指向下一个 char*[3] 的数组
    // *(p_strs + 1) 会得到下一个 char*[3] 的数组
    // (*(p_strs + 1))[0] 会得到那个数组的第一个 char*
    // 但我们的 strs 是最后一个了,所以这里只是演示语法
    // printf("(*(p_strs + 1))[0] = %s\n", (*(p_strs + 1))[0]); // 越界访问
    return 0;
}

内存模型示意图:

      +-------+      +-------+      +-------+
      | "Hello" |    | "World" |    | "C Language" | <-- 字符串常量
      +-------+      +-------+      +-------+
          ^               ^               |
          |               |               |
      +-------+      +-------+      +-------+
      |  &s1   |      |  &s2   |      |  &s3   |   <-- strs 指针数组的元素
      +-------+      +-------+      +-------+
          ^               ^               |
          |               |               |
      +-----------------------------------+
      |   strs[0]  |   strs[1]  |   strs[2]  |   <-- strs 指针数组本身
      +-----------------------------------+
                          ^
                          |
      +------------------|-----------------+
      | p_strs (存放 &strs 的地址)          |
      +-------------------------------------+

小结: 指针数组指针是一个指针,它指向的是一个“指针数组”。


总结与区分

名称 语法 本质 通俗解释
一维数组 int arr[5]; 变量集合 一排连续的抽屉,每个抽屉里放一个int。
指针数组 int *p_arr[3]; 指针的集合 一个架子,架子上放了3个标签,每个标签指向一个int。
数组指针 int (*p_arr)[5]; 指向数组的指针 一个遥控器,它控制的是一整排5个抽屉。
指针数组指针 int *(*p_p_arr)[3]; 指向指针数组的指针 一个遥控器,它控制的是一个放标签的架子。

*区分技巧:看中间的`[]`如何结合**

  1. *`int p[3];`**

    • p 先和 [3] 结合,是 p[3](一个数组)。
    • 再和 结合,是 *p[3](一个数组,其元素是 ,即指针)。
    • 指针数组
  2. *`int (p)[3];`**

    • p 先和 结合,是 *p(一个指针)。
    • 再和 [3] 结合,是 (*p)[3](一个指针,指向 [3] 数组)。
    • 数组指针
  3. `int p[3];`**

    • p 先和 [3] 结合,是 p[3](一个数组)。
    • 再和 结合,是 **p[3](一个数组,其元素是 ,即指向指针的指针)。
    • 二级指针数组
  4. `int (p)[3];`**

    • p 先和 结合,是 **p(一个指向指针的指针)。
    • 再和 [3] 结合,是 (**p)[3](一个指向指针的指针,这个指针指向 [3] 数组)。
    • 指向数组指针的指针,这和“指针数组指针” *(*p)[3] 在概念上非常接近,都是指向一个数组指针。

希望这个从简到繁的拆解和比喻能帮助你彻底理解这几个概念!

-- 展开阅读全文 --
头像
unsigned char 是什么?范围和用法有哪些?
« 上一篇 12-07
数据结构与算法C语言描述PDF哪里找?
下一篇 » 12-07

相关文章

取消
微信二维码
支付宝二维码

目录[+]