在C++中,std::vector和std::array的区别是什么?

389

std::vectorstd::array 在 C++ 中有什么不同?应该在什么情况下选择其中之一?它们各自的优缺点是什么?我所看的教材只列出了它们相同的地方。


2
我正在寻找std::vectorstd::array的比较,以及它们之间的差异。 - Zud
1
Zud,std::array并不等同于C++数组。std::array只是一个非常薄的C++数组包装器,其主要目的是将指针从类的用户中隐藏起来。我会更新我的答案。 - ClosureCowboy
我更新了问题的标题和文本以反映您的澄清。 - Matteo Italia
如果您正在实现constexpr或consteval函数,那么可以使用std :: array,但不能使用std :: vector。https://dev59.com/0VwX5IYBdhLWcg3w6S8h - Anton Krug
6个回答

407

std::vector 是一个模板类,封装了一个动态数组1,存储在堆上,如果添加或删除元素,则会自动增长和缩小。它提供了所有的钩子(begin()end()、迭代器等),使其与STL的其余部分良好地配合工作。它还有几个有用的方法,让您执行在普通数组中繁琐的操作,例如在向量中插入元素(它处理幕后的所有工作)。

由于它将元素存储在堆上分配的内存中,因此与静态数组相比,它具有一定的开销。

std::array 是一个模板类,封装了一个静态大小的数组,存储在对象本身内部,这意味着,如果您在堆栈上实例化该类,则数组本身将在堆栈上。它的大小必须在编译时知道(作为模板参数传递),并且不能增长或缩小。

std::arraystd::vector 的功能更有限,不过在实践中它通常比后者更高效,尤其是对于小规模数据,因为它主要是一个轻量级的 C 风格数组封装。然而,由于禁用了指针的隐式转换,并且提供了许多与 STL 相关的 std::vector 和其他容器的功能,所以它更为安全,可以轻松地与 STL 算法等使用。总之,由于固定大小的限制,它比 std::vector 更不灵活。

如果想了解关于 std::array 的介绍,请参阅 this article;如果想快速了解 std::vector 及其可操作性,请查看其documentation


  1. 实际上,我认为在标准中它们是以不同操作的最大复杂度来描述的(例如,在常数时间内进行随机访问,在线性时间内迭代所有元素,在常摊时间内添加和删除元素等),但据我所知,除了使用动态数组之外,没有其他方法来满足这些要求。正如@Lucretiel所述,标准实际上要求元素被连续存储,因此它是一个动态数组,存储在相关分配器放置的位置。

8
关于您的脚注:虽然正确,但标准还保证了对内部元素指针算术运算的支持,这意味着它必须是一个数组:&vec[9] - &vec[3] == 6 成立。 - Lucretiel
11
我很确定,向量不会自动缩小,但自从C++11以来,你可以调用shrink_to_fit方法。 - Dino
5
@Zboson说:"一定不仅仅是你, 'static' 是一个被滥用的术语;在 C++ 中,'static' 关键字有三种不相关的含义,这个术语也经常用来谈论在编译时固定的东西。我希望 'statically-sized' 更加清晰一些。" - Matteo Italia
4
需要注意的一点是:对于实时编程(在启动后不应该有任何动态分配/释放),std::array可能比std::vector更受欢迎。 - T.E.D.
1
可能是因为在这个例子中,向量没有动态调整大小? - talekeDskobeDa
显示剩余9条评论

31
为了强调@MatteoItalia所说的一点,效率差异在于数据存储的位置。堆内存(使用时所需)需要调用系统以分配内存,如果您计算周期,则这可能是昂贵的。栈内存(对而言是可能的)在时间上几乎是“零开销”的,因为内存是通过仅调整堆栈指针来分配的,并且只有进入函数时才执行一次。栈还避免了内存碎片化。确保并不总在栈上;它取决于您的分配位置,但与vector相比,它将涉及一个更少的内存分配。如果您有
•小型“数组”(100个元素以下)-(典型的栈大小约为8MB,因此不要在栈上分配超过几KB或递归代码较少的情况下)
•尺寸将被固定
•生命周期在函数范围内(或者是具有与父类相同生命周期的成员值)
•您正在计算周期
那么一定要使用而不是vector。如果其中任何一个要求不符合,则使用。

4
不错的回答。“确实,std::array并不总是在堆栈上;它取决于你在哪里分配它。”那么,我如何创建一个具有大量元素的std::array而不在堆栈上? - Trilarion
7
请使用new std::array或将其作为一个类的成员,并使用'new'进行分配。 - Mark Lakata
2
这意味着 new std::array 仍然期望在编译时知道其大小并且无法更改其大小,但仍然存在于堆上? - Trilarion
1
是的。使用new std::arraynew std::vector之间没有显著的优势。 - Mark Lakata

