[英]How does c++ std::vector work?
添加和删除元素如何“重新调整”数据? 向量的大小是如何计算的(我相信它会被跟踪)? 任何其他了解向量的其他资源将不胜感激。
在施胶方面,也有用于感兴趣的两个值std::vector
: size
,和capacity
(经由访问.size()
和.capacity()
.size()
是包含在矢量元素的数量,而.capacity()
是可以被添加到矢量元素的数量,之前存储将被重新分配。
如果你.push_back()
一个元素,大小将增加一,直到你达到容量。 一旦达到容量,大多数(所有?)实现会重新分配内存,将容量加倍。
您可以使用.reserve()
预留容量。 例如:
std::vector<int> A;
A.reserve(1); // A: size:0, capacity:1 {[],x}
A.push_back(0); // A: size:1, capacity:1 {[0]}
A.push_back(1); // A: size:2, capacity:2 {[0,1]}
A.push_back(2); // A: size:3, capacity:4 {[0,1,2],x}
A.push_back(3); // A: size:4, capacity:4 {[0,1,2,3]}
A.push_back(4); // A: size:5, capacity:8 {[0,1,2,3,4],x,x,x}
内存的重新分配将发生在第 4、5 和 7 行。
该向量通常具有三个指针。 如果从未使用过向量,则它们都是 0 或 NULL。
当插入一个元素时,向量分配一些存储空间并设置它的指针。 它可能分配 1 个元素,也可能分配 4 个元素。 或 50。
然后它插入元素并递增最后一个元素指针。
当您插入的元素多于分配的元素时,向量必须获得更多的内存。 它出去并得到一些。 如果内存位置发生变化,那么它必须将所有元素复制到新空间并释放旧空间。
调整大小的常见选择是每次需要更多内存时将分配加倍。
std::vector
的实现随着 C++0x 和后来的移动语义的引入而略有变化(请参阅什么是移动语义?有关介绍)。
当向已经满的std::vector
添加元素时,会调整vector
的大小,这涉及分配新的更大内存区域、将现有数据移动到新vector
、删除旧vector
空间,然后添加的过程新元素。
std::vector
是标准模板库中的一个集合类。 把对象为vector
,把他们从,或vector
,当一个项目被添加到一个完整的进行大小调整vector
都要求该对象的类支持的赋值操作符,一个拷贝构造函数,和移动语义。 (有关详细信息,请参阅std::vector 的类型要求以及std::vector 适用于不可默认构造的类? )
一种将std::vector
视为 C 风格的连续元素array
,该vector
具有定义vector
时指定的类型的连续元素,该array
具有一些附加功能以将其集成到标准模板库产品中。 vector
与标准array
在于vector
将随着项目的添加而动态增长。 (有关差异的一些讨论,请参阅std::vector 和 c 样式数组以及何时使用数组而不是向量/字符串? )
使用std::vector
允许使用其他标准模板库组件,例如算法,因此当您开始使用已经存在的功能时,使用std::vector
比 C 样式array
具有相当多的优势。
如果提前知道最大值,您可以指定初始大小。 (见设置两个元件和std ::向量的初始容量以及矢量::调整大小()和矢量::储备之间选择() )
std::vector
物理表示的基础是一组使用从堆分配的内存的指针。 这些指针允许用于访问存储在所述元素的实际操作vector
,从删除元素vector
,遍历vector
,确定元素的数量,确定其大小等
由于物理表示是连续内存,删除项目可能会导致移动剩余项目以关闭删除操作产生的任何漏洞。
使用现代 C++ 移动语义, std::vector
的开销已经减少,因此它通常是 Bjarne Stroustrup 在他的书 The C++ Programming Language 4th Edition 中推荐的用于大多数应用程序的默认容器,其中讨论了 C++ 11.
我认为std::vector
的基本思想可以通过一个例子来理解:
template<typename T>
class vector {
T *storage;
unsigned int length, cap;
void resizeStorage() {
int *copy = new T[cap];
for (unsigned int i = 0 ; i < length, ++i) {
copy[i] = storage[i];
}
delete [] storage;
storage = copy
}
public:
vector(unsigned int cap = 1): length(0), cap(cap), storage(new T[cap]) {
if (!cap)
cap = 1;
}
unsigned int size() {
return this.length;
}
unsigned int capacity() {
return this.cap;
}
T& operator[](int index) {
return storage[index];
}
const T& operator[](int index) const {
return storage[index];
}
void push_back(T element) {
reserve(++length);
storage[length] = element;
}
void reserve(int capacity) {
if(cap >= capacity) {
return;
}
while(cap < capacity) {
cap *= 2;
}
resizeStorage();
}
virtual ~vector() {
delete[] storage;
}
}
如果存储空间太小,我们需要为每个push_back
保留足够的容量。 我们也可以手动保留,完成后清理内存。 我在这里使用了 2 的因子,因为这通常是数组在内存中调整大小的方式(如果愿意,可以使用 3)。
请注意,虚拟析构函数通常被认为是最佳实践,在不涉及继承的情况下,这里并不是绝对必要的。 使析构函数非虚拟化可能会导致更快的静态绑定。 然而,考虑到载体可包含未知物体它极有可能vector
有一个虚拟析构函数派生类解除分配元件。
构造函数中的容量参数还允许比 C++ 11 之前的版本进行更细粒度的控制,实际上在 C++ 11 之前不存在。但是,我将它作为vector
包含在 C++ 14 中的Allocator
中。我不会深入细节( Allocator
只是另一个用于分配单个向量元素的模板类)。 stdlib 提供了这些高级抽象来标准化具有性能的常见操作。
在 stdlib 中还有许多用于vector
辅助函数,例如swap
、 begin
和end
,但是这些只是以安全的方式在数组存储上运行。 实际的核心vector
实现是基于以上的(类似于11之前的C++标准),额外的逻辑是可以推断的。
数组复制逻辑可以使用数组std::copy
,我们也可以使用智能指针(特别是shared_ptr
)来隐式清理。 但相反,我选择使用低级 API 来演示逻辑步骤。
大约一年前,我用 C++ 编写了一个向量。 它是一个具有固定大小(例如 16 个字符)的数组,可在需要时按该数量扩展。 也就是说,如果默认大小是 16 个字符并且您需要存储Hi my name is Bobby
,那么它将数组的大小加倍到 32 个字符,然后将字符数组存储在那里。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.