STL源码剖析—顺序容器
一、vector
1、vector简介:
vector的数据安排及其操作方式与数组非常相似,微小的差别在于空间的使用,数组是静态空间,一旦配置了就不能改变。vector是动态空间,随着元素的加入,它的内部机制会自行扩充空间以容纳新元素。
2、vector的构造和内存管理
其数据结构为:
1 class vector 2 { 3 ... 4 private: 5 iterator start; //表示目前使用空间的头 6 iterator finish; //表示目前使用空间的尾 7 iterator end_of_storage; //表示目前可用空间的尾 8 ... 9 };
vector提供许多的constructs,其中一个允许我们指定空间大小及初值。
1 vector(size_type n, const T& value) //构造函数,允许指定vector大小n和初值value 2 { 3 fill_initialize(n, value); 4 } 5 void fill_initialize(size_type n, const T& value) 6 { 7 start = allocate_and_fill(n, value); 8 finish = start + n; 9 end_of_storage = finish; 10 } 11 iterator allocate_and_fill(size_type n, const T& x) 12 { 13 iterator reslut = data_allocator::allocator(n);//配置n个元素空间 14 uninitialized_fill_n(result, n, x); //全局函数 15 return result; 16 }//uninitialized_fill_n()根据第一参数的型别特性决定使用fill_n()或反复调用construct()来完成任务,
//如果[first,n)范围内的每一个迭代器都指向未初始化的内存,那么uninitialized_fill_n()会调用copy construct,在该范围内的产生x的复制品,
//也就是在该范围内的每个迭代器都会调用construct(&*i,x),在对应位置上产生x的复制品.
当我们以push_back()将新元素插入vector尾端时,该函数首先检查是否还有备用空间,如果有就直接在备用空间上构造元素,并调整迭代器finish,使vector变大,如果没有备用空间了,就扩充空间,扩充空间的过程包括重新配置、移动数据和释放原空间。
注:当没有备用空间时,为了时间成本,容量会扩充至两倍,如果两倍容量仍不足,就扩充至足够大的容量。另外,对vector的任何操作,一旦引起空间重新配置,指向原vector的所有迭代器就失效了。
1 void push_back(const T& x) 2 { 3 if (finish != end_of_storage) //还有备用空间 4 { 5 construct(finish, x); //构造元素 6 ++finish; 7 } 8 else 9 { 10 insert_aux(end(), x); //如果没有备用空间,调用该全局函数 11 } 12 } 13 insert_aux(iterator position, const T& x) 14 { 15 if (finish != end_of_storage) //还有备用空间, 16 { 17 construct(finish, *(finish - 1)); //在备用空间起始处构造一个元素,并以vector最后一个元素值为其初始值 18 ++finish; 19 T x_copy = x; 20 copy_backward(position, finish - 2, finish - 1); 21 *position = x_copy; 22 } 23 else //没有备用空间 24 { 25 const size_type old_size = size(); 26 const size_type len = old_size != 0 ? 2 * old_size : 1; 27 //配置原则:如果原大小为0,则配置一个元素大小空间 28 //如果原大小不为0,则配置为原大小的两倍, 29 //前半段用来放置原数据,后半段准备用来放新数据 30 iterator new_start = data_allocator::allocator(len); //配置大小为len的元素空间 31 iterator new_finish = new_start; 32 try 33 { 34 new_finish = uninitialized_copy(start, position, new_start); //将原vector的内容拷贝到新vector中 35 construct(new_finish, x); //为新元素设定初值x; 36 ++finish; 37 new_finish = uninitialized_copy(position, finish, new_finish);//将安插点的原内容拷贝过来 38 } 39 catch (...) 40 { 41 destory(new_start, new_finish); 42 data_allocator::deallocate(new_start, len); 43 throw; 44 } 45 //析构并释放原vector 46 destroy(begin(), end()); 47 deallocate(); 48 //调整迭代器,指向新的vector 49 start = new_start; 50 finish = new_finish; 51 end_of_storage = new_start + len; 52 } 53 }
3、vector的元素操作
vector所提供的元素很多,见下图
这里只选择几种典型的操作进行介绍。详细参考http://www.cplusplus.com/reference/vector/vector/?kw=vector。
1 iterator erase(iterator first, iterator last) //清除[first,last)中所有的元素 2 { 3 iterator i = copy(last, finish, first); 4 destroy(i, finish); 5 finish = finish - (last - first); 6 return first; 7 } 8 9 iterator erase(iterator position) //清除某个位置上的元素 10 { 11 if (position + 1 != end()) 12 { 13 copy(position + 1, finish, position); //该函数将[position + 1,finish)的元素拷贝到从position开始的位置上 14 } 15 --finish; 16 destroy(finish); 17 return position; 18 }
二、list
1、list简介
相对vector的连续性空间,list则为离散的空间布局,每次插入或删除一个元素,就会配置或释放一个元素空间。
2、list的构造和内存管理
SGI list是一个环状双向链表,其节点结构为:
1 template <class T> 2 struct _list_node 3 { 4 typedef void* void_pointer; 5 void_pointer prve; 6 void_pointer next; 7 T data; 8 };
以下四个函数,分别用来配置、释放、构造、销毁一个节点:
1 link_type get_node() //配置一个节点 2 { 3 return list_node_allocator::allocator(); 4 } 5 void put_node(link_type p) //释放一个节点 6 { 7 list_node_allocator::deallocate(p); 8 } 9 link_type create_node(const T& x) //产生一个节点,带有元素值 10 { 11 link_type p = get_node(); 12 construct(&p->data, x); 13 return p; 14 } 15 void destroy_node(link_type p) //销毁(析构释放)一个节点 16 { 17 destroy(&p->data); 18 put_node(p); 19 }
1 void empty_initialize() //产生一个空链表 2 { 3 node = get_node(); 4 node->next = node; //令头尾都指向自己 5 node->prve = node; 6 }
3、list的元素操作
主要的操作包括:
1 iterator insert(iterator position, const T& x) //在迭代器position所指位置出插入一个节点,内容为x 2 { 3 link_type tmp = create_node(x); 4 tmp->next = position.node; 5 tmp->prve = position.node->prve; 6 (link_type(position.node->prve))->next = tmp; 7 position.node->prve = tmp; 8 return tmp; 9 } 10 11 iterator erase(iterator position) //移除迭代器position所指节点 12 { 13 link_type next_node = link_type(position.node->next); 14 link_type prve_node = link_type(position.node->prve); 15 prve_node->next = next_node; 16 next_node->prve = prve_node; 17 destroy_node(position.node); 18 return iterator(next_node); 19 } 20 21 void remove(const T& value)//将数值为value的元素移除 22 { 23 iterator first = begin(); 24 iterator last = end(); 25 while (first != last) 26 { 27 iterator next = last; 28 ++next; 29 if (*first == value) 30 erase(first); 31 first = next; 32 } 33 }
list还提供一个迁移操作,将某连续范围的元素迁移到某个特定位置之前,这个操作为其他人如splice、sort、merge奠定了基础。
1 void transfer(iterator position, iterator first, iterator last) //将[first,last)中的元素移到position之前 2 { 3 if (position != last) 4 { 5 (*(link_type((*last.node).prve))).next = position.node; 6 (*(link_type((*first.node).prve))).next = position.node; 7 (*(link_type((*position.node).prve))).next = first.node; 8 link_type tmp = link_type((*position.node).prve); 9 (*position.ndoe).prve = (*last.node).prve; 10 (*last.node).prve = (*first.node).prve; 11 (*last.node).prve = tmp; 12 } 13 }
三、deque
1、deque的简介
deque是一种双向开口的连续性空间,可以在头尾两端分别做元素的插入和删除操作。
2、deque的构造和内存管理
deuqe除了维护一个指向map的指针,也维护start、finish两个迭代器,分别指向第一缓冲区的第一个元素和最后一个缓冲区的最后一个元素(的下一个位置)。
1 template<class T,class Alloc=alloc,size_t BufSiz=0> 2 class deque 3 { 4 public : 5 typedef T value_type; 6 typedef value_type* pointer; 7 typedef size_t size_type; 8 typedef _deque_iterator<T, T&, T*, BufSiz> iterator; 9 protected: 10 typedef pointer* map_pointer; 11 iterator start; 12 iterator finish; 13 map_pointer mao; 14 size_type map_size; 15 }
根据上述结构,容易得到下列函数:
1 iterator begin() 2 { 3 return start; 4 } 5 iterator end() 6 { 7 return finish; 8 } 9 size_type size() const 10 { 11 return finish - start; 12 } 13 bool empty() const 14 { 15 return finish == start; 16 } 17 reference back() 18 { 19 iterator tmp = finish; 20 --tmp; 21 return *tmp; 22 } 23 reference operator[](size_type n) //对[]符号的重载 24 { 25 return start[difference_type(n)]; 26 }