首先,让我们看看为什么不能将 int arr[2][3]
分配给 int **
。为了更容易地进行可视化,我们将使用序列初始化您的数组,并考虑它在内存中的样子:
int arr[2][3] = {{1,2,3},{4,5,6}};
在内存中,数组数据被存储为单个块,就像普通的一维数组:
arr: [ 1, 2, 3, 4, 5, 6 ]
变量
arr
包含此块开头的地址,从其类型(
int[2][3]
)可以知道编译器将索引
arr [1] [0]
解释为“获取数组中位置为(1 * 2 + 0)的值”。但是对于指向指针(
int **
),预期指向指针的指针包含单个内存地址或内存地址数组,该/这些地址指向另一个单个int值或int数组。假设我们将数组
arr
复制到
int **ptrptr
中,那么在内存中它看起来像这样:
ptrptr: [0x203F0B20, 0x203F17D4]
0x203F0B20: [ 1, 2, 3 ]
0x203F17D4: [ 4, 5, 6 ]
所以除了实际的
int
数据外,每行数组还必须存储一个额外的指针。不是将两个索引转换为单个数组查找,而是通过进行第一次数组查找(“取ptrptr中的第二个值以获取int *”),然后不再进行另一个数组查找(“取先前获得的int *所持有地址处数组中的第一个值”)来执行访问。
下面是一个说明此过程的程序:
#include <iostream>
int main()
{
int arr[2][3] = {{1,2,3},{4,5,6}};
std::cout << "Memory addresses for int arr[2][3]:" << std::endl;
for (int i=0; i<2; i++)
{
for (int j=0; j<3; j++)
{
std::cout << reinterpret_cast<void*>(&arr[i][j]) << ": " << arr[i][j] << std::endl;
}
}
std::cout << std::endl << "Memory addresses for int **ptrptr:" << std::endl;
int **ptrptr = new int*[2];
for (int i=0; i<2; i++)
{
ptrptr[i] = new int[3];
for (int j=0; j<3; j++)
{
ptrptr[i][j] = arr[i][j];
std::cout << reinterpret_cast<void*>(&ptrptr[i][j]) << ": " << ptrptr[i][j] << std::endl;
}
}
for (int i=0; i<2; i++)
{
delete[] ptrptr[i];
ptrptr[i] = nullptr;
}
delete[] ptrptr;
ptrptr = nullptr;
return 0;
}
输出:
Memory addresses for int arr[2][3]:
0x7ecd3ccc0260: 1
0x7ecd3ccc0264: 2
0x7ecd3ccc0268: 3
0x7ecd3ccc026c: 4
0x7ecd3ccc0270: 5
0x7ecd3ccc0274: 6
Memory addresses for int **ptrptr:
0x38a1a70: 1
0x38a1a74: 2
0x38a1a78: 3
0x38a1a90: 4
0x38a1a94: 5
0x38a1a98: 6
注意观察内存地址,
arr
的地址每次增加 4 个字节,但是对于
ptrptr
来说,在值为 3 和 4 之间有一个跳跃的 24 个字节。
一个简单的赋值语句不能创建类型为
int **
所需的指向指针的结构,这就是为什么上面的程序需要循环的原因。它最好能做到的就是将类型为
int[2][3]
的数组衰变为该数组的一行的指针,即
int (*)[3]
。这就是您的
auto ptr = arr;
最终得到的结果。
auto
,我应该如何在开头声明ptr
的类型?谢谢。 - R.Xinint (* ptr )[3] = arr;
,如果需要经常重复这样的声明,大多数人都会使用typedef来简化。 - StoryTeller - Unslander Monicatypedef int MyArr[3]; MyArr* ptr;
。 - eerorikausing MyArr = int[3]
会更好。 - eerorika