指向二维数组的指针的类型是什么?
What is the type of a pointer to a 2D array?
我知道以下是不正确的:
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 的类型是什么,下面发生了什么?
好吧,当几乎无处不在时,数组会退化为指针。因此,您的代码片段中自然也会发生衰退。
但只有 "outer-most" 数组维度衰减为指针。由于数组是行优先的,因此您最终得到 int (*)[3]
作为指针类型,它是指向一维数组的指针,而不是二维数组。它指向第一个 "row".
如果您希望 ptr
的推导成为指向数组 的指针 ,则使用寻址运算符:
auto ptr = &arr;
现在 ptr
是 int(*)[2][3]
。
在
auto ptr = arr;
arr
以正常方式衰减为指向其第一个元素的指针;相当于
auto ptr = &arr[0];
因为 arr[0]
是一个包含三个 int
的数组,所以 ptr
是一个 int (*)[3]
- 指向 int[3]
.[=28= 的指针]
another_arr
以完全相同的方式衰减,所以在
ptr = another_arr;
赋值的两边都是int (*)[3]
类型,对于任何类型T
.
,您可以将T*
赋值给T*
指向 arr
的指针本身具有类型 int(*)[2][3]
。
如果你想要一个指向数组的指针而不是指向数组第一个元素的指针,你需要使用&
:
auto ptr = &arr;
What is the type of [...]
您是否已经尝试让编译器告诉您表达式的类型?
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
功能进行一些实验,例如这种基本的 hack
#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
相同的类型,即 int [2][3]
.
首先,让我们看看为什么不能将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]
这样的索引解释为"take the value that is at position (1*2 + 0) in the array".
但是对于指向指针的指针(int**
),预期指向指针的指针包含单个内存地址或内存地址数组,并且this/these地址指向(一个)其他单个整数值或整数数组。假设我们将数组 arr
复制到 int **ptrptr
。在记忆中,它看起来像这样:
ptrptr: [0x203F0B20, 0x203F17D4]
0x203F0B20: [ 1, 2, 3 ]
0x203F17D4: [ 4, 5, 6 ]
因此除了实际的 int
数据外,还必须为数组的每一行存储一个额外的指针。不是将两个索引转换为单个数组查找,而是必须通过进行第一个数组查找 ("take the second value in ptrptr to get an int*"),然后是另一个数组查找 ("take the first value in the array at the address held by the previously obtained 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;
最终的结果。
我知道以下是不正确的:
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 的类型是什么,下面发生了什么?
好吧,当几乎无处不在时,数组会退化为指针。因此,您的代码片段中自然也会发生衰退。
但只有 "outer-most" 数组维度衰减为指针。由于数组是行优先的,因此您最终得到 int (*)[3]
作为指针类型,它是指向一维数组的指针,而不是二维数组。它指向第一个 "row".
如果您希望 ptr
的推导成为指向数组 的指针 ,则使用寻址运算符:
auto ptr = &arr;
现在 ptr
是 int(*)[2][3]
。
在
auto ptr = arr;
arr
以正常方式衰减为指向其第一个元素的指针;相当于
auto ptr = &arr[0];
因为 arr[0]
是一个包含三个 int
的数组,所以 ptr
是一个 int (*)[3]
- 指向 int[3]
.[=28= 的指针]
another_arr
以完全相同的方式衰减,所以在
ptr = another_arr;
赋值的两边都是int (*)[3]
类型,对于任何类型T
.
T*
赋值给T*
指向 arr
的指针本身具有类型 int(*)[2][3]
。
如果你想要一个指向数组的指针而不是指向数组第一个元素的指针,你需要使用&
:
auto ptr = &arr;
What is the type of [...]
您是否已经尝试让编译器告诉您表达式的类型?
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
功能进行一些实验,例如这种基本的 hack
#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
相同的类型,即 int [2][3]
.
首先,让我们看看为什么不能将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]
这样的索引解释为"take the value that is at position (1*2 + 0) in the array".
但是对于指向指针的指针(int**
),预期指向指针的指针包含单个内存地址或内存地址数组,并且this/these地址指向(一个)其他单个整数值或整数数组。假设我们将数组 arr
复制到 int **ptrptr
。在记忆中,它看起来像这样:
ptrptr: [0x203F0B20, 0x203F17D4]
0x203F0B20: [ 1, 2, 3 ]
0x203F17D4: [ 4, 5, 6 ]
因此除了实际的 int
数据外,还必须为数组的每一行存储一个额外的指针。不是将两个索引转换为单个数组查找,而是必须通过进行第一个数组查找 ("take the second value in ptrptr to get an int*"),然后是另一个数组查找 ("take the first value in the array at the address held by the previously obtained 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;
最终的结果。