从0开始写STL—容器—vector

vector又称为动态数组,那么动态体现在哪里?vector和一般的数组又有什么区别?vector中各个函数的实现原理是怎样的,我们怎样使用会更高效?

  • 以上内容我们将通过写一个自己的vector来进行学习

typedef 简析

在容器类的最前面我们会看到许多的typedef ,常见的如下:

	public:
typedef T value_type;
typedef value_type* pointer;
typedef value_type* iterator;
typedef T& reference;
typedef size_t size_type;
typedef ptrdiff_t difference_type;

这些typedef 的意义在于:

  • 1.使得变量名便于理解和修改
  • 2.提供RTTI的实现可能性,通过模板萃取可以萃取出 value_type 等

Alloctor 简析

在这里我们只需要知道allocator类是vector类和底层内存之间的一个中间层,屏蔽了内存分配,对象初始化的细节。

内存分配相关函数->动态分配内存

首先vector和普通数组一样,是在内存中连续分布的,然而它可以实现内存的动态分配(动态分配是指vector可以自动扩充自己的容量来满足存储元素的需要)。

现有的申请到的内存空间不足以满足动态数组增长的需求时,vector会自动申请重新分配一块更大的内存(也就是会自动进行一次reserve函数,在push_back不断插入元素的时候,为了避免大量重新分配浪费性能,会将内存分配到原来的两倍)

该内存模型的实现形式

vector内部会维护三个指针,start 指针指向vector中的第一个元素,end指针指向vector中最后一个元素的后面,end_of_storage 申请到内存的最末端。

为什么要这样设计?

可以在 O1 的时间 求出数组内元素个数 和 容量(通过指针运算)

destroyanddeallocate函数

  • 如名称所示,这个函数可以销毁当前所有元素并且回收内存。析构函数直接调用该函数
void destroyanddeallocate()
{
if(capacity()==0)
{
return ;
}
for(auto it = start; it != End; it++)
{
destroy(it);//对所有元素调用析构函数
}
data_allocator.deallocate(start,end_of_storage-start);//由allocator回收内存
}
~vector()
{
destroyanddeallocate();
}

reallocate 函数 重新分配内存

  • 函数负责在内存不够的情况下动态分配内存并且将原有元素拷贝到新的内存中,通过将默认变量设置为0,如果没有传入变量覆盖掉默认变量就将申请的内存扩大到原有内存的两倍。
void reallocate(size_type new_size = 0)
{
if(new_size == 0)
{
new_size = size() == 0 ? 1 : 2 * size();
}
iterator new_start = data_allocator.allocate(new_size);//首先分配一块新的内存
if(new_start == nullptr)
{
//分配失败的处理
//直接终止程序(内存不足)
}
iterator new_End = ministl::uninitialized_copy(start, End, new_start);//移动元素
DestroyAndDeallocateAll();//销毁元素 并且 回收内存
start = new_start;
End = new_End;
end_of_storage = start + new_size;// 设置指针位置
}

代码分析(异常安全)

我们将从异常安全的角度来逐行分析代码。

	//这三行提供基本的异常安全保证
if(new_size == 0)
{
new_size = size() == 0 ? 1 : 2 * size();
}
iterator new_start = data_allocator.allocate(new_size);//首先分配一块新的内存
if(new_start == nullptr)
{
//分配失败的处理
//进行内存回收
}
iterator new_End = ministl::uninitialized_copy(start, End, new_start);//may throw
DestroyAndDeallocateAll();//析构函数 noexcept保证
//在最后替换成员指针,提供了强类型的异常安全保证
start = new_start;
End = new_End;
end_of_storage = start + new_size;// 设置指针位置

