指向2D数组的指针的类型是什么?

时间:2017-07-31 07:25:14

标签: 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_arr;

任何人都可以解释在第二段代码中分配给ptr的类型是什么,以及下面发生了什么?

4 个答案:

答案 0 :(得分:26)

嗯,当几乎在任何地方使用时,数组都会衰减到指针。很自然地,你的代码片段中也会出现衰减。

但它只是"最外面的"衰减到指针的数组维度。由于数组是行主要的,因此最终使用int (*)[3]作为指针类型,它是指向一维数组的指针,而不是二维数组。它指向第一个"行"。

如果您希望ptr的推论是指向的指针,请使用address-of运算符:

auto ptr = &arr;

现在ptrint(*)[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]之类的索引解释为“取值”这是在数组“。

中的位置(1 * 2 + 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;使rfaarr的{​​{1}}具有相同的类型。