数组声明如何在C++中工作
How does array declaration work in C++?
我试图理解在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);
}
是的,此示例有大量有符号/无符号的 int 不匹配,但该部分与问题无关。另外,不要忘记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
的前int[2]
*(A0+0)
相同
同样地:
A0[1]
与将"指针"偏移 sizeof(A0[0])
*(A0+1)
相同(即 8,见上文(,它最终引用了A0
的第二个int[2]
A3
行为略有不同。这是因为A3
是唯一一个不连续存储 2 x 2 数组的所有 4 个整数的数组。在我的示例中,A3
指向一个由 2 个 int 指针组成的数组,每个指针都指向两个整数的完全独立的数组。使用 A3[1]
或 *(A3+1)
最终仍然会将您定向到两个 int 数组中的第二个,但它可以通过仅偏移 A3 开头的 4 个字节来做到这一点(使用 32 位指针用于我的目的(,这为您提供了一个指针,告诉您在哪里可以找到第二个 two-int 数组。我希望这是有道理的。
对于数组声明,第一个指定的维度是最外层的维度,即包含其他数组的数组。
对于指针声明,每个*
都会添加另一个间接级别。
该语法是为 C 设计的,目的是让声明模拟用法。C 创建者和C++创建者 (Bjarne Stroustrup( 都将语法描述为失败的实验。主要问题是它不遵循数学中通常的替换规则。
在 C++11 中,您可以使用std::array
而不是方括号声明。
您还可以定义类似的ptr
类型生成器,例如
template< class T >
using ptr = T*;
然后写
ptr<int> p;
ptr<ptr<int>> q;
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
的情况下指向数组中的第二个指针。
其他答案已经涵盖了其他声明,但我会解释为什么在前两个初始化中不需要大括号。这两个初始化相同的原因:
int A[2][2] = {0,1,2,3};
int A[2][2] = {{0,1},{2,3}};
是因为它被聚合初始化所涵盖。在这种情况下,允许"省略"(省略(大括号。
C++标准在§ 8.5.1中提供了一个示例:
[...]
float y[4][3] = { { 1, 3, 5 }, { 2, 4, 6 }, { 3, 5, 7 }, };
[...]
在下面的示例中,省略了初始值设定项列表中的大括号; 但是,初始值设定项列表与 上面示例的完全支撑的初始值设定项列表,
y 的初始值设定项float y[4][3] = { 1, 3, 5, 2, 4, 6, 3, 5, 7 };
以左大括号开头,但 y[0] 的初始值设定项 不会,因此使用列表中的三个元素。同样 接下来的三个依次取为 y[1] 和 y[2]。
我会尝试向您解释:
- 这是一个初始化。使用以下值创建一个二维数组:
- A[0
- ][0] -> 0
- A[0][1] -> 1
- A[1][0] -> 2 A[1
- ][1] -> 3
- 这与上面完全相同,但在这里您使用大括号。总是这样做,更适合阅读。
- int **A 表示您有一个指向整数指针的指针。当你做新的int*[2]时,你将为2个整数指针保留内存。
- 这不会被编译。
int A[2][2] = {0,1,2,3};
int A[2][2] = {{0,1},{2,3}};
这两者是等价的。
两者都意味着:"我声明一个整数的二维数组。阵列的大小为 2 x 2"。
然而,记忆不是二维的,它不是以网格形式排列的,而是(在概念上(在一条长线上。在多维数组中,每一行都只在内存中紧跟在前一行之后。正因为如此,我们可以转到 A
指向的内存地址,要么存储两行长度为 2 的行,要么存储一行长度为 4,最终在内存中的结果将是相同的。
int **A = new int*[2];
声明指向名为 A 的指针的指针。
A 存储指向大小为 2 的数组的指针的地址,该数组包含 int
s。此数组在堆上分配。
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];
int A[2][2] = {0,1,2,3};
A 是一个由 4 个整数组成的数组。为了程序员的方便,他决定将其声明为二维数组,以便编译器允许编码人员将其作为二维数组进行访问。Coder 在将所有元素放入内存时线性初始化它们。像往常一样,由于 A 是一个数组,A 本身就是数组的地址,因此 A + 1(应用指针数学后(将 A 偏移 2 个整数指针的大小。由于数组的地址指向该数组的第一个元素,因此 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];
A 是指向 int 指针的指针,该指针已初始化为指向指向 int 指针的 2 个指针数组。由于 A 是一个指针,A + 1 取 A 的值,这是指针数组的地址(因此,数组的第一个元素(并加 1(指针数学(,它现在将指向数组的第二个元素。由于数组没有初始化,实际上用 A + 1 做一些事情(比如读取或写入它(将是危险的(谁知道那里有什么值以及它实际上指向什么,如果它甚至是一个有效的地址(。
int *A = new int[2][2];
编辑:正如Jarod42指出的那样,这是无效的。我认为这可能更接近你的意思。如果没有,我们可以在评论中澄清。
int *A = new int[4];
A 是指向 int 的指针,该指针已初始化为指向 4 个整数的匿名数组。由于 A 是一个指针,A + 1 取 A 的值,这是指针数组的地址(因此,数组的第一个元素(并加 1(指针数学(,它现在将指向数组的第二个元素。
一些要点:
- 在前两种情况下,A 是数组的地址,而在后两种情况下,A 是恰好初始化为数组地址的指针的值。
- 在前两个中,A 一旦初始化就无法更改。在后两者中,A 可以在初始化后更改并指向其他内存。
也就是说,您需要小心如何将指针与数组元素一起使用。请考虑以下事项:
int *a = new int(5); int *b = new int(6); int c[2] = {*a, *b}; int *d = a;
c+1
与d+1
不同。事实上,访问d+1
是非常危险的。为什么?因为 c
是一个 int 数组,它已通过取消引用 a
和 b
进行初始化。这意味着c
是内存块a
地址,其中在该内存位置是已设置为变量a
指向的值的值,而在下一个内存位置是变量b
固定的值。另一方面,d
只是a
的地址。所以你可以看到,c != d
因此,没有理由c + 1 == d + 1
.
- QSqlquery prepare()和bindvalue()不工作
- 导入库可以跨dll版本工作吗
- 以螺旋方式打印矩阵的程序.(工作不好)
- 对象指针在c++中是如何工作的
- 为什么在Windows上的VS 2019和Clang 9中"size_t"在没有标题的情况下工作
- VSOMEIP-2个设备之间的通信(TCP/UDP)不工作
- 为字符串中每 N 个字符插入空格的函数没有按照我认为的方式工作?
- C++为线程工作动态地分割例程
- 为什么我的 std::ref 无法按预期工作?
- 布尔比较运算符是如何在C++中工作的
- SampleConsensusPrerejective(ext.RANSAC)是如何真正工作的
- 不确定要在我的main中放入什么才能使我的代码正常工作
- 为什么std::condition_variable notify_all的工作速度比notify_one快(对于随机请
- <<操作员在下面的行中工作
- 有人能解释一下为什么下界是这样工作的吗C++的
- ExtractIconEx:可以工作,但偶尔会崩溃
- C++中的memset函数工作不正常
- 当我在第一个循环中使用"auto"时,它工作正常,但是使用"int"它会给出错误,为什么?
- 链表c++插入,所有情况都已检查,但没有任何工作
- 为什么stream::忽略未按预期工作