第一篇
基础
c++STL 容器大纲呈现
- 分配器(空间配置器):一般的分配器的std:alloctor都含有两个函数allocate与deallocte ,这两个函数分别调用operator new()与delete(),这两个函数的底层又分别是malloc()and free();但是每次malloc会带来格外开销(因为每次malloc一个元素都要带有附加信息)
- 容器之间的实现关系以及分类
-
list属于双向链表,其结点与list本身是分开设计的
template<class T, class Alloc = alloc> class list { protected: typedef __list_node<T> list_node; public: typedef list_node *link_type; typedef __list_iterator<T, T &, T *> iterator; protected: link_type node; }; //学习到了一个分析方法,拿到这样一个类,先看它的数据比如上面的link_type node,然后我们再看它的前缀,link_type,去上面在link_type,找到typedef list_node *link_type;按这个方法继续找到上面的typedef __list_node<T> list_node;我们发现__list_node<T>是下面的类,我们一层层的寻找,就能看懂这些源码
template<class T> struct __list_node { typedef void *void_pointer; void_pointer prev; void_pointer next; T data; };
list是一个环状的双向链表,同时它也满足STL对于“前闭后开”的原则,即在链表尾端可以加上空白节点
-
【重点】:
list的迭代器的设计
:迭代器是泛化的指针所以里面重载了->,–,++,*(),等运算符,同时迭代器是算法与容器之间的桥梁,算法需要了解容器的方方面面,于是就诞生了5种关联类型,(这5种类型是必备的,可能还需要其他类型)我们知道算法传入的是迭代器或者指针,算法根据传入的迭代器或指针推断出算法所想要了解的容器里的5种关联类型的相关信息。由于光传入指针,算法推断不出来其想要的信息,所以我们需要一个中间商(萃取器)也就是我们所说的iterator traits类,对于一般的迭代器,它直接提供迭代器里的关联类型值,而对于指针和常量指针,它采用的类模板偏特化,从而提供其所需要的关联类型的值。
// 针对一般的迭代器类型,直接取迭代器内定义的关联类型
template<class I>
struct iterator_traits {
typedef typename I::iterator_category iterator_category;
typedef typename I::value_type value_type;
typedef typename I::difference_type difference_type;
typedef typename I::pointer pointer;
typedef typename I::reference reference;
};
// 针对指针类型进行特化,指定关联类型的值
template<class T>
struct iterator_traits<T *> {
typedef random_access_iterator_tag iterator_category;
typedef T value_type;
typedef ptrdiff_t difference_type;
typedef T* pointer;
typedef T& reference;
};
// 针对指针常量类型进行特化,指定关联类型的值
template<class T>
struct iterator_traits<const T *> {
typedef random_access_iterator_tag iterator_category;
typedef T value_type; // value_tye被用于创建变量,为灵活起见,取 T 而非 const T 作为 value_type
typedef ptrdiff_t difference_type;
typedef const T* pointer;
typedef const T& reference;
};
- 关于前置++,与后置++的不同形式
self& operator++() {
node = (link_type) ((*node).next);
return *this;
}
const self operator++(int) {
self tmp = *this;
++*this;
return tmp;
}
//为了区分前后置,重载函数是以参数类型来区分,在调用的时候,编译器默默给int指定为一个0
//第一为什么后置返回对象,而不是引用,因为后置为了返回旧值创建了一个临时对象,在函数结束的时候这个对象就会被销毁,如果返回引用,那么我请问你?你的对象对象都被销毁了,你引用啥呢?
//第二为什么后置前面也要加const,其实也可以不加,但是为了防止你使用i++++,连续两次的调用后置++重载符,为什么呢?原因有两个:它与内置类型行为不一致;你无法活得你所期望的结果,因为第一次返回的是旧值,而不是原对象,你调用两次后置++,结果只累加了一次,所以我们必须手动禁止其合法化,我就要在前面加上const。
//第三,处理用户的自定义类型时,最好使用前置++,因为他不会创建临时对象,进而不会带来构造和析构而造成的格外开销。
stl源码剖析
容器-序列容器
vector容器
-
vector概述:与数组非常相似,唯一差别就是vector是动态空间,能够自动扩充空间容纳元素
-
vector源码
templeta<class T,class Alloc=alloc> class vector{ public: typedef T value_type; typedef value_type* pointer; typedef value_type& reference; typedef value_type* iterator; typedef size_t size_type; typedef ptrdiff_t difference_type //嵌套类型定义,也可以是关联类型定义 protected: typedef simple_alloc<value_type,Alloc> data_alloctor //空间配置器(分配器) iterator start; iterator finish; iterator end_of_storage; //这3个就是vector里的数据,所以一个vector就是包含3个指针12byte,下面有图介绍 void insert_aux(iterator position ,const T& x); //这个就是vector的自动扩充函数,在下面章节我会拿出来分析 void deallocate(){ if(start) data_allocator::deallocate(start,end_of_storage); } //析构函数的部分实现函数 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()const{return begin()==end();} reference operator[](size_type n){return *(begin()+n);}; //重载[]说明vector支持随机访问 vector():start(0),end(0),end_of_storage(0){}; vector(size_type n,const T&value)(fill_initialize(n,value);); vector(long n,const T&value)(fill_initialize(n,value);); vector(int n,const T&value)(fill_initialize(n,value);); explicit vector(size_type n){fill_initialize(n,T());}; //重载构造函数 ~vector(){ destory(start,finish);//全局函数,析构对象 deallocate();//成员函数,释放空间 } //接下来就是一些功能函数 reference front(){return *begin();}; reference back(){return *(end()-1);}; void push_back(const T&x){ if(finsih!=end_of_storage){ construct(finish,x); ++finish; } else insert_aux(end(),x); //先扩充在添加 } void pop_back(){ --finish; destory(finish); } iterator erase(iterator position){ if(position+1!=end()) copy(position+1,finish,position); --finish; destory(finish); return position; } void resize(size_type new_size,const T&x){ if(new_size()<size()) erase(begin()+new_size,end()); else insert(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);//全局函数 } }
-
vector迭代器:由于vector维护的是一个线性区间,所以普通指针具备作为vector迭代器的所有条件,就不需要重载operator+,operator*之类的东西
template <class T, class Alloc = alloc> class vector { public: typedef T value_type; typedef value_type* iterator; //vector的迭代器是原生指针 ... };
-
vector的数据结构:线性空间。为了降低配置空间的成本,我们必须让其容量大于其大小。
-
vector的构造以及内存管理:当我们使用push_back插入元素在尾端的时候,我们首先检查是否还有备用空间也就是说end是否等于end_of_storage,如果有直接插入,如果没有就扩充空间
template<class T,class Alloc> void vector<T,Alloc>::insert_aux(iterator position,const T&x){ if(finish!=end_of_storage){//有备用空间 consruct(finish,*(finish-1));//在备用空间处构造一个元素,以vector最后一个元素为其初值 ++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; //vector中如果没有元素就配置一个元素,如果有就配置2倍元素。 iterator new_start=data_allocator::allocate(len); iterator new_finish=new_start; try{ //拷贝插入点之前的元素 new_finish=uninitialized_copy(start,position,new_start); construct(new_finish,x); ++new_finish; //拷贝插入点之后的元素 new_finish=uninitialized_copy(position,finish,new_finish); } catch(){ destroy(new_start,new_finish); data_allocator::deallocate(new_start,len); throw; } //析构并释放原vector destory(begin(),end()); deallocate(); //调整迭代器指向新的vector start=new_start; finish=new_finish; end_of_storage=new_start+len; } } //整个分为3个部分,配置新空间,转移元素,释放原来的元素与空间,因此一旦引起空间配置指向以前vector的所有迭代器都要失效。
-
vector的部分元素操作:pop_back,erase,clear,insert
-
void pop_back(){ --finish; destory(finish); }
-
//erase版本一:清除范围元素 iterator erase(iterator first,iterator last){ interator i=copy(last,finish,first); destory(i,finish); finish=finish-(last-first); return first; } //版本二:清除某个位置上的元素 iterator erase(iterator position){ if(position+1!=finish){ copy(position+1,finish,position); --finish; distory(finish); return position; } }
-
void clear(){erase(begin(),end())};
-
template<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_strage-finish)>n){ //备用空间大于插入的元素数量 T x_copy=x; //以下计算插入点之后的现有元素个数 const size_type elems_after=finish-positon; iterator old_finish=finish; if(elems_after>n){ //插入点之后的元素个数大于要插入的元素个数 uninitialiazed_copy(finish-n,finish,finish); finish+=n;//将vector的尾端标记后移 copy_backward(position,old_finish-n,old_finish); fill(position,old_finish,x_copy);//从插入点之后开始插入新值 } else{ //插入点之后的元素个数小于要插入的元素个数 uninitialiazed_fill_n(finish,n-elems_after,finish); finish+=n-elems_after; uninitialiazed_copy(position,old_finish,finish); finish+=elems_after; fill(position,old_finish,x_copy); } else{ //备用空间小于要插入元素的个数 //首先决定新长度,原长度的两倍,或者老长度+新的元素个数 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; _STL_TRY{ //拷贝插入点之前的元素 new_finish=uninitialized_copy(start,position,new_start); //把新增元素(初值皆为n)传入新空间 new_finish=uninitialized_fill_n //拷贝插入点之后的元素 new_finish=uninitialized_copy(position,finish,new_finish); //这一段有利于理解上面的insert_aux函数 } #ifdef _STL_USE_EXCEPTIONS catch(){ //如果有异常发生 destroy(new_start,new_finish); data_allocator::deallocate(new_start,len); throw; } #endif/* _STL_USE_EXCEPTIONS*/ //析构并释放原vector destory(begin(),end()); deallocate(); //调整迭代器指向新的vector start=new_start; finish=new_finish; end_of_storage=new_start+len; } } } }
-
deque容器
-
deque概述:deque是一个双端开口的连续线性空间,其内部为分段连续的空间组成,随时可以增加一段新的空间并链接 注意:
由于deque的迭代器比vector要复杂,这影响了各个运算层面,所以除非必要尽量使用vector;为了提高效率,在对deque进行排序操作的时候,我们可以先把deque复制到vector中再进行排序最后在复制回deque
-
deque中控器:deque是由一段一段的定量连续空间构成。一旦有必要在其头端或者尾端增加新的空间,便配置一段定量连续空间,串接在整个deque的头端或者尾端【好处:避免“vector的重新配置,复制,释放”的轮回,维护连整体连续的假象,并提供随机访问的接口;坏处:其迭代器变得很复杂】。
deque采用一块map作为主控,其中的每个元素都是指针,指向另一片连续线性空间,称之为缓存区,这个区才是用来储存数据的。
template<class T,class Alloc=alloc,size_t Bufsize=0>
class deque{
public:
typedef T value_type ;
typedef value_type pointer*;
typedef size_t size_type;
...
public:
typedef _deque_iterator<T,T&,T*,BufSiz> iterator;
protected:
typedef pointer* map_pointer;
protected:
iterator start;
iterator finish;
map_pointer map;//指向map
size_type map_size;//map内可容纳多少指针
}
//map其实是一个T**
- deque迭代器
template<class T,class Ref,class Ptr,size_t BufSiz>
struct _deque_iterator{
typedef _deque_iterator<T,T&,T*,BufSiz> iterator;
typedef _deque_iterator<T,cosnt T&,const T*,BufSiz>const_iterator;
static size_t buffer_size(){return _deque_buf_size(BufSiz,sizeof(T));};
typedef randem_access_iterator_tag iterator_category;
typedef T value_type;
typedef Ptr pointer;
typedef Ref reference;
typedef size_t size_type;
typedef ptrdiff_t difference_type;
typedef T** map_pointer;
typedef _deque_iterator self;
T* cur;
T* first;
T* last;
map_pointer node;
...
//这是一个决定缓存区大小的函数
inline size_t _deque_buf_size(size_t n,size_t sz){
return n!=0?n;(sz<512?size_t(512/sz):size_t(1));
}
}
deque拥有两个数据成员-start与finish迭代器,分别由deque:begin()与deque:end()传回;
//迭代器的关键行为,其中要注意的是一旦遇到缓冲区边缘,可能需要跳一个缓存区
void set_node(map_pointer new_node){
node=new_node;
first=*new_node;
last=first+difference_type(buffer_size());
}
//接下来重载运算子是_deque_iterator<>成功运作的关键
reference operator*()const{return *cur;}
pointer operator->()const{return &(operator*());}
difference_type operator—(const self& x)const{
return difference_type(buffer_szie())*(node-x.node-1)+(cur-first)+(x.last-x.cur);
}
self& operator++(){
++cur;
if(cur==last){
set_node(node+1);
cur=first;
}
return *this;
}
self operator++(int){
self temp=*this;
++*this;
return temp;
}
self& operator--(){
if(cur==first){
set_node(node-1);
cur=last;
}
--cur;
return *this;
}
self operator-(int){
self temp=*this;
--*this;
return temp;
}
//以下实现随机存取,迭代器可以直接跳跃n个距离
self& operator+=(difference_type n){
difference_type offest=n+(cur-first);
if(offest>0&&offest<difference_type(buffer_size()))
cur+=n;
else{
offest>0?offest/fifference_type(buffer_size()):-difference_type((-offest-1)/buffer_size())-1;
set_node(node+node_offest);
cur=first+(offest-node_offest*difference_type(buffer_size()));
}
return *this;
}
self operator+(differnece_type n){
self tmp=*this;
return tmp+=n;
}
self operator-=(){return *this+= -n;}
self operator-(difference_type n){
self temp=*this;
return *this-=n;
}
rference operator[](difference_type n){
return *(*this+n);
}
bool operator==(const self&x)const{return cur==x.cur;}
bool operator!=(const self&x)const{return !(*this==x);}
bool operatoe<(const self&x)const{
return (node==x.node)?(cur<x.cur):(node-x.node);
}
-
deque数据结构:deque除了维护一个map指针以外,还维护了start与finish迭代器分别指向第一缓冲区的第一个元素,和最后一个缓冲区的最后一个元素的下一个元素,同时它还必须记住当前map的大小。具体结构和源代码看上面
-
deque的构造与管理
//deque首先自行定义了两个空间配置器
typedef simple_alloc<value_type,Alloc>data_allocator;
typedef simple_alloc<pointer,Alloc>map_allocator;
deque中有一个构造函数用于构造deque结构并赋初值
deque(int n,const value_type& value):start(),finish(),map(0),map_size(0){
fill_initialize(n,value);//这个函数就是用来构建deque结构,并设立初值
}
template<class T,class Alloc,size_t BufSize)
void deque<T,Alloc,BufSize>::fill_initialize(size_type n,const value_type &value){
creat_map_and_node(n);//安排结构
map_pointer cur;
_STL_TRY{
//为每个缓存区赋值
for(cur=start.node;cur<finish.node;++cur)
uninitalized_ fill(*cur,*cur+buffer_size(),value);
//设置最后一个节点有一点不同
uninitalized_fill(finish.first,finish.cur,value);
}
catch(){
...
}
}
template<class T,class Alloc,size_t Bufsize>
void deque<T,alloc,Bufsize>::creat_map_and_node(size_type num_elements){
//需要节点数=元素个数/每个缓存区的可容纳元素个数+1
size_type num_nodes=num_elements/Buf_size()+1;
map_size=max(initial_map_size(),num_nodes+2);//前后预留2个供扩充
//创建一个大小为map_size的map
map=map_allocator::allocate(map_size);
//创建两个指针指向map所拥有的全部节点的最中间区段
map_pointer nstart=map+(map_size()-num_nodes)/2;
map_poniter nfinish=nstart+num_nodes-1;
map_pointer cur;
_STL_TRY{
//为每个节点配置缓存区
for(cur=nstart;cur<nfinish;++cur)
+cur=allocate_node();
}
catch(){
...
}
//最后为deque内的start和finish设定内容
start.set_node(nstart);
finish.set_node(nfinish);
start.cur=start.first;
finish.cur=finish.first+num_elements%buffer_szie();
}
接下来就是插入操作的实现,第一,首先判断是否有扩充map的需求,若有就扩,然后就是在插入函数中,首先判断是否在结尾或者开头从而判断是否跳跃节点。
void push_back(const value_type&t){
if(finish.cur!=finish.last-1){
construct(finish.cur,t);
++finish.cur;
}
else
push_back_aux(t);
}
//由于尾端只剩一个可用元素空间(finish.cur=finish.last-1),所以我们必须重新配置一个缓存区,在设置新元素的内容,然后更改迭代器的状态
tempalate<class T,class Alloc,size_t BufSize>
void deque<T,alloc,BufSize>::push_back_aux(const value_type&t){
value_type t_copy=t;
reserve_map_at_back();
*(finish.node+1)=allocate_node();
_STL_TRY{
construct(finish.cur,t_copy);
finish.set_node(finish.node+1);
finish.cur=finish.first;
}
-STL_UNWIND{
deallocate_node(*(finish.node+1));
}
}
//push_front也是一样的逻辑
stack(栈)与queue(队列)
- 概述:栈与队列被称之为duque的配接器,其底层是以deque为底部架构。通过deque执行具体操作
- stack无迭代器
- 源码剖析
template<class T,class Sequence=deque<T>>
class stack{//_STL_NULL_TMPL_ARGS展开为<>
friend bool operator==_STL_NULL_TMPL_ARGS(const stack&,const stack&);
friend bool operator<_STL_NULL_TMPL_ARGS(coonst stack&,const stack&);
public:
typedef typename Sequence:: value_type value_type;
typedef typename Sequence:: size_type size_type;
typedef typename Sequence:: reference reference;
typedef typename Sequence:: const_reference const_refernece;
protected:
Sequence c;
public:
bool empty()const{return c.empty();}
size_type size()const{return c.size();}
reference top()(return c.back();)
const_rference top()const{return c.back();}
void push(const value_type&x){c.push_back(x);}
void pop_back(){c.pop_back();}
};
template <class T,class Sequence>
bool operator==(const stack<T,Sequence>&x,const stack<T,Sequence>&y){
return x.c==y.c;
}
template <class T,class Sequence>
bool operator<(const stack<T,Sequence>&x,const stack<T,Sequence>&y){
return x.c<y.c;
}
//queue的源码和stack差不多
-
heap(堆):建立在完全二叉树上,分为两种,大根堆,小根堆,其在STL中做priority_queue的助手,即,以任何顺序将元素推入容器中,然后取出时一定是从优先权最高的元素开始取,完全二叉树具有这样的性质,适合做priority_queue的底层
- priority_queue,优先队列,也是配接器。其内的元素不是按照被推入的顺序排列,而是自动取元素的权值排列,确省情况下利用一个max-heap完成,后者是以vector—表现的完全二叉树。
template<class T,class Sequence=vector<T>,class Compare=less<typename Sequence::value_type>>
class priority_queue{
public:
typedef typename Sequence:: value_type value_type;
typedef typename Sequence:: size_type size_type;
typedef typename Sequence:: reference reference;
typedef typename Sequence:: const_reference const_refernece;
protected:
Sequence c;//底层容器
Compare comp//容器比较大小标准
public:
priority_queue():c(){}
explicit priority_queue(const Compare &x):c(),comp(x){}
//以下用到的make_heap(),push_heap(),pop_heap()都是泛型算法
//任何一个构造函数都可以立即在底层产生一个heap
template <class InputIterator>
priority_queue(InputIterator first,InputIterator last const Compare&x)
:c(first,last),comp(x){make_heap(c.begin(),c.end(),comp);}
template <class InputIterator>
priority_queue(InputIterator first,InputIterator last const Compare&x)
:c(first,last) {make_heap(c.begin(),c.end(),comp);}
bool empty()const{return c.empty();}
size_type size()const{return c.size();}
const_reference top()const{return c.front();}
void push(const value_type&x){
_STL_TRy{
c.push_back(X);
push_heap(c.begin(),c.end(),comp);
}
_STL_UNWIND{c.clear()};
}
void pop(){
_STL_TRY{
pop_heap(c.begin(),c.end(),comp);
c.pop_back();
}
_STL_UNWEIND{c.clear()};
}
};
/priority_queue无迭代器
typedef typename Sequence:: size_type size_type;
typedef typename Sequence:: reference reference;
typedef typename Sequence:: const_reference const_refernece;
protected:
Sequence c;//底层容器
Compare comp//容器比较大小标准
public:
priority_queue():c(){}
explicit priority_queue(const Compare &x):c(),comp(x){}
//以下用到的make_heap(),push_heap(),pop_heap()都是泛型算法
//任何一个构造函数都可以立即在底层产生一个heap
template <class InputIterator>
priority_queue(InputIterator first,InputIterator last const Compare&x)
:c(first,last),comp(x){make_heap(c.begin(),c.end(),comp);}
template <class InputIterator>
priority_queue(InputIterator first,InputIterator last const Compare&x)
:c(first,last) {make_heap(c.begin(),c.end(),comp);}
bool empty()const{return c.empty();}
size_type size()const{return c.size();}
const_reference top()const{return c.front();}
void push(const value_type&x){
_STL_TRY{
c.push_back(X);
push_heap(c.begin(),c.end(),comp);
}
_STL_UNWIND{c.clear()};
}
void pop(){
_STL_TRY{
pop_heap(c.begin(),c.end(),comp);
c.pop_back();
}
_STL_UNWEIND{c.clear()};
}
};//priority_queue无迭代器