目录
unordered_set、 unordered_map(HashTb)
序列式容器
list
list是一个双向链表,普通指针已经不能满足list迭代器的需求,因为list的空间是不连续的,所以list的迭代器需要具备前移和后退的功能,所以list迭代器的类型是Bidirectionallterator。
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;
...
}
list(双向链表),在GNU2.9源码中,list类中维护了node这个属性,这个属性就是一个包含两个指针prev、next和当前data的结构体。基于容器都要满足前闭后开的原则,在list的头部会添加一个灰部地带,begin()获得灰部地带下一个节点,end()获得灰部地带:

vector
// vector源码
template<class T,class Alloc=alloc>
class vector
{
public:
typedef T value_type;
typedef value_type* iterator;
typedef value_type& reference;
typedef size_t size_type;
protected:
iterator start;
iterator finish;
iterator end_of_storage;
public:
iterator begin(){return start;}
iterator end(){return finish;}
size_type size()const{return (size_type)end_of_storage-begin();}
bool empty()const{return begin()==end();}
reference operator[](size_type n){
return *(begin()+n);
}
reference front(){return *begin();}
reference back(){return *(end()-1);}
...
};
vector容器的迭代器start指向第一个元素,finish指向最后一个元素的下一个元素,满足了前闭后开特性,end_of_storage是vector的容量。vector在使用上是连续的,在实现上也是连续的,所以迭代器采用non-class类型。

vector.push_back()的方法,首先会判断end_of_storage是不是满了,满了的话就会扩容2倍再进行添加元素。注意:扩充的过程重并不是在原有空间后面追加容量,而是重新申请一块连续的内存空间,将原有的数据拷贝到新空间中,再释放原来空间中内存。所以扩充之后,原有的迭代器将会失效。
void push_back(const T& x){
if(finish!=end_of_storage){
construct(finish,x);
++finish;
}else
insert_aux(end(),x);
}
insert_aux是vector容器用来在内部任意位置插入元素,内存不足的情况下会扩容:
template<class T, class Alloc>
void vector<T, Alloc>::insert_ux(iterator position, const T &x) {
if (finish != end_of_storage) { // 尚有备用空间,则将插入点后元素后移一位并插入元素
construct(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; // 扩容后长度为原长度的两倍
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;
}
// 释放原容器所占内存
destroy(begin(), end());
deallocate();
// 调整迭代器
start = new_start;
finish = new_finish;
end_of_storage = new_start + len;
}
};
deque
deque是一种双向开口的连续线性空间。deque支持从头尾两端进行元素的插入和删除。

template<class T,class Alloc=alloc,size_t BufSiz=0>
class deque
{
public:
typedef T value_type;
typedef __deque_iterator<T,T&,T*,BufSiz> iterator;
protected:
typedef pointer* map_pointer; //T**
iterator start;
iterator finish;
map_pointer map;
size_type map_size;
public:
iterator begin(){return start;}
iterator end(){return end;}
size_type size(){return finish-start;}
...
};
deque没有容量的概念,因为它是动态地以分段连续空间组合而成 ,随时可以增加一段新的空间并连接起来。为了维护这种整体连续的假象,代价就是迭代器的类型会比较复杂。
即采用map(类型为T**)作为主控。map实际上是一块大小连续的空间,其中每一个元素称之为node,每个node都指向了另一端连续线性的空间(buffer),buffer才是deque真正存储空间的地方。

deque的iterator有四个部分,cur指向buffer当前元素,first指向buffer的头,last指向尾,node指向map(主控中心):

template<class T,class Ref,class Ptr,size_t BufSiz>
struct __deque_iterator
{
typedef random_access_iterator_tag iterator_category; //1
typedef T value_type; //2
typedef Ptr pointer; //3
typedef Ref reference; //4
typedef size_t size_type;
typedef ptrdiff_t difference_type; //5
typedef T** map_pointer;
typedef __deque_iterator self;
T* cur;
T* first;
T* last;
map_pointer node;
...
};
deque<T>::insert()插入函数首先判断传入迭代器的位置是处于容器前半部分还是后半部分,再插入进比较短的那一段:
iterator inset(iterator position,const value_type& x)
{
if(position.cur==start.cur){
push_front(x);
return start;
}else if(position.cur==finish.cur){
push_back(x);
return finish
}else{
return insert_aux(position,x);
}
}
若插入位置是容器首部,则直接push_front,位置是容器尾部,则直接push_back。其他情况则调用insert_aux方法:
template<class T,class Alloc,size_t BufSize>
typename deque<T,Alloc,BufSize>::iterator
deque<T,Alloc,BufSize>::insert_aux(iterator pos,const value_type& x){
difference_type index = pos-start; //安插点之前的元素个数
value_type x_copy = x;
if(index<size()/2){ //如果安插点之前的元素个数较少
push_front(front()); //在最前端加入和第一元素同值的元素
...
copy(front2,pos1,front1); //元素搬移
}else{ //安插点之后较少
push_back(back()); //在尾端加入和最末元素同值的元素
...
copy_backward(pos,back2,back2); //元素搬移
}
*pos = x_copy;
return pos;
}
queue、stack
queue、stack都以deque作为适配器(adapter),其内部均默认封装了一个deque作为底层容器,上层容器的API大部分通过操作deque的API进行实现。queue是先进先出,stack是先进后出,进出都有严格要求,所以两个容器不允许遍历,所以它们没有迭代器。
queue:

