我知道以下内容不正确:
int arr[2][3] = {}; //some array initialization here
int** ptr;
ptr = arr;
但我很惊讶以下几行确实有用
int arr[2][3] = {}; //some array initialization here
auto ptr = arr;
int another_arr[2][3] = {}; //some array initialization here
ptr = another_arr;
任何人都可以解释在第二段代码中分配给ptr的类型是什么,以及下面发生了什么?
答案 0 :(得分:26)
嗯,当几乎在任何地方使用时,数组都会衰减到指针。很自然地,你的代码片段中也会出现衰减。
但它只是"最外面的"衰减到指针的数组维度。由于数组是行主要的,因此最终使用int (*)[3]
作为指针类型,它是指向一维数组的指针,而不是二维数组。它指向第一个"行"。
如果您希望ptr
的推论是指向的指针,请使用address-of运算符:
auto ptr = &arr;
现在ptr
是int(*)[2][3]
。
答案 1 :(得分:12)
在
auto ptr = arr;
arr
以正常方式衰减成指向其第一个元素的指针;它相当于
auto ptr = &arr[0];
由于arr[0]
是一个包含三个int
的数组,因此ptr
成为int (*)[3]
- 指向int[3]
的指针。
another_arr
以完全相同的方式衰减,所以在
ptr = another_arr;
作业的两边都有int (*)[3]
类型,您可以为任何类型T*
的{{1}}分配T*
。
指向T
的指针本身的类型为arr
。
如果你想要一个指向数组的指针而不是指向数组第一个元素的指针,你需要使用int(*)[2][3]
:
&
答案 2 :(得分:7)
首先,让我们看看为什么你不能将int arr[2][3]
分配给int **
。为了更容易可视化,我们将使用序列初始化您的数组,并考虑它在内存中的样子:
int arr[2][3] = {{1,2,3},{4,5,6}};
在内存中,数组数据存储为单个块,就像常规的1D数组一样:
arr: [ 1, 2, 3, 4, 5, 6 ]
变量arr
包含此块的起始地址,并且从其类型(int[2][3]
)开始,编译器知道将arr[1][0]
之类的索引解释为“取值”这是在数组“。
然而,对于指向指针(int**
),指望指针指向包含单个内存地址或内存地址数组,以及这个/这些地址指向(a)其他单个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;
最终的结果。
答案 3 :(得分:2)
[...]
的类型是什么
您是否已经尝试让编译器告诉您表达式的类型?
int main()
{
int arr[2][3] = {{0,1,2}, {3,4,5}}; // <-- direct complete initialized here
auto ptr = arr; // <-- address assignment only
cout << "arr: " << typeid(arr).name() << endl;
cout << "ptr: " << typeid(ptr).name() << endl;
return 0;
}
我要承认输出
arr: A2_A3_i
ptr: PA3_i
乍一看似乎不太可读(与其他一些语言相比),但如有疑问,可能有所帮助。它非常紧凑,但很快就会习惯它。编码依赖于编译器,如果您使用的是gcc,您可以阅读Chapter 29. Demangling以了解其操作方法。
一些simple_cpp_name
函数的实验,就像这个基本的黑客
#include <typeinfo>
#include <cxxabi.h>
#include <stdlib.h>
#include <string>
std::string simple_cpp_name(const std::type_info& ti)
{
/// simplified code extracted from "Chapter 29. Demangling"
/// https://gcc.gnu.org/onlinedocs/libstdc++/manual/ext_demangling.html
char* realname = abi::__cxa_demangle(ti.name(), 0, 0, 0);
std::string name = realname;
free(realname);
return name;
}
将向您显示auto &rfa = arr;
使rfa
与arr
的{{1}}具有相同的类型。