# 8 指针与数组 ## 8.1 完整概念讲解 ### 数组名的本质 数组名在大多数情况下会退化(decay)为指向数组首元素的指针。但有两个例外: 1. `sizeof` 运算符:返回整个数组的大小 2. `&` 取地址运算符:返回指向数组的指针 ```c int arr[5] = {1, 2, 3, 4, 5}; int *p = arr; // arr退化为&arr[0] ``` ### sizeof和&对数组的特殊行为 - `sizeof(arr)` 返回整个数组占用的字节数(元素个数 × 元素大小) - `&arr` 返回指向整个数组的指针类型(`int (*)[5]`) - 当数组作为函数参数时,sizeof会丢失,必须额外传递长度 ### 指针遍历数组 ```c 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 指针数组 ```c int (*p)[10]; // 数组指针:指向包含10个int的数组 int *p[10]; // 指针数组:包含10个int指针的数组 ``` ### 二维数组与指针 ```c int arr[3][4]; int (*p)[4] = arr; // 数组指针指向二维数组的行 ``` ### 数组传参退化为指针 ```c void func(int arr[]) { // arr在这里是指针,sizeof(arr) = sizeof(int*) } ``` ## 8.2 核心API/语法 ### 数组与指针的关系表 | 表达式 | 数组行为 | 指针行为 | | ------------- | ------------------ | ---------- | | `arr` | 退化为 `&arr[0]` | 指向首元素 | | `arr[i]` | `*(arr + i)` | `*(p + i)` | | `&arr` | 指向整个数组的指针 | 指针的地址 | | `sizeof(arr)` | 整个数组大小 | 指针大小 | ### 指针运算 ```c int *p = arr; p + n; // 地址增加 n * sizeof(int) p - q; // 两指针间的元素个数 p++; // 指向下一个元素 p--; // 指向上一个元素 ``` ### 数组传参 ```c // 错误:数组退化为指针,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 ```c #include 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:指针算术与数组遍历 ```c #include 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 指针数组 ```c #include 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:二维数组与指针 ```c #include 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:数组传参的正确方式 ```c #include // 错误方式:数组退化为指针 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 *p`,`p + 1` 移动4字节(32位系统)或4字节(64位系统,因为int通常是4字节)。对于 `double *p`,`p + 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 完整示例程序 ```c #include #include 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`