C++二维数组与数组指针详解:地址运算、定义规则及函数传参
本文详细解析C++中二维数组的内存模型,解释二维数组名代表首行地址的原因,说明数组指针的定义方法与行列初始化约束。通过代码实例对比指针数组与二维数组的本质区别,并演示两种主流的二位数组函数传参技巧,帮助开发者消除概念混淆。
1、二维数组的内存地址特性
理解二维数组 int arr[3][3] 的核心在于区分“地址值”与“步长”。虽然 arr、arr[0] 和 &arr[0][0] 在数值上相等,但它们的类型含义不同:
arr是二维数组的地址(指向首行),其步长为整行的字节数(例如3个int)。arr[0]是第一行的名称,等价于首元素地址,步长为单个元素的字节数(1个int)。
以下代码用于验证这种地址递增的差异:
#include <stdio.h>
#define ROW 3
#define COLUMN 3
int main()
{
int arr[ROW][COLUMN] = {0};
printf("arr = %p\n", arr);
printf("arr[0] = %p\n", arr[0]);
printf("&arr[0][0] = %p\n", &arr[0][0]);
printf("\n\n");
printf("arr + 1 = %p\n", arr + 1); // 跳过一整行 (3*4 bytes)
printf("arr[0] + 1 = %p\n", arr[0] + 1); // 跳过一个元素 (1*4 bytes)
printf("&arr[0][0] + 1 = %p\n", &arr[0][0] + 1);
printf("&arr[0][1] = %p\n", &arr[0][1]);
return 0;
}
运行结果清晰显示:arr + 1 的偏移量远大于 arr[0] + 1。这证明了 arr 确实是首行(一维数组)的地址,而非首元素 arr[0][0] 的地址。
第1步:正确定义数组指针保存二维数组
由于 arr 本质上是包含5个int的一维数组的地址,定义数组指针时必须指定列数。对于 int arr[3][5],正确的数组指针定义为:
int (*parr)[5] = arr;
解析逻辑:(*) 表示这是一个指针,指向一个拥有 [5] 个元素的数组,每个元素类型为 int。
第2步:识别复杂声明的含义
针对复杂声明 int (*parr[2])[3],需注意 [] 优先级高于 *,因此它不是“包含两个指针的数组”,而是:
parr是一个数组,有2个元素。- 这些元素是
int (*)[3]类型的指针,即“数组指针”。 - 它们指向的元素组包含3个int整数。
代码验证如下:
#include <stdio.h>
int main()
{
int arr1[3] = {1,2,3};
int arr2[3] = {2,3,4};
int (*parr[2])[3] = {&arr1, &arr2}; // 数组指针数组
printf("&arr1 = %p\n&arr2 = %p\n",&arr1,&arr2);
for (int i = 0; i < 2; i++) {
printf("parr[%d] = %p\n",i,parr[i]);
}
return 0;
}
运行结果为:
2、数组指针与二维数组的使用约束
再次强调:在二维数组 int arr[][N] 中,第一维(行数)可省略,第二维(列数)不可省略。这是因为编译器必须知道每一行的长度才能正确计算索引偏移量。
// 合法:允许省略行数
int arr[][3] = {{1,2,3},{1,2,3}};
// 非法:不允许省略列数
int arr[2][] = {{1,2,3},{1,2,3}}; // 编译错误
第3步:二维数组作为函数参数
将二维数组传递给函数时,通常采用以下两种方式,核心在于确保行/列大小的确定性以进行边界访问。
方式A:显式声明数组形式
这是最常用的方式,形参直接声明为数组。注意必须在函数内或外部宏定义控制循环边界。
#define ROW 2
#define COLUMN 3
void arrReset(int arr[ROW][COLUMN], int row, int col) {
for (int i = 0; i < row; i++) {
for (int j = 0; j < col; j++) {
arr[i][j] = 0;
}
}
}
int main()
{
int arr[ROW][COLUMN] = {{1,2,3},{1,2,3}};
arrReset(arr, ROW, COLUMN);
// 打印结果验证归零...
return 0;
}
方式B:使用指针形式传参
使用数组指针可以更灵活地处理不同类型的二维数组,避免在函数签名中硬编码列数限制。
#define ROW 2
#define COLUMN 3
void arrReset(int(*parr)[COLUMN], int row, int col) {
for (int i = 0; i < row; i++) {
for (int j = 0; j < col; j++) {
// 以下写法在语义上是等价的:
// parr[i][j] = 0;
// (*(parr + i))[j] = 0;
*(*(parr + i) + j) = 0;
}
}
}
注意:在使用指针访问元素时,*(parr[i] + j) 是完全合法的,因为 parr[i] 解引用后即为该行首元素地址。arrReset 依然需要传入实际的行数和列数以辅助循环控制。
排错:不要混淆指针数组与二维数组
初学者常犯的一个错误是将“指向指针的数组”强行赋给“二维数组”。这两者在内存结构中是不同的:
- 二维数组:一块连续的内存空间。
- 指针数组:存储多个独立内存块地址的数组,这些内存块可能不连续。
错误的尝试示例:
int arr1[3] = {1,2,3};
int arr2[3] = {4,5,6};
int* arr3[2] = {arr1, arr2}; // 指针数组,存放的是地址
int arr4[2][3] = arr3; // 编译错误!无法直接用指针数组初始化二维数组
以上就是关于C++二维数组与数组指针的详细解析,更多关于C++基础语法的资料请关注本站其它相关文章!
本站声明
本文内容仅供参考,不代表本站观点。转载或引用请注明出处。如发现内容问题,请通过联系我们页面反馈。













