C ++从C继承了数组,几乎无处不在。 C ++提供了更容易使用且更不容易出错的抽象(std::vector<T>
自C ++ 98和std::array<T, n>
以来C++11),因此对数组的需求并不像但是,当您阅读遗留代码或与使用C语言编写的库进行交互时,您应该牢牢掌握数组的工作原理。
本常见问题分为五个部分:
如果您觉得此常见问题解答中缺少重要内容,请写下答案并将其作为附加部分链接到此处。
在下文中,“array”表示“C array”,而不是类模板std::array
。假定了C声明符语法的基本知识。请注意,如下所示,new
和delete
的手动使用在面对异常时非常危险,但这是another FAQ的主题。
<子> (注意:这是Stack Overflow's C++ FAQ的一个条目。如果您想批评在此表单中提供常见问题解答的想法,那么the posting on meta that started all this就是这样做的地方。在C++ chatroom中监控了这个问题,首先是FAQ的想法,所以你的答案很可能会被那些提出想法的人阅读。) 子>
答案 0 :(得分:287)
答案 1 :(得分:131)
程序员经常将多维数组与指针数组混淆。
大多数程序员都熟悉命名的多维数组,但许多程序员并不知道多维数组也可以匿名创建。多维数组通常称为“数组数组”或“ true 多维数组”。
使用命名多维数组时,必须在编译时知道所有维度:
int H = read_int();
int W = read_int();
int connect_four[6][7]; // okay
int connect_four[H][7]; // ISO C++ forbids variable length array
int connect_four[6][W]; // ISO C++ forbids variable length array
int connect_four[H][W]; // ISO C++ forbids variable length array
这是命名多维数组在内存中的样子:
+---+---+---+---+---+---+---+
connect_four: | | | | | | | |
+---+---+---+---+---+---+---+
| | | | | | | |
+---+---+---+---+---+---+---+
| | | | | | | |
+---+---+---+---+---+---+---+
| | | | | | | |
+---+---+---+---+---+---+---+
| | | | | | | |
+---+---+---+---+---+---+---+
| | | | | | | |
+---+---+---+---+---+---+---+
请注意,上面的2D网格仅仅是有用的可视化。从C ++的角度来看,内存是一个“平坦”的字节序列。多维数组的元素以行主顺序存储。也就是说,connect_four[0][6]
和connect_four[1][0]
是内存中的邻居。事实上,connect_four[0][7]
和connect_four[1][0]
表示相同的元素!这意味着您可以采用多维数组并将它们视为大型一维数组:
int* p = &connect_four[0][0];
int* q = p + 42;
some_int_sequence_algorithm(p, q);
使用匿名多维数组时,必须在编译时知道除第一个之外的所有维:
int (*p)[7] = new int[6][7]; // okay
int (*p)[7] = new int[H][7]; // okay
int (*p)[W] = new int[6][W]; // ISO C++ forbids variable length array
int (*p)[W] = new int[H][W]; // ISO C++ forbids variable length array
这是匿名多维数组在内存中的样子:
+---+---+---+---+---+---+---+
+---> | | | | | | | |
| +---+---+---+---+---+---+---+
| | | | | | | | |
| +---+---+---+---+---+---+---+
| | | | | | | | |
| +---+---+---+---+---+---+---+
| | | | | | | | |
| +---+---+---+---+---+---+---+
| | | | | | | | |
| +---+---+---+---+---+---+---+
| | | | | | | | |
| +---+---+---+---+---+---+---+
|
+-|-+
p: | | |
+---+
请注意,数组本身仍然作为单个块分配在内存中。
您可以通过引入另一个间接级别来克服固定宽度的限制。
这是一个由五个指针组成的命名数组,它们使用不同长度的匿名数组进行初始化:
int* triangle[5];
for (int i = 0; i < 5; ++i)
{
triangle[i] = new int[5 - i];
}
// ...
for (int i = 0; i < 5; ++i)
{
delete[] triangle[i];
}
以下是它在内存中的样子:
+---+---+---+---+---+
| | | | | |
+---+---+---+---+---+
^
| +---+---+---+---+
| | | | | |
| +---+---+---+---+
| ^
| | +---+---+---+
| | | | | |
| | +---+---+---+
| | ^
| | | +---+---+
| | | | | |
| | | +---+---+
| | | ^
| | | | +---+
| | | | | |
| | | | +---+
| | | | ^
| | | | |
| | | | |
+-|-+-|-+-|-+-|-+-|-+
triangle: | | | | | | | | | | |
+---+---+---+---+---+
由于现在每条线都是单独分配的,因此将2D数组视为一维数组不再有效。
这是一个包含5个(或任何其他数量)指针的匿名数组,这些指针使用不同长度的匿名数组进行初始化:
int n = calculate_five(); // or any other number
int** p = new int*[n];
for (int i = 0; i < n; ++i)
{
p[i] = new int[n - i];
}
// ...
for (int i = 0; i < n; ++i)
{
delete[] p[i];
}
delete[] p; // note the extra delete[] !
以下是它在内存中的样子:
+---+---+---+---+---+
| | | | | |
+---+---+---+---+---+
^
| +---+---+---+---+
| | | | | |
| +---+---+---+---+
| ^
| | +---+---+---+
| | | | | |
| | +---+---+---+
| | ^
| | | +---+---+
| | | | | |
| | | +---+---+
| | | ^
| | | | +---+
| | | | | |
| | | | +---+
| | | | ^
| | | | |
| | | | |
+-|-+-|-+-|-+-|-+-|-+
| | | | | | | | | | |
+---+---+---+---+---+
^
|
|
+-|-+
p: | | |
+---+
数组到指针的衰减自然会扩展到数组和指针数组:
int array_of_arrays[6][7];
int (*pointer_to_array)[7] = array_of_arrays;
int* array_of_pointers[6];
int** pointer_to_pointer = array_of_pointers;
但是,没有从T[h][w]
到T**
的隐式转换。如果确实存在这样的隐式转换,则结果将是指向h
指向T
指针数组的第一个元素的指针(每个指针指向原始2D数组中一行的第一个元素) ,但指针数组在内存中的任何地方都不存在。如果需要这样的转换,则必须手动创建并填充所需的指针数组:
int connect_four[6][7];
int** p = new int*[6];
for (int i = 0; i < 6; ++i)
{
p[i] = connect_four[i];
}
// ...
delete[] p;
请注意,这会生成原始多维数组的视图。如果您需要副本,则必须创建额外的数组并自行复制数据:
int connect_four[6][7];
int** p = new int*[6];
for (int i = 0; i < 6; ++i)
{
p[i] = new int[7];
std::copy(connect_four[i], connect_four[i + 1], p[i]);
}
// ...
for (int i = 0; i < 6; ++i)
{
delete[] p[i];
}
delete[] p;
答案 2 :(得分:85)
由于没有特殊原因,不能将数组分配给彼此。请改用std::copy
:
#include <algorithm>
// ...
int a[8] = {2, 3, 5, 7, 11, 13, 17, 19};
int b[8];
std::copy(a + 0, a + 8, b);
这比真正的数组赋值更灵活,因为可以将更大的数组切片复制到更小的数组中。
std::copy
通常专门用于基本类型,以提供最高性能。 std::memcpy
不太可能表现得更好。如果有疑问,请测量。
虽然您无法直接分配数组,但可以分配包含数组成员的结构和类。这是因为赋值运算符的array members are copied memberwise是编译器默认提供的。如果为自己的结构或类类型手动定义赋值运算符,则必须回退到数组成员的手动复制。
无法通过值传递数组。您可以通过指针或引用传递它们。
由于数组本身不能通过值传递,因此通常会通过值传递指向其第一个元素的指针。这通常被称为“通过指针传递”。由于数组的大小不能通过该指针检索,因此必须传递第二个参数,指示数组的大小(经典的C解决方案)或第二个指针指向数组的最后一个元素(C ++迭代器解决方案) :
#include <numeric>
#include <cstddef>
int sum(const int* p, std::size_t n)
{
return std::accumulate(p, p + n, 0);
}
int sum(const int* p, const int* q)
{
return std::accumulate(p, q, 0);
}
作为一种语法替代方法,您还可以将参数声明为T p[]
,这意味着仅与参数列表上下文中的T* p
完全相同:
int sum(const int p[], std::size_t n)
{
return std::accumulate(p, p + n, 0);
}
您可以将编译器视为仅在参数列表的上下文中将T p[]
重写为T *p
。这个特殊规则部分地导致了对数组和指针的整体混淆。在每个其他上下文中,将某些内容声明为数组或指针会产生巨大的差异。
不幸的是,您还可以在数组参数中提供一个大小,编译器会忽略该参数。也就是说,以下三个签名完全等效,如编译器错误所示:
int sum(const int* p, std::size_t n)
// error: redefinition of 'int sum(const int*, size_t)'
int sum(const int p[], std::size_t n)
// error: redefinition of 'int sum(const int*, size_t)'
int sum(const int p[8], std::size_t n) // the 8 has no meaning here
数组也可以通过引用传递:
int sum(const int (&a)[8])
{
return std::accumulate(a + 0, a + 8, 0);
}
在这种情况下,数组大小很重要。由于编写一个只接受8个元素的数组的函数几乎没用,程序员通常会将这些函数写成模板:
template <std::size_t n>
int sum(const int (&a)[n])
{
return std::accumulate(a + 0, a + n, 0);
}
请注意,您只能使用实际的整数数组调用此类函数模板,而不能使用指向整数的指针。自动推断数组的大小,对于每个大小n
,从模板中实例化不同的函数。您还可以编写从元素类型和大小中抽象出来的quite useful函数模板。
答案 3 :(得分:70)
答案 4 :(得分:67)