构造函数

		vector()
{
End = end_of_storage = start = nullptr;//避免野指针
}
vector(size_t n, value_type val = value_type())//要求value_type 有一个默认构造函数
{
start = data_allocator.allocate(n);
End = end_of_storage = start + n;
ministl::uninitialized_fill_n(start, n, val);// 使用默认构造函数的值进行拷贝构造
}
vector(iterator first, iterator last)
{
start = data_allocator.allocate(last - first);
End = end_of_storage = uninitialized_copy(first, last, start);
}
vector(const vector<T, Alloc> &rhs)
{
assign(rhs.start, rhs.End);//Dont repeat yourself!//dont repeat yourself
}
vector(vector<T, Alloc> &&rhs)
{
if (this != &rhs)
{
DestroyAndDeallocateAll();
start = rhs.start, End = rhs.End, end_of_storage = rhs.end_of_storage;
rhs.start = rhs.end_of_storage = rhs.End = nullptr;//将移动后的右值置于可析构的状态
}
}

Element Access 相关函数

  • 通过指针的移动获得元素的值,注意传递引用 这样外部的修改才是对容器内部元素的修改。
		value_type& operator [](size_type n)
{
if (n >= size())
{
std::cerr << "Out of range" << std::endl;
std::exit(1);
}
return *(start + n);
}
value_type& front()
{
if (empty())
{
std::cerr << "front on empty vector" << std::endl;
std::exit(1);
}
return *(start);
}
value_type& back()
{
if (empty())
{
std::cerr << "back on empty vector" << std::endl;
std::exit(1);
}
return *(End - 1);
}

