从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. 多个文本框点击复制 zClip (ZeroClipboard)有关问题

    <script type="text/javascript" src="js/jquery.min.js"$amp;>amp;$lt;/script ...

  2. AJPFX实列判断一个字符串是不是对称字符串

    import java.util.Scanner; /** *        判断一个字符串是否是对称字符串 */ public class StringDemo { public static vo ...

  3. avd manager或sdk manager无法打开

    最近开始搞安卓,使用AS启动项目时老是报各种错误,而网上这方面的资料很多都解决不了.只能边实验边做. 定位到avd manager或sdk manager无法打开,网上找了很多资料,都不能解决,知道看 ...

  4. iOS Programming Editing UITableView

    iOS Programming Editing UITableView 1.1 Editing mode  UITableView has an editing property, and when ...

  5. R in action读书笔记(11)-第八章:回归-- 选择“最佳”的回归模型

    8.6 选择“最佳”的回归模型 8.6.1 模型比较 用基础安装中的anova()函数可以比较两个嵌套模型的拟合优度.所谓嵌套模型,即它的一 些项完全包含在另一个模型中 用anova()函数比较 &g ...

  6. sql把两值之和当作条件进行查询

    目的:把表中两个字段之和作为where条件进行过滤查询 //查询在没有过期的记录select a,b from test where a+b>now();// a:存入时间 b:有效期时间段 进 ...

  7. JavaScript——max-age

    https://zhidao.baidu.com/question/391047416053664205.html 页面优化方式之一,延长过期时间.默认max-age=0

  8. jQuery的属性与样式之样式操作.css()

    .css() 方法:获取元素样式属性的计算值或者设置元素的CSS属性 获取: .css( propertyName ) :获取匹配元素集合中的第一个元素的样式属性的计算值 .css( property ...

  9. node.js入门之三

    Node.js REPL(交互式解释器) Node.js REPL(Read Eval Print Loop:交互式解释器) 表示一个电脑的环境,类似 Window 系统的终端或 Unix/Linux ...

  10. Matlab plotyy画双纵坐标图实例

    Matlab plotyy画双纵坐标图实例 x = 0:0.01:20;y1 = 200*exp(-0.05*x).*sin(x);y2 = 0.8*exp(-0.5*x).*sin(10*x);[A ...