数组名在大多数情况下会退化(decay)为指向数组首元素的指针。但有两个例外:
sizeof 运算符:返回整个数组的大小& 取地址运算符:返回指向数组的指针
int arr[5] = {1, 2, 3, 4, 5};
int *p = arr; // arr退化为&arr[0]
sizeof(arr) 返回整个数组占用的字节数(元素个数 × 元素大小)&arr 返回指向整个数组的指针类型(int (*)[5])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++:指针指向下一个元素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*)
}
| 表达式 | 数组行为 | 指针行为 |
|---|---|---|
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]);
}
}
#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
#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
#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
#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
#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
int (*p)[10] 是数组指针,指向包含10个int的数组int *p[10] 是指针数组,包含10个int指针[] 高于 *,所以 *p[10] 是指针数组int arr[][N] 或 int (*arr)[N]arr++ 是错误的,但 p++ 是合法的(p是指针变量)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列。
#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