数组声明如何在C ++中工作?

时间:2014-10-24 22:27:48

标签: c++ arrays pointers multidimensional-array

我正在尝试理解在C ++中声明数组(一维或二维)的不同方法以及它们究竟返回的内容(指针,指针指针等)

以下是一些例子:

int A[2][2] = {0,1,2,3};
int A[2][2] = {{0,1},{2,3}};
int **A = new int*[2];
int *A = new int[2][2];

在每种情况下,A究竟是什么?它是一个指针,双指针?我A+1时会发生什么?这些都是宣告矩阵的有效方法吗?

另外,为什么第一个选项不需要第二组花括号来定义“列”?

7 个答案:

答案 0 :(得分:3)

在我写作的时候,你的答案看起来很多,但我还是可以发布我的答案,所以我觉得这不是一无所有......

(所有sizeof结果取自VC2012 - 32位构建,指针大小当然会在64位构建时加倍)

size_t f0(int* I);
size_t f1(int I[]);
size_t f2(int I[2]);

int main(int argc, char** argv)
{
    // A0, A1, and A2 are local (on the stack) two-by-two integer arrays
    // (they are technically not pointers)

    // nested braces not needed because the array dimensions are explicit [2][2]
    int A0[2][2] = {0,1,2,3};

    // nested braces needed because the array dimensions are not explicit,
    //so the braces let the compiler deduce that the missing dimension is 2
    int A1[][2] = {{0,1},{2,3}};

    // this still works, of course. Very explicit.
    int A2[2][2] = {{0,1},{2,3}};

    // A3 is a pointer to an integer pointer. New constructs an array of two
    // integer pointers (on the heap) and returns a pointer to the first one.
    int **A3 = new int*[2];
    // if you wanted to access A3 with a double subscript, you would have to
    // make the 2 int pointers in the array point to something valid as well
    A3[0] = new int[2];
    A3[1] = new int[2];
    A3[0][0] = 7;

    // this one doesn't compile because new doesn't return "pointer to int"
    // when it is called like this
    int *A4_1 = new int[2][2];

    // this edit of the above works but can be confusing
    int (*A4_2)[2] = new int[2][2];
    // it allocates a two-by-two array of integers and returns a pointer to
    // where the first integer is, however the type of the pointer that it
    // returns is "pointer to integer array"

    // now it works like the 2by2 arrays from earlier,
    // but A4_2 is a pointer to the **heap**
    A4_2[0][0] = 6;
    A4_2[0][1] = 7;
    A4_2[1][0] = 8;
    A4_2[1][1] = 9;


    // looking at the sizes can shed some light on subtle differences here
    // between pointers and arrays
    A0[0][0] = sizeof(A0);        // 16 // typeof(A0) is int[2][2] (2by2 int array, 4 ints total, 16 bytes)
    A0[0][1] = sizeof(A0[0]);     // 8  // typeof(A0[0]) is int[2] (array of 2 ints)

    A1[0][0] = sizeof(A1);        // 16 // typeof(A1) is int[2][2]
    A1[0][1] = sizeof(A1[0]);     // 8  // typeof(A1[0]) is int[2]

    A2[0][0] = sizeof(A2);        // 16 // typeof(A2) is int[2][2]
    A2[0][1] = sizeof(A2[0]);     // 8  // typeof(A1[0]) is int[2]

    A3[0][0] = sizeof(A3);        // 4 // typeof(A3) is int**
    A3[0][1] = sizeof(A3[0]);     // 4 // typeof(A3[0]) is int*

    A4_2[0][0] = sizeof(A4_2);    // 4 // typeof(A4_2) is int(*)[2] (pointer to array of 2 ints)
    A4_2[0][1] = sizeof(A4_2[0]); // 8 // typeof(A4_2[0]) is int[2] (the first array of 2 ints)
    A4_2[1][0] = sizeof(A4_2[1]); // 8 // typeof(A4_2[1]) is int[2] (the second array of 2 ints)
    A4_2[1][1] = sizeof(*A4_2);   // 8 // typeof(*A4_2) is int[2] (different way to reference the first array of 2 ints)

// confusion between pointers and arrays often arises from the common practice of
// allowing arrays to transparently decay (implicitly convert) to pointers

    A0[1][0] = f0(A0[0]); // f0 returns 4.
    // Not surprising because declaration of f0 demands int*

    A0[1][1] = f1(A0[0]); // f1 returns 4.
    // Still not too surprising because declaration of f1 doesn't
    // explicitly specify array size

    A2[1][0] = f2(A2[0]); // f2 returns 4.
    // Much more surprising because declaration of f2 explicitly says
    // it takes "int I[2]"

    int B0[25];
    B0[0] = sizeof(B0); // 100 == (sizeof(int)*25)
    B0[1] = f2(B0); // also compiles and returns 4.
    // Don't do this! just be aware that this kind of thing can
    // happen when arrays decay.

    return 0;
}