27
总结上述讨论,以便快速参考的表格如下:
C-Style Array std::array std::vector
Size 固定/静态 固定/静态 动态
Memory efficiency 更高效 更高效 不太高效
(可能在新分配时会增加其大小。)
Copying 遍历元素
或使用 std::copy()
直接复制:a2 = a1; 直接复制:v2 = v1;
Passing to function 通过指针传递
(函数中无法获得大小)
或作为 std::span
按值传递
或作为 std::span
按值传递
或作为 std::span
Size sizeof a1 / sizeof *a1
std::size(a1)
a1.size()
std::size(a1)
v1.size()
std::size(v1)
Use case 用于快速访问,且不经常需要插入/删除。 与经典数组相同,但更安全且更容易传递和复制。 当需要频繁添加或删除时。

void foo(T (& arr)[N]) 可以捕获数组大小。类似于 magic-arguments-in-function-templates - I.Omar
我会添加这些行:"| 值语义 | 否 | 是 | 是 |" 和 "| 移动 | O(N) | O(N) | O(1) |" 以及 "| 交换 | O(N) | O(N) | O(1) |"。 - alfC
1
“内存效率”到底是什么?这个表达可能会有歧义。此外,std::vector 的容量在新分配时如何增加取决于实现。例如,MSVC 将容量增加了x1.5,而不是x2。 - starriet

25
如果您考虑使用多维数组,则 std::arraystd::vector 之间还有一个额外的区别。多维 std::array 的元素将在所有维度上紧密地打包在内存中,就像 C 风格数组一样。而多维 std::vector 在所有维度上都不会被紧密地打包。
给定以下声明:
int cConc[3][5];
std::array<std::array<int, 5>, 3> aConc;
int **ptrConc; // initialized to [3][5] via new and destructed via delete
std::vector<std::vector<int>> vConc; // initialized to [3][5]

指向 C 风格数组(cConc)或 std::array(aConc)中第一个元素的指针可以通过将 1 添加到每个前面的元素来遍历整个数组。它们是紧密打包的。

指向向量数组(vConc)或指针数组(ptrConc)中第一个元素的指针只能遍历前 5 个(在这种情况下)元素,然后下一个向量有 12 字节(在我的系统上)的开销。

这意味着作为 [3][1000] 数组初始化的 std::vector<std::vector<int>> 数组在内存中比作为 [1000][3] 数组初始化的数组要小得多,并且两者都比以任何方式分配的 std::array 更大。

这也意味着您不能简单地将多维向量(或指针)数组传递给 OpenGL,而不考虑内存开销,但您可以天真地将多维 std::array 传递给 OpenGL 并使其正常工作。


21

使用 std::vector<T>类:

  • 如果你只是对现有的元素进行读取和写入,那么使用 std::vector<T> 类与使用内置数组一样快。

  • 当插入新元素时,std::vector 会自动调整大小。

  • 可以在向量的开头或中间插入新元素,并自动“移动”其余元素。它还允许您从 std::vector 中删除任何位置的元素,同时自动将其余元素向下移动。

  • 使用 at() 方法可以执行范围检查的读取(如果不想执行此检查,则始终可以使用索引器 [])。

使用 std::vector<T> 的主要注意事项有 两个 三个:

  1. 无法可靠地访问底层指针,如果需要处理要求数组地址的第三方函数,则可能会存在问题。

  2. std::vector<bool> 类很傻。它实现为一个压缩的位域,而不是一个数组。如果需要一个布尔数组,请避免使用它!

  3. 在使用中,std::vector<T> 比具有相同元素数量的 C++ 数组要稍微大一些。这是因为它们需要跟踪少量其他信息,例如它们的当前大小,并且每当 std::vector<T> 调整大小时,它们都会预留比所需更多的空间。这是为了防止它们每次插入新元素时都必须调整大小。可以通过提供自定义的 allocator 来更改此行为,但我从未感到有必要这样做!


编辑:阅读Zud对问题的回答后,我感到有必要补充一下:

std::array<T>类与C++数组不同。 std::array<T>是一个非常薄的包装器,用于隐藏该类的用户指针(在C ++中,数组通常被隐式转换为指针,这往往会带来困扰)。 std::array<T>类还存储其大小(长度),这可以非常有用。


9
它的速度“和使用动态分配的内置数组一样快”。另一方面,使用自动数组可能会有显著不同的性能(不仅在分配期间,还因为局部性效应)。 - Ben Voigt
8
在 C++11 及以后的版本中,对于非布尔向量,您可以在 std::vector<T> 上调用 data() 方法以获取底层指针。您也可以直接获取第一个元素的地址(在 C++11 中保证有效,在早期版本中可能也能工作)。 - Matt
1
在最后一段中,你是指C数组吗?对吗? - 0xB00B

-20

向量是一个容器类,而数组是分配的内存。


27
你的回答似乎是针对std::vector<T>T[]的区别,但问题是关于std::vector<T>std::array<T>的比较。 - Keith Pinson

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接