STL源码剖析 序列式容器|Vector

容器的概观和分类

  • array 数组 、list 链表、tree树 、stack堆栈、queue队列、hash table散列表、set集合、map映射表
  • 根据数据在容器中的排列顺序,将上述数据结构分为序列式和关联式两种类型

  • SGI STL使用内缩方式来表达基层和衍生层之间的关系
  • 衍生不是派生,本质上是一种内含的关系,例如heap内含vector,priority-queue内含heap;stack 和 queue都内含deque;set map multiset multimap 内含一个 RB-tree,hash_x都内含hashtable
  • 序列式容器,内部元素都可序但是未必有序。C++本身提供了一个序列式容器array,STL提供了vector、list、deque、priority_queue等
  • stack 和 queue都是在deque的基础上改头换面形成的,技术上将其归类为一种 配接器

Vector

  • vector类似于array,二者唯一的差别在于 空间的运用的灵活性。
  • array是静态空间,一旦配置就不会改变大小,如果存储空间不够需要申请空间进行元素的拷贝,这些操作均需要用户自己来执行,最后还需要释放先前拥有的区间。  具体操作:(配置新的空间 | 数据移动 | 释放先前的内存空间 )
  • vector是动态空间,随着元素的加入,内部的实现机制会自行进行内存空间的扩充从而容纳新的元素
  • vector的实现技术:关键在于其对于大小的控制和重新配置的时候对于数据的移动的效率。第二次申请的内存空间是先前的两倍
  • SGI vector将vector实现于更加底层的<stl_vector.h> ,具体使用的时候直接引入<vector>头文件即可