// these are always returning 4 above because, when compiled,
// all of these functions actually take int* as an argument
size_t f0(int* I)
{
    return sizeof(I);
}

size_t f1(int I[])
{
    return sizeof(I);
}

size_t f2(int I[2])
{
    return sizeof(I);
}

// indeed, if I try to overload f0 like this, it will not compile.
// it will complain that, "function 'size_t f0(int *)' already has a body"
size_t f0(int I[2])
{
    return sizeof(I);
}

是的,此示例有大量有符号/无符号的int不匹配,但该部分与该问题无关。此外,请不要忘记delete使用newdelete[]创建的所有内容new[]创建的所有内容A+1

编辑:

“当我P时会发生什么?” - 我之前错过了。

像这样的操作将被称为“指针算术”(即使我在我的答案的顶部调用,其中一些不是指针,但它们可以变成指针)。

如果我有someType数组的指针P[n],则下标访问*(P + n)与使用此语法*(P + n*sizeof(someType))完全相同。编译器将考虑两种情况下指向的类型的大小。因此,生成的操作码实际上会为您*(P + n*sizeof(*P))或等效A0执行类似的操作,因为物理cpu不知道或不关心我们所有组成的“类型”。最后,所有指针偏移都必须是字节数。为了保持一致性,使用像指针这样的数组名称在这里工作相同。

回到上面的示例:A1A2A4_2A0[0]的行为与指针算法相同。

*(A0+0)int[2]相同,A0引用A0[1]的第一个*(A0+1)

类似地:

sizeof(A0[0])int[2]相同,后者将“指针”偏移A0(即8,见上文),最终引用A3的{​​{1}} {1}}

A3行为略有不同。这是因为A3是唯一一个不连续存储2乘2阵列的所有4个整数的{0}。在我的示例中,A3[1]指向一个包含2个int指针的数组,每个指针指向完全分离两个整数的数组。使用*(A3+1)或{{1}}仍会最终指向两个int数组中的第二个,但是它会通过从A3的开头仅偏移4个字节来实现它(使用32位指针用于我的目的)它给你一个指针,告诉你在哪里找到第二个两个int数组。我希望这是有道理的。

答案 1 :(得分:2)

int A[2][2] = {0,1,2,3};
int A[2][2] = {{0,1},{2,3}};

这些将A声明为array of size 2 of array of size 2 of int。声明完全相同。

int **A = new int*[2];

这声明了一个用两个指针数组初始化的pointer to pointer to int。如果要将其用作二维数组,则应为这两个指针分配内存。

int *A = new int[2][2];

这不会编译,因为右边部分的类型是pointer to array of size 2 of int,无法转换为pointer to int

在所有有效情况下,A + 1&A[1]相同,这意味着它指向数组的第二个元素,即,如果int A[2][2]指向第二个数组两个整数,如果int **A到数组中的第二个指针。

答案 2 :(得分:2)

对于数组声明,第一个指定的维度是最外面的维度,是包含其他数组的数组。

对于指针声明,每个*都会添加另一个间接层。

为C语言设计的语法是让声明模仿使用。 C创建者和C ++创建者(Bjarne Stroustrup)都将语法描述为失败的实验。主要问题是它并没有遵循数学中通常的替代规则。

在C ++ 11中,您可以使用std::array代替方括号声明。

您也可以定义类似的ptr类型构建器,例如

template< class T >
using ptr = T*;

然后写

ptr<int> p;
ptr<ptr<int>> q;

答案 3 :(得分:1)

其他答案涵盖了其他声明,但我将解释为什么在前两次初始化中不需要大括号。这两个初始化的原因是相同的:

int A[2][2] = {0,1,2,3};
int A[2][2] = {{0,1},{2,3}};

是因为它被aggregate initialization覆盖。在这种情况下,允许“省略”(省略)大括号。

C ++标准提供了第8.5.1节中的示例:

  

[...]

float y[4][3] = {
  { 1, 3, 5 },
  { 2, 4, 6 },
  { 3, 5, 7 },
};
     

[...]

     

在以下示例中,初始化列表中的大括号被省略;   但初始化列表与。具有相同的效果   上面例子的完全支撑的初始化列表,

float y[4][3] = {
  1, 3, 5, 2, 4, 6, 3, 5, 7
};
     