Modify 修改相关函数

  • 内容较多 见注释
		void resize(size_type new_size, value_type val = value_type())
{
if (new_size < size())// 无需重新分配内存
{
// 销毁不需要的元素,无需回收内存
for (iterator it = start + new_size; it != End; it++)
{
destroy(it);
}
End = start + new_size;// 重新设置最后一个元素位置
}
else if (new_size < capacity())//小于申请到的内存容量
{
//只需将后面未初始化的内存初始化一下
uninitialized_fill_n(End, new_size - size(), val);
End = start + new_size;
}
else // 重新分配内存
{
reallocate(new_size);
uninitialized_fill_n(End, new_size - size(), val);
End = start + new_size;
}
}
void reserve(size_type n)
{
if (n <= capacity()) return;
reallocate(n); // 重新分配内存
}
bool empty()
{
return start == End;
}
//Modifiers
void push_back(value_type data)
{
if (capacity() == size())//判断容量
reallocate();
construct(End++, data);//更改End位置并进行初始化
}
void pop_back()
{
if (empty())
{
std::cerr << "pop on empty" << std::endl;
std::exit(1);
}
End--;
destroy(End);//析构
}
template<class InputIterator>
void assign(InputIterator first, InputIterator last)
{
DestroyAndDeallocateAll();
start = data_allocator.allocate(last - first);//申请内存
End = end_of_storage = uninitialized_copy(first, last, start);//初始化
}
void assign(size_type n, const value_type &v)
{
DestroyAndDeallocateAll();
start = data_allocator.allocate(n);
End = end_of_storage = start + n;
uninitialized_fill_n(start, n, v);
}
iterator insert(iterator pos, const value_type &v)
{
if (pos == end())//特殊情况
{
push_back(v);
return &back();
}
if (size() == capacity())//内存申请
reallocate();
iterator new_end = End + 1;
data_allocator.construct(new_end, v);//要进行初始化否则会报错
copy_backward(pos, End, new_end);//移动pos后面的元素到最终位置
*pos = v;//赋值
End = new_end;
end_of_storage++;
return pos;
}
/*
这个函数比较复杂,一定注意End pos 的含义变化
*/
iterator insert(iterator pos, const size_type &n, const value_type &val)
{
size_type index = pos - start;
if (n + size() > capacity())//判断内存容量
reserve(n + size());
pos = start + index;// 插入元素的位置
iterator new_end = End + n; // 元素插入后 结束点的位置
size_type after_num = End - pos;// 插入点后面的元素个数
if (after_num > n) // 如果后面元素个数比要插入的元素多
{
uninitialized_copy(End - n, End, End);// 将End-n 到 End 的元素放置到最正确位置
End += n; // 重新设置End 的位置
copy_backward(pos, End - n, End);// 将pos到End-n的元素放置正确位置
fill(pos, pos + n, val);// 填充出去
return pos + n;// 返回插入最后一个元素的位置
}
else //插入元素个数很多
{
iterator old_end = End;
uninitialized_fill_n(End, n - after_num, val);//填充n-afternum个元素,后面的元素没必要初始化因为后面的元素将会是afternum个插入节点后面的元素
End += n - after_num;
uninitialized_copy(pos, old_end, End);// 拷贝afternum个插入节点后面的元素
End += after_num;
fill(pos, old_end, val);// 填充
return pos + n;
}
}
/*
这个和上面大同小异
*/
template<class InputIterator>
void insert(iterator pos, InputIterator first, InputIterator last)
{
size_type add_num = last - first, index = pos - start, after_num = End - pos;
if (add_num + size() > capacity())
reserve(add_num + size());
pos = start + index;
if (pos > End) pos = End;
if (pos <= End - add_num)
{
uninitialized_copy(End - add_num, End, End);
copy_backward(pos, End - add_num, End);
copy(first, last, pos);
End += add_num;
}
else
{
iterator new_end = End + add_num;
uninitialized_copy(pos, End, new_end - after_num);
copy_n(first, after_num, pos);
uninitialized_copy_n(first + after_num, add_num - after_num, End);
End += add_num;
}
}
iterator erase(iterator pos)
{
copy(pos + 1, End, pos);
destroy(End - 1);
End--;
return pos;
} iterator erase(iterator first, iterator last)
{
copy(last, End, first);
for (auto it = End - (last - first); it != End; it++)
destroy(it);
End = End - (last - first);
return last;
}
void clear()
{
DestroyAndDeallocateAll();
End = start;
}
void swap(vector<T, Alloc> &rhs)
{
iterator tmp;
tmp = start, start = rhs.start, rhs.start = tmp;
tmp = End, End = rhs.End, rhs.End = tmp;
tmp = end_of_storage, end_of_storage = rhs.end_of_storage, rhs.end_of_storage = tmp;
}
bool operator==(vector<T, Alloc>& rhs)
{
if (size() != rhs.size())//首先size 要相同
return false;
else
{
auto it = begin();
auto j = rhs.begin();
for (; it != end(); )
{
if (*it != *j)
return false;
else
it++, j++;
}
return true;
}
}
bool operator!=(vector<T, Alloc>& rhs)
{
return !(*this == rhs);
}
void operator=(const vector<T, Alloc>& rhs)
{
if (this != &rhs)
{
//assign(rhs.begin(), rhs.end());
//or
vector<T,Alloc> tmp = rhs;
swap(tmp); // tmp 交换后存储的是原数组的元素,再函数结尾调用析构函数释放内存
}
}
}; template<class T>
void swap(vector<T>& a, vector<T>& b)
{
a.swap(b);
}

怎样高效使用

  • 尽量避免vector容器前端的插入,会引起大量元素的重新拷贝
  • reserve 内存会一定情况下减少内存的重新分配
  • 一定注意迭代器失效(与其考虑更新迭代器,不如从代码的设计上去找问题,在动态数组内部随机存取复杂度为O1,为什么要保存某一特定位置的迭代器?如果是要指向某一特定的元素,难道我们不能用下标去记录位置,用常量引用来记录动态数组?)

