我试图了解在 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
时会发生什么?这些都是声明矩阵的有效方法吗?
另外,为什么第一个选项不需要第二组花括号来定义“列”?
看起来你在我写我的时候得到了太多的答案,但我还是把我的答案贴出来,这样我才不会觉得这一切都是徒劳的......
(所有 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);
}
是的,这个样本有大量有符号/无符号整数不匹配,但那部分与问题无关。另外,不要忘记 delete
使用 new
创建的所有内容和 delete[]
使用 new[]
创建的所有内容
编辑:
“当我执行 A+1
时会发生什么?” -- 我之前错过了这个。
像这样的操作将被称为“指针算术”(尽管我在回答的顶部大声说其中一些不是指针,但它们可以变成指针)。
如果我有一个指向 someType
数组的指针 P
,那么下标访问 P[n]
与使用它完全相同语法 *(P + n)
。在这两种情况下,编译器都会考虑指向的类型的大小。因此,生成的操作码实际上会为您做这样的事情 *(P + n*sizeof(someType))
或等效的 *(P + n*sizeof(*P))
因为物理 cpu 不知道也不关心我们所有的“类型”。最后,所有指针偏移量都必须是字节数。为了保持一致性,在这里使用像指针这样的数组名称也是一样的。
回到上面的示例:A0
、A1
、A2
和 A4_2
都表现相同用指针运算。
A0[0]
与*(A0+0)
相同,引用了的第一个int[2]
>A0
类似的:
A0[1]
与 *(A0+1)
相同,后者将“指针”偏移 sizeof(A0[0])
(即 8,见上文),它最终引用 A0
的第二个
int[2]
A3
的行为略有不同。这是因为 A3
是唯一一个不连续存储 2 x 2 数组的所有 4 个整数的数组。在我的示例中,A3
指向一个包含 2 个 int 指针的数组,每个指针都指向包含两个 int 的完全独立数组。使用 A3[1]
或 *(A3+1)
最终仍会将您定向到两个 int 数组中的第二个,但它只会通过偏移 4 个字节来实现从 A3 的开头(出于我的目的使用 32 位指针),它为您提供了一个指针,告诉您在哪里可以找到第二个双整数数组。我希望这是有道理的。