8. 指针与数组.md 9.7 KB

8 指针与数组

8.1 完整概念讲解

数组名的本质

数组名在大多数情况下会退化(decay)为指向数组首元素的指针。但有两个例外:

  1. sizeof 运算符:返回整个数组的大小
  2. & 取地址运算符:返回指向数组的指针

    int arr[5] = {1, 2, 3, 4, 5};
    int *p = arr;  // arr退化为&arr[0]
    

sizeof和&对数组的特殊行为

  • sizeof(arr) 返回整个数组占用的字节数(元素个数 × 元素大小)
  • &arr 返回指向整个数组的指针类型(int (*)[5]
  • 当数组作为函数参数时,sizeof会丢失,必须额外传递长度

指针遍历数组

int arr[5] = {10, 20, 30, 40, 50};
int *p = arr;
for (int i = 0; i < 5; i++) {
    printf("%d ", *(p + i));  // 或 p[i]
}

指针算术

  • p + n:地址增加 n * sizeof(*p) 字节
  • p - q:两指针间的元素个数(前提是同一数组内)
  • p++:指针指向下一个元素

数组指针 vs 指针数组

int (*p)[10];  // 数组指针:指向包含10个int的数组
int *p[10];    // 指针数组:包含10个int指针的数组

二维数组与指针

int arr[3][4];
int (*p)[4] = arr;  // 数组指针指向二维数组的行

数组传参退化为指针

void func(int arr[]) {
    // arr在这里是指针,sizeof(arr) = sizeof(int*)
}

8.2 核心API/语法

数组与指针的关系表

表达式 数组行为 指针行为
arr 退化为 &arr[0] 指向首元素
arr[i] *(arr + i) *(p + i)
&arr 指向整个数组的指针 指针的地址
sizeof(arr) 整个数组大小 指针大小

指针运算

int *p = arr;
p + n;    // 地址增加 n * sizeof(int)
p - q;    // 两指针间的元素个数
p++;      // 指向下一个元素
p--;      // 指向上一个元素

数组传参

// 错误:数组退化为指针,sizeof丢失
void wrong_func(int arr[]) {
    int size = sizeof(arr);  // 错误:这是sizeof(int*)
}

// 正确:额外传递长度
void correct_func(int arr[], int size) {
    for (int i = 0; i < size; i++) {
        printf("%d ", arr[i]);
    }
}

8.3 代码示例(完整可编译,附gcc命令)

示例1:数组名退化与sizeof

#include <stdio.h>

int main() {
    int arr[5] = {1, 2, 3, 4, 5};
    int *p = arr;

    printf("sizeof(arr) = %zu\n", sizeof(arr));  // 20 (5*4)
    printf("sizeof(p) = %zu\n", sizeof(p));      // 4 或 8 (指针大小)
    printf("sizeof(int) = %zu\n", sizeof(int));  // 4

    printf("arr = %p\n", (void*)arr);
    printf("&arr[0] = %p\n", (void*)&arr[0]);
    printf("p = %p\n", (void*)p);

    // &arr 的类型是 int (*)[5]
    int (*arr_ptr)[5] = &arr;
    printf("arr_ptr = %p\n", (void*)arr_ptr);
    printf("sizeof(arr_ptr) = %zu\n", sizeof(arr_ptr));  // 指针大小

    return 0;
}

编译命令:gcc -o array_decay array_decay.c

示例2:指针算术与数组遍历

#include <stdio.h>

int main() {
    int arr[5] = {10, 20, 30, 40, 50};
    int *p = arr;

    // 方式1:下标访问
    for (int i = 0; i < 5; i++) {
        printf("arr[%d] = %d\n", i, arr[i]);
    }

    // 方式2:指针解引用
    for (int i = 0; i < 5; i++) {
        printf("*(p + %d) = %d\n", i, *(p + i));
    }

    // 方式3:指针自增
    p = arr;  // 重置指针
    while (p < arr + 5) {
        printf("%d ", *p);
        p++;
    }
    printf("\n");

    // 指针相减
    p = &arr[3];
    int *q = &arr[1];
    printf("p - q = %td\n", p - q);  // 2

    return 0;
}

编译命令:gcc -o pointer_arithmetic pointer_arithmetic.c

示例3:数组指针 vs 指针数组

#include <stdio.h>

int main() {
    // 指针数组:数组元素是指针
    int a = 1, b = 2, c = 3;
    int *ptr_arr[3] = {&a, &b, &c};

    printf("指针数组:\n");
    for (int i = 0; i < 3; i++) {
        printf("ptr_arr[%d] = %d\n", i, *ptr_arr[i]);
    }

    // 数组指针:指针指向数组
    int arr1[3] = {10, 20, 30};
    int arr2[3] = {40, 50, 60};
    int (*arr_ptr)[3];

    arr_ptr = arr1;
    printf("\n数组指针指向arr1:\n");
    for (int i = 0; i < 3; i++) {
        printf("(*arr_ptr)[%d] = %d\n", i, (*arr_ptr)[i]);
    }

    arr_ptr = arr2;
    printf("数组指针指向arr2:\n");
    for (int i = 0; i < 3; i++) {
        printf("(*arr_ptr)[%d] = %d\n", i, (*arr_ptr)[i]);
    }

    return 0;
}

编译命令:gcc -o array_pointer_vs_pointer_array array_pointer_vs_pointer_array.c

示例4:二维数组与指针

#include <stdio.h>

void print_matrix(int (*mat)[4], int rows) {
    for (int i = 0; i < rows; i++) {
        for (int j = 0; j < 4; j++) {
            printf("%3d ", mat[i][j]);
        }
        printf("\n");
    }
}

int main() {
    int arr[3][4] = {
        {1, 2, 3, 4},
        {5, 6, 7, 8},
        {9, 10, 11, 12}
    };

    printf("二维数组遍历:\n");
    print_matrix(arr, 3);

    // 指针访问二维数组
    int *p = &arr[0][0];
    printf("\n指针遍历:\n");
    for (int i = 0; i < 12; i++) {
        printf("%d ", *(p + i));
        if ((i + 1) % 4 == 0) printf("\n");
    }

    return 0;
}

编译命令:gcc -o two_d_array two_d_array.c

示例5:数组传参的正确方式

#include <stdio.h>

// 错误方式:数组退化为指针
void wrong_func(int arr[]) {
    printf("wrong_func中sizeof(arr) = %zu\n", sizeof(arr));
}

// 正确方式1:传递长度
void correct_func(int arr[], int size) {
    printf("correct_func: ");
    for (int i = 0; i < size; i++) {
        printf("%d ", arr[i]);
    }
    printf("\n");
}

// 正确方式2:使用宏定义数组大小
#define ARRAY_SIZE(arr) (sizeof(arr) / sizeof(arr[0]))

void macro_func(int arr[], int size) {
    printf("macro_func: ");
    for (int i = 0; i < size; i++) {
        printf("%d ", arr[i]);
    }
    printf("\n");
}

int main() {
    int arr[5] = {1, 2, 3, 4, 5};
    int size = ARRAY_SIZE(arr);

    printf("main中sizeof(arr) = %zu\n", sizeof(arr));
    wrong_func(arr);
    correct_func(arr, size);
    macro_func(arr, size);

    return 0;
}

编译命令:gcc -o array_param array_param.c

8.4 注意事项与易错点

数组退化丢失大小信息

  • 数组作为函数参数时退化为指针,sizeof结果改变
  • 必须额外传递数组长度,或使用宏计算

指针算术越界

  • 指针指向数组外的内存是未定义行为
  • 不同数组间的指针比较是无意义的

数组指针与指针数组混淆

  • int (*p)[10] 是数组指针,指向包含10个int的数组
  • int *p[10] 是指针数组,包含10个int指针
  • 运算符优先级:[] 高于 *,所以 *p[10] 是指针数组

二维数组传参

  • 二维数组传参时必须指定第二维大小
  • 函数参数应声明为 int arr[][N]int (*arr)[N]

数组名不是指针

  • 数组名是常量,不能自增或赋值
  • arr++ 是错误的,但 p++ 是合法的(p是指针变量)

指针比较的限制

  • 只有指向同一数组的指针才能进行有意义的比较
  • 不同数组间的指针比较是未定义行为

8.5 面试要点(3-5个Q&A)

Q1: 数组名在什么情况下不会退化为指针? A: 两种情况:1) sizeof 运算符作用于数组名时,返回整个数组的大小;2) & 取地址运算符作用于数组名时,返回指向整个数组的指针(类型为 int (*)[N])。

