指针数组与指向数组的指针有何区别?

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

核心思想一句话总结

  • 指针数组:它是一个数组,数组里的每个元素都是一个指针
    • int *arr[5]; -> 一个包含5个 int* 类型元素的数组。
  • 指向数组的指针:它是一个指针,这个指针指向了一个数组
    • int (*parr)[5]; -> 一个指向 int[5] 类型数组的指针。

记住这个核心区别,下面我们展开细说。

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

指针数组

定义

指针数组,顾名思义,是一个数组,它的每个元素都是一个指针,最常见的是字符串数组,即一个字符指针数组。

语法

类型 *数组名[元素个数];

char *str_array[4];

  • [] 的优先级高于 ,str_array 先和 [4] 结合,表示 str_array 是一个包含4个元素的数组。
  • 然后剩下的 char * 表示这个数组的每个元素都是一个指向 char 类型的指针(也就是字符串字面量的首地址)。

内存模型

想象一下,它就像一个架子,架子上放了4个标签(指针),每个标签都指向一个字符串(一个连续的内存空间)。

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

代码示例

#include <stdio.h>
int main() {
    // 定义一个指针数组,它包含3个 char* 指针
    char *fruits[3] = {
        "Apple",  // "Apple" 是一个字符串字面量,其首地址被赋给 fruits[0]
        "Banana", // "Banana" 的首地址被赋给 fruits[1]
        "Cherry"  // "Cherry" 的首地址被赋给 fruits[2]
    };
    // 遍历指针数组
    // 注意:这里我们直接使用 %s,因为 fruits[i] 是一个 char* 指针,printf会从它指向的地址开始打印字符,直到遇到'\0'
    for (int i = 0; i < 3; i++) {
        printf("Fruit %d: %s\n", i, fruits[i]);
        // 也可以这样理解:
        // printf("Fruit %d: %s\n", i, *(fruits + i));
    }
    // 访问第一个字符串的第二个字符
    printf("The second letter of the first fruit is: %c\n", fruits[0][1]); // 输出 'p'
    return 0;
}

主要用途

  1. 存储多个字符串:这是指针数组最经典的应用场景,比如存储文件名、命令行参数等。
  2. 管理多个不同类型的变量:虽然不如字符串常见,但也可以创建一个 void* 指针数组来管理不同类型的数据。

指向数组的指针

定义

指向数组的指针,它本身是一个指针,但它不指向单个变量,而是指向一个完整的数组,在处理多维数组时,这个概念非常有用。

语法

类型 (*指针名)[元素个数];

int (*parr)[5];

  • 是必须的,它强制 先和 parr 结合,表示 parr 是一个指针。
  • 然后剩下的 int [5] 表示这个指针指向一个包含5个 int 元素的数组。

内存模型

它就像一个“大指针”,这个指针的“步长”不是一个 int 的大小,而是一整个数组的大小(比如5个 int 的大小)。

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

代码示例

#include <stdio.h>
int main() {
    // 定义一个包含3个元素的数组,每个元素本身又是一个包含5个int的数组
    int matrix[3][5] = {
        {1, 2, 3, 4, 5},
        {6, 7, 8, 9, 10},
        {11, 12, 13, 14, 15}
    };
    // 定义一个指向包含5个int的数组的指针
    int (*p)[5]; // p 是一个指针,指向一个 int[5] 的数组
    // 让 p 指向 matrix 的第一行
    // matrix 在表达式中会“退化”为其首元素地址,即第一行的地址,类型是 int(*)[5]
    p = matrix;
    // 遍历矩阵
    // 外层循环遍历行数 (3行)
    for (int i = 0; i < 3; i++) {
        // 内层循环遍历列数 (5列)
        for (int j = 0; j < 5; j++) {
            // 访问方式 1:使用下标法 (p[i][j])
            printf("%d ", p[i][j]);
            // 访问方式 2:使用指针算术 (p + i) 指向第 i 行
            // *(p + i) 是对第 i 行数组进行解引用,得到第 i 行的首元素地址
            // ( *(p + i) ) + j 指向第 i 行第 j 列的元素
            // printf("%d ", *(*(p + i) + j));
        }
        printf("\n");
    }
    // 指针算术的步长演示
    printf("\nDemonstrating pointer arithmetic:\n");
    printf("Address of p: %p\n", (void*)p);
    printf("Address of p + 1: %p\n", (void*)(p + 1));
    printf("Difference: %ld bytes\n", (char*)(p + 1) - (char*)p);
    // 输出会是 20 (5 * sizeof(int)),说明 p+1 跳过了整整一行(5个int)
    return 0;
}

主要用途

  1. 作为函数参数:当需要将一个多维数组作为参数传递给函数时,使用指向数组的指针作为形参是最高效、最标准的方式,它能让函数正确地计算内存偏移。
  2. 处理多维数组:在遍历和操作多维数组时,它提供了一种比下标法更底层、更灵活的访问方式。

对比与总结

特性 指针数组 (int *p[5]) 指向数组的指针 (int (*p)[5])
本质 数组 指针
数组元素 每个元素都是一个指针 (int*) 指针本身指向一个完整的数组 (int[5])
内存模型 一组分散的指针,指向各自的数据 一个单一的指针,指向一块连续的内存(一个数组)
语法 类型 *数组名[大小]; 类型 (*指针名)[大小];
步长 p + 1 的步长是 sizeof(int*) (一个指针的大小) p + 1 的步长是 sizeof(int[5]) (整个数组的大小)
主要用途 存储多个字符串、管理多个不同类型变量 作为函数参数传递多维数组、操作多维数组
类比 一个装满遥控器的架子 一个可以控制整个电视面板的遥控器

常见误区与陷阱

混淆 int* a[5]int (*a)[5]

这是最经典的混淆点。运算符优先级是关键:

  • int *a[5] -> int *(a[5]) -> a 是一个数组,其元素是 int*
  • int (*a)[5] -> a 是一个指针,指向 int[5]

int *p[N] 去处理多维数组

// 错误用法示例
int matrix[3][5] = { ... };
int *p[3]; // 定义一个指针数组
// 尝试让它指向 matrix 的每一行
for (int i = 0; i < 3; i++) {
    p[i] = matrix[i]; // matrix[i] 是第 i 行的首元素地址,类型是 int*
}
// 这样看起来可以工作...
printf("%d\n", p[0][1]); // 输出 2
// ...p 和 matrix 的关系是松散的,p 中的指针只是“借用”了 matrix 的空间。
// matrix 生命周期结束,p 中的指针就会变成“野指针”。
// 而指向数组的指针 `int (*p)[5] = matrix;` 则与 matrix 有更强的逻辑关联。

虽然技术上可以这样做,但它破坏了多维数组的连续性,容易出错,使用 int (*p)[N] 是更健壮、更符合逻辑的方式。

  • 指针数组一个装着指针的盒子,适合用来管理一组独立的字符串或对象。
  • 指向数组的指针一个能指向整个盒子的遥控器,适合用来操作一个完整的、连续的数据块,尤其是多维数组。

理解这两者的区别是掌握 C 语言指针进阶用法的关键一步,多看代码、多动手练习,很快就能熟练运用它们了。

-- 展开阅读全文 --
头像
织梦tags标签自定义标题
« 上一篇 2025-12-28
DedeCMS是什么?适合建站吗?
下一篇 » 2025-12-28

相关文章

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

目录[+]