从零开始写STL—容器—vector
从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的更多相关文章
- 从零开始写STL - 智能指针
从零开始写STL - 智能指针 智能指针的分类及其特点: scoped_ptr:初始化获得资源控制权,在作用域结束释放资源 shared_ptr: 引用计数来控制共享资源,最后一个资源的引用被释放的时 ...
- 从零开始写STL—栈和队列
从零开始写STL-栈和队列 适配器模式 意图:将一个类的接口转换成客户希望的另外一个接口.适配器模式使得原本由于接口不兼容而不能一起工作的那些类可以一起工作. 主要解决:主要解决在软件系统中,常常要将 ...
- [C++]STL容器Vector的内存释放
直接抛出两句话,说明到底应该如何释放Vector占用的内存. “vector的clear不影响capacity,你应该swap一个空的vector.” <Effective STL>中的“ ...
- STL容器vector应用注意事项
[1]提前分配足够空间以免不必要的重新分配和复制代价 关于vector容器重新分配和复制及析构释放的代价,请参见随笔<STL容器之vector>. 应用示例对比代码如下: #include ...
- STL容器 vector,list,deque 性能比较
C++的STL模板库中提供了3种容器类:vector,list,deque对于这三种容器,在觉得好用的同时,经常会让我们困惑应该选择哪一种来实现我们的逻辑.在少量数据操作的程序中随便哪一种用起来感觉差 ...
- STL容器 -- Vector
核心:Vector 是 STL 里的一个向量容器,可以像数组那样进行随机访问,能在尾部插入元素,对于元素的删除和插入可以动态管理内存. 头文件: #include <vector> 构造函 ...
- STL - 容器 - vector简单应用
VectorTest.cpp #include <vector> #include <iostream> #include <string> #include &l ...
- 从零开始写STL—哈希表
static const int _stl_num_primes = 28; template<typename T, typename Hash = xhash<T>> cl ...
- 从零开始写STL—模板元编程之any
any class any; (since C++17) The class any describes a type-safe container for single values of any ...
随机推荐
- (一)Spring之初了解
1.认识 Spring 框架 Spring 框架是 Java 应用最广的框架,它的成功来源于理念,而不是技术本身,它的理念包括 IoC (Inversion of Control,控制反转) 和 AO ...
- JDBC连接Oracle工具类
import java.sql.*;import java.util.ResourceBundle; /** * jdbc工具类,负责: * 1. 加载/注册数据库驱动程序 * 2. 获取数据库连接 ...
- [BZOJ2330][SCOI2011]糖果 差分约束系统+最短路
题目链接:http://www.lydsy.com/JudgeOnline/problem.php?id=2330 类似于题目中这种含有不等式关系,我们可以建立差分约束系统来跑最长路或最短路. 对于一 ...
- js promise 介绍和使用
1.什么是promise js是单线程执行的. ajax是典型的异步操作,我们通常会在ajax的成功或者失败之后写上回掉函数.这中写法是一种嵌套的方式,如果回掉多了会造成代码复杂并且难以复用. pro ...
- Java的知识储备及面试-几个方面
1.Java本身语法基础 https://github.com/Snailclimb/JavaGuide/blob/master/Java%E7%9B%B8%E5%85%B3/Java%E5%9F%B ...
- RDO、SAD、SATD、λ相关概念【转】
率失真优化概述: 率失真优化(Rate D isto r t i on Op t i m ized)策略是在率失真理论[3 ]的基础上提出的一种代价函数方案, RDO 的主要思想是, 在计算代价函数时 ...
- nutwk的maven中央仓库及配置
官方maven服务器:https://jfrog.nutz.cn/artifactory/jcenter/ 如果用阿里的maven服务器,特别提醒:
- 最好的Sublime Text插件集合
阅读目录 WebInspector Emmet Git GitGutter & Modific Sublimall AllAutocomplete SublimeREPL DocBlockr ...
- viewer && ImageFlow 图片滚动组件 图片点击放大 可以滚轮放大缩小 viewer
ImageFlow https://finnrudolph.com/products/imageflow https://github.com/countzero/ImageFlow http://w ...
- 数据结构之线性顺序表ArrayList(Java实现)
一.ListMe接口: import java.util.ArrayList; //实现线性表(顺序表和链表)的接口://提供add get isEmpty size 功能public interfa ...