首先,让我们看看为什么不能将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**),指针指针应该包含单个内存地址或内存地址数组,并且 this/these address(es) 指向(an) 其他单个 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;
}
}
// Cleanup
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; 的最终结果。