一、vector概述
vector容器是一个动态数组,而arrary是一个静态数组。这里的静态和动态的区分,是从内存分配上面来看的。
int n = 10;
std::vector<int> vec(n);
std::array<int, n> arr = { 0 }; //编译出错
因为vector是动态分配,所以vector的大小其实是在运行时确定的所以可以时一个变量,而arrar则是一个静态数组,其大小是在编译时确定的,所以不能用变量去指定其大小。
另外注意const修饰的变量,因为const修饰的变量初始化是在编译期间完成的,所以下面的例子可以通过。
const int n = 10;
std::array<int, n> arr = { 0 };
二、vector的数据结构
vector的底层数据结构是一个动态数组。其内存示意图如下:
通过 start,finish,end_of_storage 三个迭代器 可以很简单的提供大部分功能:大小、是否为空、[ ]、前端元素、后端元素。
class vector{
...
protected:
iterator start; // 指向使用连续空间的第一个元素的位置
iterator finish;// 指向使用连续空间的最后一个元素的下一个位置
iterator end_of_storage;// 指向整个连续空间的尾端
...
};
三、vector的迭代器
因为 vector 维护的是一个连续线性空间,因此普通指针可以作为vector的迭代器,满足迭代器的必要条件。
四、vector 基本操作
1.push_back
void push_back(const T& x)
{
if(finish != end_of_storage) // 说明还有备用空间
{
construct(finish, x); // constuct(T* p, const T& x); 构造函数
++finish;
}
else
insert_aux(end(), x);// vector 成员函数
}
// 实现 空间资源的重新分配,得到更大的空间
template <class T, class Alloc>
void vector<T, Alloc>::insert_aux(iterator position, const T& x)
{
if(finish != end_of_storage) // 还有备用空间 - 上面明明判断过了,这边加上去的理由应该是 防止其他情况 处理吧
{
construct(finish, *(finish - 1));
++finish;
T x_copy = x;
copy_backward(position, finish - 2, finish - 1);
*position = x_copy;
}
else
{
const size_type old_size = size();
const size_type len = old_size != 0 ? 2 * old_size : 1;// 这里实现了 新空间 大小翻倍
iterator new_start = data_allocator::allocate(len); // 申请新 空间
iterator new_finish = new_start;
try
{
// 将原本的内容拷贝 到新的 vector
new_finish = uninitialized_copy(start, position, new_start); // 将 [start,position) 的内容 拷贝 到 new_start为起点的 vector中
// 为新元素 设定 初值 x
construct(new_finish, x);
++new_finish;
// 然后 再把 后半段 也拷过来
new_finish = uninitialized_copy(position, finish, new_finish); // 将 [position,finish) 的内容 拷贝 到 new_finish为起点的 vector中
}
catch(...)
{
// 执行失败则滚回
destroy(nwe_start, new_finish); // 析构
data_allocator::deallocate(new_start, len); // 释放申请的空间
throw;
}
// 析构并释放原来的vector
destroy(begin(), end());
dealloacte(); // 成员函数
// 将迭代器指向新的vector
start = new_start;
finish = new_finish;
end_of_storage = new_start + len;
}
}
2.pop_back
void pop_back()
{
--finish;
destroy(finish);
}
在开辟空间时之所以是2倍扩容,可能是考虑到如果开辟的过小,则拓展的容量还是不足以放下 新加入的元素还需要再次扩容,如果开辟的过大,则会造成空间的浪费,而且申请一大块的内存本身也比较困难。
3.erase
iterator erase(iterator first, iterator last)
{
iterator i = copy(last,finish,first);// 将[last,finish)的元素拷贝到 first为起点的内存空间,返回 拷贝最后一个元素的位置
destroy(i, finish); // 销毁 i 到 finish 的元素
finish = finish - (last - first);// 更新finish
return first;
}
iterator erase(iterator position)
{
if(positio + 1 != end())
{
copy(position+1,finish,position);
}
--finish;
destroy(finish);
return position;
}
4.clear
void clear()
{
erase(begin(), end());
}
5.insert
emplate <class T, class Alloc>
void vector<T, Alloc>::insert(iterator position, size_type n, const T& x)
{
if(n!=0)
{
if(size_type(end_of_storage - finish) >=n) // 场景1)
{
T x_copy = x;
const size_type elems_after = finish - position; //position后面的元素个数
iterator old_finish = finish;
if(elems_after > n) // 场景1) 中的 场景A.
{
uninitialized_copy(finish - n, finish, finish); // A.a
finish += n;
copy_backward(position, old_finish - n, old_finish); // A.c
fill(position, position + n, x_copy); // A.d
}
else // 场景1) 中的 场景B.
{
uninitialized_fill_n(finish, n - elems_after, x_copy); // A.a
finish += n - elems_after;
uninitialized_copy(position, old_finish, finish); // A.c
finish += elems_after;
fill(position, old_finish, x_copy); // A.e
}
}
else // 场景2)
{
const size_type old_size = size();
const size_type len = old_size + max(old_size, n); // 备用空间的翻倍
iterator new_start = data_allocator::allocate(len); // 分配空间
iterator new_finish = new_start;
try // 异常情况
{
new_finish = uninitialized_copy(start, position, new_start); // 2).b
new_finish = uninitialized_fill_n(new_finish, n, x); // 2).c
new_finish = uninitialized_copy(position, finish, new_finish); // 2).d
}
catch(...)
{
destroy(new_start, new_finish);
data_allocator::deallocate(new_start, len);
throw;
}
// 2).e
destroy(start, finish);
deallocate();
// 更新游标
start = new_satrt;
finish = new_finish;
end_of_storage = new_start + len;
}
}
}
小结
在使用vector的时,要考虑其底层实现是一个动态数组,有3个意义:1)这是一块连续的内存,支持随机访问。2)在扩容时,大小扩容到原来的二倍,并且是在系统中(虚拟内存空间)另外分配二倍的内存,然后将原来的内容拷贝到新的内存,并释放旧内存。3)迭代器失效问题,当插入或者删除时会使当前及之后的迭代器失效,发生扩容时所有迭代器均失效。
五、sort算法分析
因为vector的内存是一块连续的空间,支持随机访问,所以可以说使用sort进行快排。
本博客用于记录学习笔记,以整理本人学习原博客时的疑问。