在C语言中,指针是存储内存地址的变量,可修改指向;数组是连续存储相同类型数据的集合,其数组名在表达式中会退化为指向首元素的常量指针(如arr等价于&arr[0]),因此数组访问(如arr[i])与指针操作(如*(arr+i))等价。关键区别在于:指针数组(如int *p[3])是元素为指针的数组,而数组指针(如int (*p)[3])是指向整个数组的指针,混淆二者易导致错误。需注意指针不可未初始化、数组不可越界,且数组名作为常量不可修改(如arr++非法),这些是正确使用指针与数组的核心要点。

一、基本概念

1. 指针

  • 定义:指针是存储内存地址的变量,可以指向任何类型的数据。
  • 特点
    • 指针是变量,可以修改指针的值(即改变它指向的内存地址)
    • 指针有类型,决定了它指向的数据类型和移动时的"步长"(如int*指针移动1位,地址增加4字节)
  • 声明类型 *指针名; 例如:int *p;

2. 数组

  • 定义:数组是一组相同类型数据的有序集合。
  • 特点
    • 内存上连续存储:数组元素在内存中占据一块连续的空间
    • 数组名是常量:数组名代表数组首元素的地址,但不能被修改(不能给数组名赋值)
  • 声明类型 数组名[大小]; 例如:int arr[5];

二、指针与数组的关系

1. 核心联系

  • 数组名的本质:数组名在大多数情况下会被隐式转换为指向数组首元素的指针(常量指针)。
    • int arr[5] = {1,2,3,4,5};
    • int *p = arr; 等价于 int *p = &arr[0];
  • 访问方式等价
    • arr[i] 等价于 *(arr + i)
    • p[i] 等价于 *(p + i)

2. 函数参数传递

  • 当数组作为函数参数传递时,会退化为指向首元素的指针:
    • void func(int a[])void func(int *a) 完全等价
    • 函数内部无法通过 sizeof(a) 获取数组长度(只能得到指针大小)

三、指针数组与数组指针的区别

特性指针数组数组指针
本质数组指针
定义类型 *数组名[大小];类型 (*指针名)[大小];
示例int *ptr[3];int (*p)[3];
含义一个包含3个int指针的数组一个指向包含3个int元素的数组的指针
访问方式ptr[i](*p)[i]

关键区别

  • 指针数组的元素是独立的指针变量
  • 数组指针是指向整个数组的指针

四、常见应用场景

1. 通过指针遍历数组

int arr[5] = {1, 2, 3, 4, 5};
int *p = arr;
for (int i = 0; i < 5; i++) {
    printf("%d ", *(p + i)); // 或 p[i]
}

2. 指针数组存储多个字符串

char *names[3] = {"Alice", "Bob", "Charlie"};
for (int i = 0; i < 3; i++) {
    printf("%s\n", names[i]);
}

3. 指针数组存储函数指针

void func1() { printf("Func1\n"); }
void func2() { printf("Func2\n"); }
void (*funPtr[2])(void) = {func1, func2};

for (int i = 0; i < 2; i++) {
    funPtr[i](); // 调用函数
}

4. 数组指针处理二维数组

int arr[3][5] = {
    {1, 2, 3, 4, 5},
    {6, 7, 8, 9, 10},
    {11, 12, 13, 14, 15}
};
int (*p)[5] = arr; // p指向包含5个int的一维数组

printf("%d\n", *((*p) + 1)); // 访问arr[0][1] = 2
printf("%d\n", *((*(p + 1)) + 2)); // 访问arr[1][2] = 8

五、常见错误与注意事项

1. 指针与数组的主要区别

特性指针数组
存储内容存储一个地址存储一组同类型数据
可修改性指针本身的值可修改数组名是常量,不可修改
sizeof结果指针大小(32位系统4字节)数组总字节数
内存分配需手动指向已分配的内存定义时自动分配连续内存块

2. 常见错误

  • 错误1:访问未初始化的指针

    int *p;
    printf("%d", *p); // 未初始化的指针,导致未定义行为
    
  • 错误2:数组越界

    int arr[5];
    printf("%d", arr[5]); // 越界访问
    
  • 错误3:空指针解引用

    int *p = NULL;
    printf("%d", *p); // 空指针解引用,导致程序崩溃
    
  • 错误4:混淆指针数组与数组指针

    int *p[3]; // 指针数组
    int (*p)[3]; // 数组指针
    

六、深入理解

1. 为什么"数组名是常量指针"?

  • 数组名代表数组首元素的地址,但这个地址是常量,不能被修改
  • 例如:arr++ 会编译错误,因为数组名是常量

2. 指针与数组的"等价性"

  • 数组名在表达式中会被转换为指向首元素的指针
  • 但数组名本身不是指针,而是一个常量地址

3. 指针数组与二维数组的区别

  • 指针数组:每个元素是一个指针,指向独立的内存区域,不一定连续

    char *str[3] = {"hello", "world", "c"};
    
  • 二维数组:所有元素在内存中连续存储

    char str[3][6] = {"hello", "world", "c"};
    

七、总结

C语言中指针和数组是紧密关联但又本质不同的概念:

  • 指针是变量,可以修改指向的地址
  • 数组是连续内存块,数组名是常量地址
  • 指针数组是数组,每个元素是指针
  • 数组指针是指针,指向一个数组

理解指针与数组的关系是掌握C语言的关键,特别是在处理字符串、多维数组和动态内存管理时。正确区分指针数组和数组指针,避免常见的内存错误,是编写高效、安全C程序的基础。