#include <iostream>
#include <new>        //for placement new
#include <cstddef>    //for ptrdiff_t size_t
#include <cstdlib>    //for exit
#include <climits>    //for UINT_MAXnamespace Chy{template <class T>inline T* _allocate(ptrdiff_t size,T*){std::set_new_handler(0);T* tmp = (T*)(::operator new((std::size_t)(size * sizeof (T))));if (tmp == 0){std::cerr << "out of memory" << std::endl;exit(1);}return tmp;}template<class T>inline void _deallocate(T* buffer){::operator delete (buffer);}template<class T1,class T2>inline void _construct(T1 *p,const T2& value){new(p) T1 (value);  //没看懂}template <class T>inline void _destroy(T* ptr){ptr->~T();}template <class T>class allocator{public:typedef T           value_type;typedef T*          pointer;typedef const T*    const_pointer;typedef T&          reference;typedef const T&    const_reference;typedef std::size_t size_type;typedef ptrdiff_t   difference_type;template<class U>struct rebind{typedef allocator<U>other;};pointer allocate(size_type n,const void * hint = 0){return _allocate((difference_type)n,(pointer)0);}void deallocate(pointer p,size_type n){_deallocate(p);}void construct(pointer p,const T& value){_construct(p,value);}void destroy(pointer p){_destroy(p);}pointer address(reference x){return (pointer)&x;}const_pointer const_address(const_reference x){return (const_pointer)&x;}size_type max_size()const{return size_type(UINT_MAX/sizeof (T));}};
}template<class T,class Alloc>
class simple_alloc{
public:static T* allocate(std::size_t n){return 0==n?0:(T*)Alloc::allocate(n * sizeof(T));}static T* allocate(void){return (T*)Alloc::allocate(sizeof (T));}static void deallocate(T* p,size_t n){if (n!=0){Alloc::deallocate(p,n * sizeof(T));}}static void deallocate(T* p){Alloc::deallocate(p,sizeof(T));}
};//如果是copy construction 等同于assignment而且destructor 是 trivial以下就会有效
//如果是POD型别 执行的流程就会跳转到以下函数,这个是通过function template的参数推导机制得到的
template<class ForwardIterator,class Size,class T>
inline ForwardIterator __uninitizlized_fill_n_aux(ForwardIterator first,Size n,const T&x){return fill_n(first,n,x); //交给高阶函数执行
}struct __true_type{};
struct __false_type{};template<class T>
struct __type_traits {typedef __true_type this_dummy_member_must_be_first;typedef __false_type has_trivial_default_constructor;typedef __false_type has_trivial_copy_constructor;typedef __false_type has_trivial_assignment_constructor;typedef __false_type has_trivial_destructor;typedef __false_type is_POD_type;
};//函数的逻辑是
//首先萃取出 迭代器first的value type,然后判断这个型别是否是POD类型
template<class ForwardIterator,class Size,class T,class T1>
inline ForwardIterator __uninitizlized_fill_n(ForwardIterator first,Size n,const T&x,T1*){//以下使用的是__type_traits<T1>::is_POD_type is _PODtypedef typename __type_traits<T1>::is_POD_type is_POD;return __uninitizlized_fill_n_aux(first,n,x,is_POD());
}template<class ForwardIterator,class Size,class T>
ForwardIterator uninitialized_fill_n(ForwardIterator first,Size n,const T&x){return __uninitizlized_fill_n(first,n,x,value_type(first));//使用value_type()判断first的value type
}//alloc 是SGI STL默认的空间配置器
template <class T,class Alloc>
class vector{
public://vector的嵌套型别定义typedef T            value_type;typedef value_type * pointer;typedef value_type * iterator;typedef value_type & reference;typedef std::size_t  size_type;typedef ptrdiff_t    difference_type;
protected://simple_alloc 是SGI STL的空间配置器typedef simple_alloc<value_type,Alloc>data_allocator;iterator start; //表示目前使用空间的头部iterator finish; //表示目前使用空间的尾部iterator end_of_storage; //表示目前可用空间的尾部void insert_aux(iterator position,const T&x);void deallocate(){if (start){data_allocator ::deallocate(start,end_of_storage - start);}}void fill_initialize(size_type n,const T& value){start = allocate_and_fill(n,value);finish = start+n;end_of_storage = finish;}public:iterator begin(){return start;}iterator end(){return finish;}size_type size() const {return size_type(end() - begin());}size_type capacity() const{return size_type (end_of_storage - begin());}bool empty(){return begin() == end();}reference operator[] (size_type n){return (*(begin() + n));}vector():start(0),finish(0),end_of_storage(0){};vector(size_type n,const T& value){fill_initialize(n,value);}vector(int n,const T& value){fill_initialize(n,value);}vector(long n,const T& value){fill_initialize(n,value);}explicit vector(size_type n){fill_initialize(n,T());}~vector(){Chy::allocator<T>::destroy(start,finish);  //全局函数deallocate();  //vector的成员函数}reference front(){return *begin();}  //第一个元素reference back(){return *(end()-1);} //最后一个元素void push_back(const T& x){if (finish != end_of_storage){Chy::allocator<T>::construct(finish,x);++finish;} else{insert_aux(end(),x);// vector的成员函数}}void pop_back(){  //将最尾端的元素取出--finish;Chy::allocator<T>::destroy(finish);}iterator erase(iterator position){ //清除某个位置上的元素if (position+1 != end()){std::copy(position+1,finish,position); //后续元素向前移动 进行元素的覆盖}--finish;Chy::allocator<T>::destroy(finish);return position;}void resize(size_type new_size,const T& x){if (new_size < size()){erase(begin()+new_size,end());} else{std::inserter(end(),new_size - size(),x);}}void resize(size_type new_size){resize(new_size,T());}void clear(){erase(begin(),end());}
protected://配置空间并填满内容iterator allocate_and_fill(size_type n,const T&x){iterator result = data_allocator ::allocate(n);uninitialized_fill_n(result,n,x); //全局函数return result;}
};

vector迭代器

  • vector维护的是一个连续的线性空间,所以不论其元素的的型别是怎样,普通的指针都可以作为vector的迭代器从而满足所有的必要的条件
  • 因为vector迭代器所需要的操作,比如 operator*  operator-> operator++ operator-- operator+ operator- operator+= operator-= 是普通指针天生就具备的能力
  • 普通指针支持随机存取,正因为普通指针具备这样的能力,所以vector提供的是random Access iterators
  • vector的迭代器是普通的指针
  • 例子
std::vector<int>::iterator i_vite; //i_vite的型别本质上是int*
std::vector<Shape>::iterator s_vite;  //s_vite的型别本质上是Shape*