y的初始值设定项以左括号开头,但是y [0]的初始化程序   没有,因此使用列表中的三个元素。同样   接下来的三个是y [1]和y [2]连续拍摄的。

答案 4 :(得分:0)

好的我会尝试向你解释一下:

  1. 这是初始化。您使用以下值创建二维数组:
    • A [0] [0] - &gt; 0
    • A [0] [1] - &gt; 1
    • A [1] [0] - &gt; 2
    • A [1] [1] - &gt; 3
  2. 这与上面完全相同,但在这里你使用大括号。这样做总是喜欢阅读。
  3. int ** A表示你有一个指向int的指针。当你执行新的int * [2]时,你将保留2个整数指针的内存。
  4. 这不会被编译。

答案 5 :(得分:0)

int A[2][2] = {0,1,2,3};
int A[2][2] = {{0,1},{2,3}};

这两个是等价的 两者都意味着:&#34;我声明了一个二维的整数数组。该阵列的大小为2乘2&#34;。

然而,记忆不是二维的,它不是在网格中布局,而是(概念上)在一条长线上。在多维数组中,每行只是在前一行中分配在内存中。 因此,我们可以转到A指向的内存地址,并存储长度为2的两行或长度为4的一行,并且内存中的最终结果将是相同的。

int **A = new int*[2];

声明一个指向A的指针。
A存储指向包含int s的大小为2的数组的指针的地址。该数组在堆上分配。

int *A = new int[2][2];

A是指向int的指针 该int是堆中分配的2x2 int数组的开头。
一般来说这是无效的:

prog.cpp:5:23: error: cannot convert ‘int (*)[2]’ to ‘int*’ in initialization
  int *A = new int[2][2];

但由于我们在前两个看到的内容,这将起作用(并且100%相当):

int *A new int[4];

答案 6 :(得分:-2)

int A[2][2] = {0,1,2,3};

A是4个整数的数组。为了编码器的方便,他决定将其声明为二维数组,因此编译器将允许编码器将其作为二维数组进行访问。编码器将所有元素线性初始化,因为它们存放在内存中。像往常一样,由于A是一个数组,A本身就是数组的地址,因此A + 1(在应用指针数学运算之后)将A偏移2个int指针的大小。由于数组的地址指向该数组的第一个元素,因此A将指向数组第二行的第一个元素,值为2.

编辑:使用单个数组运算符访问二维数组将沿第一维操作,将第二维视为0.因此A [1]等效于A [1] [0]。 A + 1导致等效指针添加。

int A[2][2] = {{0,1},{2,3}};

A是4个整数的数组。为了编码器的方便,他决定将其声明为二维数组,因此编译器将允许编码器将其作为二维数组进行访问。编码器按行初始化元素。出于同样的原因,A + 1指向值2.

int **A = new int*[2];

是指向int指针的指针,该指针已初始化为指向指向int指针的2个指针的数组。由于A是指针,A + 1取A的,这是指针数组的地址(因此,数组的第一个元素)并加1(指针数学),其中现在将指向数组的第二个元素。由于数组没有被初始化,实际上用A + 1做一些事情(比如读它或写它)会很危险(谁知道那里有什么价值以及实际上指的是什么,如果它甚至是一个有效的地址)。 / p>

int *A = new int[2][2];

编辑:正如Jarod42指出的那样,这是无效的。我认为这可能更接近你的意思。如果没有,我们可以在评论中澄清。

int *A = new int[4];

A是指向int的指针,该指针已初始化为指向4个整数的匿名数组。由于A是指针,A + 1取A的,这是指针数组的地址(因此,数组的第一个元素)并加1(指针数学),其中现在将指向数组的第二个元素。

一些要点:

  1. 在前两种情况下,A是数组的地址,而在最后两种情况下,A是指针的值,它恰好被初始化为数组的地址。
  2. 在前两个中,A初始化后无法更改。在后两者中,A可以在初始化后更改并指向其他内存。
  3. 也就是说,你需要注意如何使用带有数组元素的指针。请考虑以下事项:

    int *a = new int(5);
    int *b = new int(6);
    int c[2] = {*a, *b};
    int *d = a;
    
  4. c+1d+1不同。事实上,访问d+1非常危险。为什么?因为c是一个int数组,已通过解除引用ab进行初始化。这意味着ca内存块的地址,其中该内存位置是已设置为tovariable a指向的值的值,并且在下一个内存位置,是变量b固定的值。另一方面,d只是a的地址。因此,您可以看到,c != d因此没有理由c + 1 == d + 1