【C】数组和指针的关系
在 C 语言 和 C++ 中,数组和指针 有非常密切的关系。它们在某些情况下表现类似,但也有重要的区别。理解数组和指针的关系对于掌握低级内存操作和优化程序性能至关重要。
1. 数组和指针的基本关系
- 数组是一个 连续存储的元素集合,在内存中占据一段连续的地址空间。
- 数组的名称(如
arr
)在很多情况下会退化为指向数组第一个元素的指针。
1.1 数组名是一个指针的表现
假设有一个数组:
int arr[5] = {1, 2, 3, 4, 5};
-
数组名的含义:
arr
是一个常量指针,指向数组的第一个元素(&arr[0]
)。arr
的类型是int*
。
-
数组元素的访问:
arr[i]
等价于*(arr + i)
。- 例如:
printf("%d\n", arr[2]); // 输出 3 printf("%d\n", *(arr + 2)); // 等价于上面
-
地址的访问:
arr
是数组第一个元素的地址:&arr[0]
。&arr
是整个数组的地址,类型是int (*)[5]
,和&arr[0]
不同。- 示例:
printf("%p\n", arr); // 输出数组第一个元素的地址 printf("%p\n", &arr[0]); // 等价于 arr printf("%p\n", &arr); // 输出整个数组的地址(类型是 int (*)[5])
2. 数组和指针的区别
尽管数组名和指针表现很相似,但它们并不完全相同:
2.1 内存分配
- 数组:
- 数组是一个固定大小的内存块,在定义时分配内存。
- 例如:
int arr[5]; // 为数组分配了连续的 5 个 int 空间
- 指针:
- 指针是一个变量,它存储的是地址,可以动态指向不同的位置。
- 例如:
int* ptr; ptr = malloc(5 * sizeof(int)); // 动态分配 5 个 int 空间
2.2 数组名是常量,指针是变量
-
数组名是常量指针,不能重新赋值。
- 示例:
int arr[5]; arr = arr + 1; // 错误,数组名不能作为左值
- 示例:
-
指针是变量,可以随时指向其他地址。
- 示例:
int* ptr; int x = 10, y = 20; ptr = &x; // 指向 x ptr = &y; // 可以改变指向,指向 y
- 示例:
2.3 使用 sizeof 的区别
-
对数组和指针使用
sizeof
的结果不同:- 数组 返回整个数组的大小。
- 指针 返回指针变量本身的大小(通常是 4 或 8 字节,取决于系统架构)。
示例:
int arr[5] = {1, 2, 3, 4, 5}; int* ptr = arr; printf("%zu\n", sizeof(arr)); // 输出 20(5 个 int 的大小) printf("%zu\n", sizeof(ptr)); // 输出 8(指针本身的大小,假设是 64 位系统)
2.4 数组和指针的类型
- 数组名和指针的类型不同:
arr
的类型是int[5]
,退化后是int*
。ptr
的类型是int*
,可以自由赋值。
3. 数组和指针在函数中的关系
3.1 数组作为函数参数时会退化为指针
当数组作为函数参数传递时,数组会退化为指针传递,函数实际上接收到的是指向数组第一个元素的指针。
示例:
void printArray(int arr[], int size)
{
for (int i = 0; i < size; i++)
{
printf("%d ", arr[i]); // 等价于 *(arr + i)
}
}
int main()
{
int arr[5] = {1, 2, 3, 4, 5};
printArray(arr, 5); // 传递的是 int* 指针
return 0;
}
实际的函数签名
上述函数 void printArray(int arr[], int size)
实际等价于:
void printArray(int* arr, int size);
3.2 多维数组的函数参数
多维数组的第一级会退化为指针,但其余维度需要明确指定大小。
示例:
void print2DArray(int arr[][3], int rows)
{
for (int i = 0; i < rows; i++)
{
for (int j = 0; j < 3; j++)
{
printf("%d ", arr[i][j]); // 访问二维数组元素
}
printf("\n");
}
}
int main()
{
int arr[2][3] = {{1, 2, 3}, {4, 5, 6}};
print2DArray(arr, 2); // 传递的是 int (*)[3] 指针
return 0;
}
注意
- 函数参数中必须指定第二维(
3
),因为编译器需要知道每行的长度。
4. 结合使用数组和指针的技巧
4.1 动态分配多维数组
可以使用指针动态分配多维数组,而不是直接定义多维数组。
示例:
int** create2DArray(int rows, int cols)
{
int** arr = malloc(rows * sizeof(int*)); // 分配行指针
for (int i = 0; i < rows; i++)
{
arr[i] = malloc(cols * sizeof(int)); // 分配每行的数据
}
return arr;
}
4.2 遍历数组的指针操作
可以使用指针运算遍历数组,而不使用下标。
示例:
int arr[5] = {1, 2, 3, 4, 5};
int* ptr = arr; // 指向数组第一个元素
for (int i = 0; i < 5; i++)
{
printf("%d ", *(ptr + i)); // 使用指针访问元素
}
5. 小结
数组和指针的相似点:
- 数组名可以退化为指向第一个元素的指针。
- 数组元素可以通过下标或指针运算访问。
数组和指针的区别:
- 数组名是常量指针,指针变量可以自由赋值。
sizeof
操作在数组和指针上有不同的行为。- 数组有固定大小,指针可以动态指向不同的位置。