与在 C++ 中使用 push_back 相比,声明具有大小的向量是不是提供了任何改进
Posted
技术标签:
【中文标题】与在 C++ 中使用 push_back 相比,声明具有大小的向量是不是提供了任何改进【英文标题】:Does declaring a vector with size offer any improvements over using push_back in C++与在 C++ 中使用 push_back 相比,声明具有大小的向量是否提供了任何改进 【发布时间】:2020-09-19 12:13:51 【问题描述】:假设我们知道我们将需要的向量的大小,(比如“n”)。
使用vector<int> Array(n);
是否比使用Array.push_back(element)
有任何改进?
推荐哪个以及为什么?
【问题讨论】:
两者都在做不同的事情。仅当您需要n
identical 元素时才能使用构造函数。
你希望reserve(n)预先分配足够的容量,否则每次超过容量时vector都会resize,这会导致整个vector复制到一个容量翻倍的新vector。这非常昂贵,因此预分配会提高性能
【参考方案1】:
有
vector<int> Array(n);
您创建一个包含n
元素的向量,这些元素所需的所有内存都会立即分配。
当你使用例如
Array.push_back(value);
然后需要调整向量的大小,这可能意味着必须重新分配内存,并且必须将所有内容复制到新内存中。
您可以预先分配(或reserve)内存,而不是创建具有设定大小的数组:
vector<int> Array; // An empty vector
Array.reserve(n); // Reserve memory, but keep the size as zero (it's still empty)
Array.push_back(value); // No reallocation needed, size is now one
当您有一个无法默认构造的对象向量时,这很有用。
需要学习的重要概念:向量大小及其容量以及它们之间的区别。
容量是向量为其分配内存的元素数。
size 是向量中当前元素的数量。
容量与大小不同是很常见的。 capacity >= size
必须始终是真的。
【讨论】:
感谢这完美地解答了我的疑惑!【参考方案2】:以下是来自Cplusplus.com 的参考:
在内部,向量使用动态分配的数组来存储它们的 元素。这个数组可能需要重新分配才能增长 插入新元素时的大小,这意味着分配一个新的 数组并将所有元素移动到它。这是一个相对昂贵的 处理时间方面的任务,因此,向量不会重新分配 每次向容器中添加一个元素。
相反,向量容器可能会分配一些额外的存储空间给 适应可能的增长,因此容器可能有一个 实际容量大于严格需要的存储容量 它的元素(即它的大小)。
现在让我们看看这两种类型的区别:
1。 vector<int>arr
:
当你声明vector<int>arr
时,向量大小取决于
实现通常为 0。因此,这种情况下的向量将
从 0 开始。
每当您尝试 push_back()
时,向量都会查看
目前的容量足以容纳该元素。
如果容量已经足够容纳元素,它只是将新元素分配到下一个空的内存空间中。
如果当前容量已满,向量将重新分配空间。例如。如果您当前的容量为 4 并且全部用完并且您尝试推回一个元素,那么向量将重新分配空间(例如,8 个元素。新的 容量几乎总是比当前容量增加一倍),然后将元素推入向量中。
如果新空间不能在当前内存位置本身扩展(可能是因为它相邻的空间已经被其他一些变量占用了),那么向量完全从原来的位置转移到一个我们有足够的所需空间的新位置。此过程涉及将 vector 的所有元素复制到新位置,这会占用时间。
如果发生重新分配,则重新分配本身在整个大小中达到线性。但是push_back()
的摊销时间复杂度仍然保持不变,即 O(1)
2。 vector<int>arr(n)
:
此声明将在开始时使用预先分配的 n 个元素的空间初始化向量。
只要您想添加另一个元素,您只需使用 []
运算符分配下一个索引。
因此,假设您的n=5
并且您已分配前两个索引。您可以直接使用arr[2]=4
来添加第三个元素。无需使用push_back()
,因为您已经为向量中的 n 个元素分配了所需的空间。
如果你想添加超过 n 个元素,你仍然可以使用push_back()
。但是对于前 n 个元素,分配是直接使用 [ ]
运算符完成的,因为 vector 已经被调整大小以容纳 n 个元素。
如果您不想使用vector<int>arr(n)
进行初始化,另一种选择是使用reserve()
。
它表示创建向量以便它可以存储至少指定数量的元素,而无需重新分配内存。
在这种情况下,您的初始向量大小将为零,您必须使用 .push_back()
来添加任何新元素。但是,先保留一个空间,然后使用 push_back 将节省您重新分配和复制整个数组到新内存位置的耗时过程。
结论:
所以,由于我们不必总是使用第二种类型不断分配新空间并复制vector的所有元素,因此第二种类型的声明比你的第一种类型的声明效率更高一开始就知道向量的大小。
效率如下:
vector<int>arr(n);
并使用 [ ]
运算符直接在每个索引处分配元素。
arr.reserve(n);
在向量声明后使用.push_back()
方法添加新元素。
vector<int>arr;
并使用.push_back()
方法添加新元素。
希望这能回答你的问题!
【讨论】:
感谢您的详细回答,清理了很多东西。只是一个建议,你能不能把东西格式化一下,目前它有点难以阅读。【参考方案3】:第一个应该比第二个好。为什么? std::vector
是一个动态大小的向量。这意味着如果你想超越它的限制,它会调整大小。这种调整大小是如何发生的?分配新内存,复制所有内容,然后删除之前的内存。这意味着如果容量不够,使用push_back()
可能会触发此分配。第一个使std::vector
从一开始就具有所需的容量,因此在插入元素时不需要移动
注意,你可以做一个特定容量的std::vector
,然后push_back()
不会做任何额外的分配,这应该是相当高效的
【讨论】:
【参考方案4】:效率的最佳选择是在使用push_back
或emplace_back
之前reserve
所需的元素数量。这保证不会发生重新分配。它也更加灵活。
以所需大小创建向量的替代方法需要您提前构造向量的所有元素,然后分配给已经构造的对象。
【讨论】:
在许多情况下,替代方案仍然比 Reserve+push_back 更快。以上是关于与在 C++ 中使用 push_back 相比,声明具有大小的向量是不是提供了任何改进的主要内容,如果未能解决你的问题,请参考以下文章
与在 Interface Builder 中使用自动布局相比,SnapKit 有啥优势?
在C++里,emplace_back可以完全取代push_back吗?
与在 BigQuery ML 中使用 ML.Evaluate 进行评估相比,创建模型后的评估结果有啥区别?
与在单个解决方案中打开所有项目相比,为啥 Devenv 项目构建非常缓慢
在 app.yaml 中定义路由与在 AppEngine 中的 WSGIApplication 中定义一个大型映射相比有性能提升吗?