template<class T, class Sequence=deque<T>>
class 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_reference;
protected:
Sequence c; // 底层容器,默认是deque<T>
public:
bool empty() const { return c.empty(); }
size_type size() const { return c.size(); }
reference front() { return c.front(); }
const_reference front() const { return c.front(); }
reference back() { return c.back(); }
const_reference back() const { return c.back(); }
void push(const value_type &x) { c.push_back(x); }
void pop() { c.pop_front(); }
// ...
};
stack:

template<class T, class Sequence=deque<T> >
class 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_reference;
protected:
Sequence c;// 底层容器,默认是deque<T>
public:
bool empty() const { return c.empty(); }
size_type size() const { return c.size(); }
reference top() { return c.back(); }
const_reference top() const { return c.back(); }
void push(const value_type &x) { c.push_back(x); }
void pop() { c.pop_back(); }
// ...
};
关联式容器
set、multiset(RBtree)
set/multiset以rb_tree为底层结构,因此有元素自动排序的功能,排序的依据是key,而set/multiset元素的value和key合一(Value就是Key)。
注意:我们无法使用set/multiset的iterator改变元素值(因为key有其严谨的排列规则),从set的源码可以看出,set的大部分核心操作其实都扔给了rbtree,set这里可以看成一个container adapter。
template<class Key,
class Compare = less<Key>,
class Alloc = alloc>
class set {
public:
typedef Key key_type;
typedef Key value_type;
typedef Compare key_compare;
typedef Compare value_compare;
private:
typedef rb_tree <key_type,
value_type,
identity<value_type>,
key_compare,
Alloc> rep_type;
rep_type t; // 内部rb_tree容器
public:
typedef typename rep_type::const_iterator iterator;
};
set/multiset的iterator是其底层rbtree的const-iterator。set的insert()用的是rb_tree的inset_unique()。-
multiset的insert()用的是rb_tree的inset_equal()。
map、mutimap(RBtree)
Map/multimap以rb_tree为底层结构,因此有元素自动排序的功能,排序的依据是key。
注意:我们无法使用map/multimap的iterator改变元素值(因为key有其严谨的排列规则),但可以用它来该改变元素的data。map/multimap内部自动将user指定的keytype设定为const,以便禁止user对元素的key赋值。
template<class key,Class T,class Compare=less<key>,class Alloc=alloc>
class map{
public:
typedef key key_type;
typedef T data_type;
typedef T mapped_type;
typedef pair<const key,T> value_type // 这里指定const key防止user修改key
typedef Compare key_compare;
private:
typedef rb_tree<key_type,value_type,select1st<value_type>,key_compare,Alloc> rep_type
rep_type t; //rb_tree
public:
typedef typename rep_type::iterator iterator;
}
template<class Pair>
struct select1st:public unary_function<Pair,typename Pair::first_type>
{
const typename Pair::first_type& operator()(const Pair& x){
return x.first;
}
}
- map的insert()用的是rb_tree的inset_unique()。
- multimap的insert()用的是rb_tree 的inset_equal()。
map中特别的符号重载operator[],即下标操作符:operator[](const key_type& _k),如果k存在,返回该iterator,不存在就在合适的位置创造该k ,注意multimap中无法使用operator[]:
mapped_type& operator[](const key_type& _k){
iterator _i = lower_bound(_k);
if(_i=end()||key_comp()(_k,(*_i).first))
_i = insert(_i,value_type(_k,mapped_type()));
return(*_i).second;
}
unordered_set、 unordered_map(HashTb)
C++11引入的容器unordered_set、unordered_multiset、unordered_map和unordered_multimap更名自GCC2.9的下 容器hash_set、hash_multiset、hash_map和hash_multimap,其底层封装了hashtable.
用法与set、multiset、map和multimap类似。
文章详细介绍了C++标准库中的几种主要容器,包括序列式容器如list、vector和deque,以及关联式容器如set、map和unordered_map。序列式容器中,list是双向链表,vector是动态数组,deque是双向开口的连续线性空间。关联式容器基于红黑树或哈希表,提供了自动排序功能。此外,还提到了queue和stack作为容器适配器的使用。


被折叠的 条评论
为什么被折叠?