Vector数据结构

  • 数据结构很简单:线性连续空间
  • 使用start和finish指向配置的连续空间中的已经使用的范围区间
  • 使用迭代器end_of_storage指向的是整个一块连续的空间(包含备用空间)的尾端
    iterator start; //表示目前使用空间的头部iterator finish; //表示目前使用空间的尾部iterator end_of_storage; //表示目前可用空间的尾部
  • vector分配的内存空间实际上是大于客户端申请的内存空间,主要是为了防止以后内存的扩充,这个便是容量的概念。
  • 即容量的概念 永远大于或者等于其大小,一旦容量等于大小,即是满载。
  • 使用三个迭代器,完成 vector的首尾标定、使用空间的大小、容量、空容器的判断、注标[]的运算子、最前端的元素 和最末尾的元素等等

Vector的构造和内存管理

  • //vector缺省使用alloc作为空间配置器,并据此另外定义了data_allocator,为的是更方便的以元素大小作为配置单位
  •     typedef simple_alloc<value_type,Alloc>data_allocator; 所以data_allocator::allocate(n)  ; 表示配置n个元素的空间
  • vector提供了很多的构造器

  • uninitialized_fill_n 根据第一个参数的特性(使用type_traits 进行类型推导),决定使用fill_n() 还是通过反复调用 construct() 来完成任务
  • 使用push_back()将元素插入到vector的尾端的时候,函数检查是否存在备用的空间,如果有直接在备用空间进行元素的构造,并且调整迭代器finish使得vector变大
  • 如果没有备用空间就需要进行元素的扩充(配置 、移动数据、释放空间)
    void push_back(const T& x){if (finish != end_of_storage){              //还存在备用空间Chy::allocator<T>::construct(finish,x); //直接进行元素的构造++finish;                               //调整水位高度} else{                                     //无备用空间insert_aux(end(),x);// vector的成员函数}}
//insert_aux 伪代码
template<class T,class Alloc>
void vector<T,Alloc>::insert_aux(iterator position, const T &x) {if (finish != end_of_storage){  //还有备用空间//在备用空间起始处构造一个元素,并使用vector最后一个元素数值为其初始化Chy::allocator<T>::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;//配置原则://如果原大小为0 则配置1 (单位是 元素的大小)//如果原大小不为0 配置内存大小是先前的两倍//前半段用于放置先前的数据,后半段用于存储新的数据iterator new_start = data_allocator::allocate(len); //实际配置内存iterator new_finish = new_start;try{//进行先前元素的拷贝,将原vector的内容拷贝到新的vectornew_finish = unintialized_copy(start,position,new_start);//为新的元素设定初始数值 xChy::allocator<T>::construct(new_finish,x);//调整水位++new_finish;//将原vector的备用空间中的内容也忠实拷贝过来new_finish = unintialized_copy(position,finish,new_finish);}catch (){//commit or rollbackChy::allocator<T>::destroy(new_start,new_finish);data_allocator::allocate(new_start,len);throw ;}//析构并且释放原有的vectorChy::allocator<T>::destroy(begin(),end());deallocate();//调整迭代器 指向新的vectorstart = new_start;finish = new_finish;end_of_storage = new_start + len;}
}
  •  动态增加大小 不是在现有的内存空间后面接入新的空间 (  无法保证原有空间之后是否还尚有可以配置的空间 )
  • 需要在背的地方申请空间
  • 因此一旦造成空间的重新配置,指向原有vector的所有迭代器就会失效了
    void pop_back(){  //将最尾端的元素取出--finish;     //将尾端标记往前移动一格,表示放弃尾端元素Chy::allocator<T>::destroy(finish);}
    void pop_back(){  //将最尾端的元素取出--finish;     //将尾端标记往前移动一格,表示放弃尾端元素Chy::allocator<T>::destroy(finish);}//清除[first ,last)内部所有元素iterator erase(iterator first,iterator last){iterator i = copy(last,finish,first);Chy::allocator<T>::destroy(i,finish);finish = finish - (last - first);return first;}//清除某个特定位置上的元素iterator erase(iterator position){ //清除某个位置上的元素if (position+1 != end()){std::copy(position+1,finish,position); //后续元素向前移动 进行元素的覆盖}--finish;Chy::allocator<T>::destroy(finish);return position;}void clear(){erase(begin(),end());}

  • 这一块还不是很清晰 需要进一步完善 insert(position,n,x)

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/446281.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

ansible 修改文件变量_Ansible Playbook中的变量与引用

Ansible是一个系列文章&#xff0c;我会尽量以通俗易懂、诙谐幽默的总结方式给大家呈现这些枯燥的知识点&#xff0c;让学习变的有趣一些。Ansible自动化运维前言前面有说到使用playbook来搞一些复杂的功能&#xff0c;我们使用YAML来写playbook&#xff0c;就像我们用其它语言…

STL源码剖析 list概述

目录 list的节点(node) list迭代器 list 的构造和内存管理 list 的元素操作 list相较于vector连续的线性空间就显得很复杂&#xff0c;他的存储空间是不连续的&#xff0c;好处是每次插入和删除一个元素的时候&#xff0c;只需要配置或者释放一个元素的空间 插入和删除十分的…

vsftp不允许切换到其它目录_IntelliJ IDEA如何对project的目录进行筛选显示?

如果你的项目很庞大&#xff0c;同一个功能用到的各种文件散落在多个文件夹&#xff0c;开发时切换不便&#xff0c;可以利用scope功能&#xff0c;只显示该功能用到的文件&#xff0c;让project列表十分清爽&#xff0c;提高开发效率。本文使用的IDEA版本为2020.1。1、打开sco…

密码学专题 对称加密算法

一般来说&#xff0c;使用OpenSSL对称加密算法有两种方式&#xff0c;一种是使用API函数的方式&#xff0c;一种是使用OpenSSL提供的对称加密算法指令方式。本书将介绍对称加密算法的指令方式OpenSSL的对称加密算法指令主要用来对数据进行加密和解密处理&#xff0c;输入输出的…

网络防火墙单向和双向_单向晶闸管与双向晶闸管之间的不同之处

晶闸管是回一个可以控导点开关&#xff0c;能以弱电去控制强电的各种电路。晶闸管常用于整流&#xff0c;调压&#xff0c;交直流变化&#xff0c;开关&#xff0c;调光等控制电路中。具有提交小&#xff0c;重量轻&#xff0c;耐压高&#xff0c;容量大&#xff0c;效率高&…

python版本切换_怎么切换python版本

展开全部 &#xff08;1&#xff09;分别安2113装 python-2.7.12.amd64.msi python-3.5.2-amd64.exe &#xff08;python官网下载的&#xff09; 顺序无所谓&#xff08;为5261了看着4102方便&#xff0c;我把安装路径修改统一了1653&#xff09; &#xff08;2&#xff09;配置…

react.lazy 路由懒加载_Vue面试题: 如何实现路由懒加载?

非懒加载import List from /components/list.vue const router new VueRouter({routes: [{ path: /list, component: List }] })方案一(常用)const List () > import(/components/list.vue) const router new VueRouter({routes: [{ path: /list, component: List }] })方…

STL源码剖析 deque双端队列 概述

vector是单向开口的连续线性空间&#xff0c;deque是一种双向开口的连续线性空间。deque可以在头尾两端分别进行元素的插入和删除操作vector和deque的差异 1&#xff0c;deque允许常数时间内对于头端元素进行插入和删除操作2&#xff0c;deque没有所谓容量(capacity)的概念&…

STL源码剖析 stack 栈 概述->(使用deque双端队列 / list链表)作为stack的底层容器

Stack是一种先进后出的数据结构&#xff0c;他只有一个出口stack允许 新增元素、移除元素、取得最顶端的元素&#xff0c;但是无法获得stack的内部数据&#xff0c;因此satck没有遍历行为Stack定义的完整列表 (双端队列作为Stack的底层容器) 将deque作为Stack的底部结构&#…

python怎么实现图像去噪_基于深度卷积神经网络和跳跃连接的图像去噪和超分辨...

Image Restoration Using Very Deep Convolutional Encoder-Decoder Networks with Symmetric Skip Connections作者&#xff1a;Xiao-Jiao Mao、Chunhua Shen等本文提出了一个深度的全卷积编码-解码框架来解决去噪和超分辨之类的图像修复问题。网络由多层的卷积和反卷积组成&a…

STL源码剖析 queue队列概述

queue是一种先进先出的数据结构&#xff0c;他有两个出口允许新增元素&#xff08;从最底端 加入元素&#xff09;、移除元素&#xff08;从最顶端删除元素&#xff09;&#xff0c;除了对于顶端和底端元素进行操作之外&#xff0c;没有办法可以获取queue的其他元素即queue没有…

python为什么运行不了_python为什么会环境变量设置不成功

学习python编程&#xff0c;首先要配置好环境变量。本文主要讲解python的环境变量配置&#xff0c;在不同版本下如何安装 Windows 打开Python官方下载网站 x86:表示是32位电脑 x86-64:表示是64位电脑 目前Python版本分为2.x版本和3.x版本。推荐大家使用3.x版本。 设置环境变量&…

STL 源码剖析 heap堆

heap不属于STL容器的组件&#xff0c;属于幕后角色&#xff0c;是priority_queue的助手priority_queue 允许用户以任何次序将任何元素推入容器内&#xff0c;但是取出的时候需要从优先级最高(也就是数值最高)的元素开始取&#xff0c;这种思想是基于heap的函数实现如果使用list…

java 打印星号

代码1 package lesson.l2_for; //6列4行 //****** //****** //****** //****** public class ForDemo8 {public static void main(String[] args) {for (int i1;i<4;i){for (int j 1; j <6 ; j) {System.out.print("*");}System.out.println();}} }代码2 pa…

python从小白到大牛百度云盘_Java从小白到大牛 (关东升著) 中文pdf+mobi版[36MB]

《Java从小白到大牛》是一本Java语言学习立体教程&#xff0c;读者群是零基础小白&#xff0c;通过本书的学习能够成为Java大牛。主要内容包括&#xff1a;Java语法基础、Java编码规范、数据类型、运算符、控制语句、数组、字符串、面向对象基础、继承与多态、抽象类与接口、枚…

java打印九九乘法表

代码1 package lesson.l5_loop; //九九乘法表 //1*11 //2*12 2*24 //3*13 3*26 3*39 //4*14 4*28 4*312 4*416 //5*15 5*210 5*315 5*420 5*525 //6*16 6*212 6*318 6*424 6*530 6*636 //7*17 7*214 7*321 7*428 7*535 7*642 7*749 //8*18 8*216 8*324 8*432 8*540 8*648 8*75…

STL源码剖析 priority_queue

priority_queue是一个拥有权重概念的queue&#xff0c;允许底部加入新的元素&#xff0c;头部删除旧的元素&#xff0c;以及审视元素数值的操作priority_queue带有权重的概念&#xff0c;即元素按照权重进行排列&#xff0c;而不是按照插入队列的顺序进行排序。要求权值高者在前…

python数字1 3怎么表示_Python入门篇之数字

数字类型 数字提供了标量贮存和直接访问。它是不可更改类型&#xff0c;也就是说变更数字的值会生成新的对象。当然&#xff0c;这个过程无论对程序员还是对用户都是透明的&#xff0c;并不会影响软件的开发方式。 Python 支持多种数字类型&#xff1a;整型、长整型、布尔型、双…

STL源码剖析 slist单向链表概述

概述 SGI STL的list是一个双向链表&#xff0c;单向链表是slist&#xff0c;其不在标准规格之内单向和双向链表的区别在于&#xff0c;单向链表的迭代器是单向的 Forward Iterator&#xff0c;双向链表的迭代器属于双向的Bidirectional Iterator。因此很多功能都被受限但是单向…

output怎么用_用树莓派实现室内温度监控

树莓派加上温度传感器实现室内温度监控。可用于家庭&#xff0c;轿车&#xff0c;工业&#xff0c;农业 等许多方面。可做温度预警&#xff0c;自动降温等操作。各位小伙伴可自行脑补发挥。1.硬件准备a.树莓派&#xff08;Raspberry Pi&#xff09;一个b.DS18B20温度传感器一个…