C++ 指向二维数组的指针的类型是什么?

C++ 指向二维数组的指针的类型是什么?,c++,arrays,c++11,pointers,multidimensional-array,C++,Arrays,C++11,Pointers,Multidimensional 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_ar

我知道以下情况不正确:

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的类型是什么,以及下面发生了什么吗?

好吧,数组实际上在任何地方使用时都会衰减为指针。因此,代码片段中自然也会出现衰退

但只有“最外层”数组维度衰减为指针。由于数组是行主数组,因此指针类型为
int(*)[3]
,它是指向一维数组而不是二维数组的指针。它指向第一行

如果您希望
ptr
的推断改为指向数组的指针,请使用运算符的地址:

auto ptr = &arr;
现在
ptr
int(*)[2][3]

中的

auto ptr = arr;
arr
以正常方式衰减为指向其第一个元素的指针;相当于

auto ptr = &arr[0];
由于
arr[0]
是由三个
int
s组成的数组,因此
ptr
a
int(*)[3]
-是指向
int[3]
的指针

另一个arr
以完全相同的方式衰减,因此

ptr = another_arr;
赋值的两边都有类型
int(*)[3]
,对于任何类型
T
,都可以将
T*
赋值给
T*

指向
arr
本身的指针的类型为
int(*)[2][3]

如果需要指向数组的指针而不是指向数组第一个元素的指针,则需要使用
&

auto ptr = &arr; 
[…]的类型是什么

您是否已经尝试让编译器告诉您表达式的类型

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;
}
乍一看似乎不太可读(与其他一些语言相比),但如果有疑问,可能会有所帮助。它很紧凑,但人们可能很快就会习惯。编码依赖于编译器,如果您使用的是gcc,您可以阅读以了解如何使用

编辑: 对一些
简单的\u cpp\u 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;
}
#包括
#包括
#包括
#包括
std::string simple\u cpp\u name(const std::type\u info&ti)
{
///简化代码摘自“第29章Demanling”
/// https://gcc.gnu.org/onlinedocs/libstdc++/手动/ext_demanling.html
char*realname=abi::uucxa_demangle(ti.name(),0,0,0);
std::string name=realname;
免费(域名);
返回名称;
}

将向您显示
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]
的索引解释为“取数组中(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;
        }
    }

    // Cleanup
    for (int i=0; i<2; i++)
    {
        delete[] ptrptr[i];
        ptrptr[i] = nullptr;
    }
    delete[] ptrptr;
    ptrptr = nullptr;

    return 0;
}
请注意,对于
arr
,内存地址总是增加4个字节,但是对于
ptrptr
,在值3和4之间有24个字节的跳跃


简单赋值无法创建类型
int**
所需的指针指向指针结构,这就是为什么在上述程序中需要循环的原因。它所能做的最好的事情是将
int[2][3]
类型分解为指向该数组中某一行的指针,即
int(*)[3]
。这就是您的
auto ptr=arr结束为

如果这是一个愚蠢的问题,很抱歉,但是如果我不使用关键字
auto
,我应该如何在开始时声明
ptr
的类型?谢谢。@R.Xin-语法有点复杂
int(*ptr)[3]=arr
如果需要经常重复这样的声明,大多数人都会使用typedef。@R.Xin如果你想像StoryTeller建议的那样使用typedef,我建议
typedef int MyArr[3];MyArr*ptr@user2079303-隐藏指针语义的坏处非常好@说书人我修正了评论,谢谢。数组大小与类型分离的事实与我的直觉相反。我认为
使用MyArr=int[3]
会更好。是的,我确实尝试过,但我没有理解运行时特定的代码。@R.Xin毕竟,
自动ptr
处于间接级别,与原始声明不同,这似乎很容易看出。啊,直到现在我才看到你已经展示了arr记忆的不知足,很高兴看到这个深入的解释:-)
#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