从零开始写STL—容器—vector的更多相关文章

  1. 从零开始写STL - 智能指针

    从零开始写STL - 智能指针 智能指针的分类及其特点: scoped_ptr:初始化获得资源控制权,在作用域结束释放资源 shared_ptr: 引用计数来控制共享资源,最后一个资源的引用被释放的时 ...

  2. 从零开始写STL—栈和队列

    从零开始写STL-栈和队列 适配器模式 意图:将一个类的接口转换成客户希望的另外一个接口.适配器模式使得原本由于接口不兼容而不能一起工作的那些类可以一起工作. 主要解决:主要解决在软件系统中,常常要将 ...

  3. [C++]STL容器Vector的内存释放

    直接抛出两句话,说明到底应该如何释放Vector占用的内存. “vector的clear不影响capacity,你应该swap一个空的vector.” <Effective STL>中的“ ...

  4. STL容器vector应用注意事项

    [1]提前分配足够空间以免不必要的重新分配和复制代价 关于vector容器重新分配和复制及析构释放的代价,请参见随笔<STL容器之vector>. 应用示例对比代码如下: #include ...

  5. STL容器 vector,list,deque 性能比较

    C++的STL模板库中提供了3种容器类:vector,list,deque对于这三种容器,在觉得好用的同时,经常会让我们困惑应该选择哪一种来实现我们的逻辑.在少量数据操作的程序中随便哪一种用起来感觉差 ...

  6. STL容器 -- Vector

    核心:Vector 是 STL 里的一个向量容器,可以像数组那样进行随机访问,能在尾部插入元素,对于元素的删除和插入可以动态管理内存. 头文件: #include <vector> 构造函 ...

  7. STL - 容器 - vector简单应用

    VectorTest.cpp #include <vector> #include <iostream> #include <string> #include &l ...

  8. 从零开始写STL—哈希表

    static const int _stl_num_primes = 28; template<typename T, typename Hash = xhash<T>> cl ...

  9. 从零开始写STL—模板元编程之any

    any class any; (since C++17) The class any describes a type-safe container for single values of any ...

随机推荐

  1. PHP使用Session遇到的一个Permission denied Notice解决办法

    搜索 session.save_path 在这里你有两个选择,一个是像我一样用; 把这一行注释掉,另一个选择就是修改一个 nobody 用户可以操作的目录,也就是说有读写权限的目录,我也查了下这个默认 ...

  2. ssm基础配置

    1.导包 <dependencies> <dependency> <groupId>org.springframework</groupId> < ...

  3. php学习知识点

    1.PHP 代码被包含在特殊的起始符和结束符中 <? ?> 2.php的用途 服务端脚本 命令行脚本. 编写桌面应用程序.3.输出语句.文本 echo printf4.$_SERVER 是 ...

  4. git ---查看工作状态和历史提交

    1.git查看状态 -git status 2.版权声明 版权声明:新建一个   LICENSE.txt   文件 开源协议:MIT   //开源许可里面的最宽松的一个协议,别人可以随便用你的代码,但 ...

  5. hibernate对象状态 的小问题

    Class classA{ List a; public void setA(List a) { this.a =a; } public List getA() { return this.a; } ...

  6. Android(java)学习笔记187:多媒体之SurfaceView

    1. SurfaceView:     完成单位时间内界面的快速切换(游戏界面流畅感). 我们之前知道一般的View,只能在主线程里面显示,主线程中更新UI.但是SurfaceView可以在子线程中里 ...

  7. python远程控制电脑

    python拥有大量的第三方库,且语法简单.今天老杨就用python实现远程控制电脑 ​ 所谓,谋定而后动,在实现任何一个需求之前,我们需要先分析,捋清楚一个思路,远程控制电脑,无非就是接收远程的命令 ...

  8. sql server数据类型与其他数据库数据类型对应关系

    SELECT * FROM msdb.dbo.MSdatatype_mappings SELECT * FROM msdb.dbo.sysdatatypemappings

  9. RestTemplate接收HashMap变为LinkedHashMap,RestTemplate接收数据后转成json数据出现反斜杠

    使用postForObject方法远程调用接口,正常会返回List<HashMap>,然而实际上却返回List<LinkedHashMap>,同时将此数据进行json转换,变成 ...

  10. 关于阻止Sublime Text更新弹窗提示

    使用Sublime Text有一段时间了,但每次重新打开都会弹出这家伙↑,很烦 网上查了查一些关闭弹窗的教程,大同小异,都说是打开Preferences --> Settings, 添加一行代码 ...