Q2: 指针算术中 p + 1 移动多少字节? A: 移动 sizeof(*p) 字节。对于 int *pp + 1 移动4字节(32位系统)或4字节(64位系统,因为int通常是4字节)。对于 double *pp + 1 移动8字节。

Q3: 如何正确地将数组传入函数? A: 有两种正确方式:1) 额外传递数组长度作为参数;2) 使用宏 sizeof(arr)/sizeof(arr[0]) 计算长度,但注意这只能在定义数组的作用域内使用。数组作为参数时会退化为指针,sizeof信息丢失。

Q4: int (*p)[10]int *p[10] 有什么区别? A: int (*p)[10] 是数组指针,p是一个指针,指向包含10个int的数组;int *p[10] 是指针数组,p是一个数组,包含10个int指针。运算符优先级:[] 高于 *,所以 *p[10] 等价于 *(p[10])

Q5: 二维数组在内存中如何存储?与指针有什么关系? A: 二维数组按行优先顺序存储在连续内存中。arr[i][j] 等价于 *(*(arr + i) + j)。可以用指向数组的指针 int (*p)[N] 来遍历二维数组,p + i 跳过i行,(*p)[j] 访问第j列。

8.6 完整示例程序

#include <stdio.h>
#include <stdlib.h>

void demonstrate_array_pointer() {
    int arr[3][4] = {
        {1, 2, 3, 4},
        {5, 6, 7, 8},
        {9, 10, 11, 12}
    };

    // 数组指针遍历
    int (*p)[4] = arr;
    printf("使用数组指针遍历二维数组:\n");
    for (int i = 0; i < 3; i++) {
        for (int j = 0; j < 4; j++) {
            printf("%3d ", p[i][j]);
        }
        printf("\n");
    }
}

void demonstrate_pointer_arithmetic() {
    int arr[5] = {10, 20, 30, 40, 50};
    int *p = arr;

    printf("指针算术演示:\n");
    printf("p = %p, *p = %d\n", (void*)p, *p);

    p += 2;
    printf("p + 2 = %p, *(p + 2) = %d\n", (void*)p, *p);

    p--;
    printf("p - 1 = %p, *(p - 1) = %d\n", (void*)p, *p);
}

void demonstrate_array_param(int arr[], int size) {
    printf("数组参数演示: ");
    for (int i = 0; i < size; i++) {
        printf("%d ", arr[i]);
    }
    printf("\n");
}

int main() {
    demonstrate_array_pointer();
    demonstrate_pointer_arithmetic();

    int arr[5] = {1, 2, 3, 4, 5};
    demonstrate_array_param(arr, 5);

    return 0;
}

编译命令:gcc -o array_demo array_demo.c