1. STL六大组件–功能与运用
- 容器(containers):各种数据结构:如vector、list、deque、map、set用来存放数据。
- 算法(algorithms):各种常用算法:sort、search、copy、erase、等
- 迭代器(iterator):扮演容器和算法之间的胶合剂,是所谓的泛型指针
- 仿函数(functor)
- 配接器(adapters)
- 配置器(allocators)
六大组件之间的交互关系:container 通过allocator取得数据存储空间,algorithm通过iterator存取container内容、functor可以协助algorithm完成不同的策略变化,adapter可以修饰或套接functor。
2. 空间配置器 allocator
为什么不说allocator是内存配置器而说它是空间配置器呢?因为空间不一定是内存,空间也可以是磁盘或其他辅助存储介质。
3. 迭代器 Iterators
4. 序列式容器
4.1 vector
vector的数据安排及操作方式与array非常相似,两者唯一的差别在于空间运用的灵活性。array是静态空间,一旦配置了就不能改变;vector是动态空间,随着元素的加入,它的内部机制会自行扩充空间以容纳新元素。
- vector定义摘要
/*
*STL 源码
*vector定义摘要
*/
template <class T, class Alloc = alloc>
class vector{
public:
//vector 的嵌套型别定义
typedef T value_type;
typedef value_type* pointer;
typedef value_type* iterator;
typedef value_type* reference;
typedef size_t size_type;
protected:
iterator start; //表示目前使用空间的头
iterator finish; //表示目前使用空间的尾
iterator end_of_storage; //表示目前可用空间的尾
void fill_initialize(size_type n, const T& value) {
start = alloc_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() : 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() { //自动释放空间
destory(start, finish);
deallocate();
}
reference front() { return *begin(); }//第一个元素
reference back() { return *(end() - 1); }//最后一个元素
void push_back(const T& x) { //向vector末尾压入一个数据
if (finish != 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()); //清零,重置大小为0,空间并未释放
}
- vector的迭代器
vector维护的是一个连续线性空间,所以不论其元素类型为何,普通指针都可以作为vector的迭代器而满足所有必要条件,因为vector迭代器所需要的操作行为,如operator*,operator->,operator++,operator–,operator+,operator-,operator+=,operator-=,普通指针天生就具备,vector支持随机存取,而普通指针正有着这样的能力,所以vector提供的是Random Access Iterators.
根据上述定义,客户端写出这样的代码
vector<int>:: iterator ivite;
vector<shape>:: iterator svite;
ivite 的类型就就是int* ;svite的类型就是shape*
- vector的数据结构
vector所采用的数据结构非常简单:线性连续空间。它以两个迭代器start、finish分别指向配置得来的连续空间中目前已使用的范围,并以end_of_storage指向整块连续空间(含备用空间)的尾端。
template <class T, class Alloc = alloc>
class vector{
...
protected:
iterator start; //表示目前使用空间的头
iterator finish; //表示目前使用空间的尾
iterator end_of_storage; //表示目前可用空间的尾
...
- vector的构造与内存管理
- vector的构造函数
vector提供许多constructor
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() { //自动释放空间
destory(start, finish);
deallocate();
}
- vector的push_back、pop_back
当我们以push_back()将新元素插入于vector尾端时,该函数首先检查是否还有备用空间,如果有就直接在备用空间上构造元素,并调整迭代器finish,使vector变大。如果没有备用空间了,就扩充空间(重新配置、移动数据、释放原空间)
void push_back(const T& x)
{
if(finish != end_of_storage)
{
constructor(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)
{
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;
iterator new_start = data_allocator::allocate(len);
iterator new_finish = new_start;
# ifdef __STL_USE_EXCEPTIONS
try {
# endif /* __STL_USE_EXCEPTIONS */
new_finish = uninitialized_copy(start, position, new_start);
construct(new_finish, x);
++new_finish;
new_finish = uninitialized_copy(position, finish, new_finish);
# ifdef __STL_USE_EXCEPTIONS
}
catch(...) {
destroy(new_start, new_finish);
data_allocator::deallocate(new_start, len);
throw;
}
# endif /* __STL_USE_EXCEPTIONS */
destroy(begin(), end());
deallocate();
start = new_start;
finish = new_finish;
end_of_storage = new_start + len;
}
}
- vector的元素操作:pop_back\erase\clear\insert
//将尾端元素拿掉,并调整大小
void pop_back(){
--finish; //将尾端标记往前移一格,表示放弃尾端元素
destroy(finish);//destroy是全局函数
}
//清除{first,last}中的所有元素
iterator erase(iterator first,iterator last)
{
iterator i = copy(last,finish,first);copy是全局函数
destroy(i,finish);
finish = finish - (last-first);
return first;
}
//清除某个位置上的元素
iterator erase(iterator position)
{
if(position+1 != end())
{
copy(position+1,finish,position);
}
--finish;
destroy(finish);
return position;
}
insert函数
//插入 在值为value的一个元素到position的位置上
void insert(iterator position, const T& value){
insert(position, 1, value);
}
//在position位置之后,插入n的值为value的元素
void insert(iterator position, size_type n, const T& value){
if (n == 0)return;
if ((end_of_storage - finish) >= n){//备用空间够插入n个新元素
T x_copy = value;
const size_type size_from_position_to_end = finish - position;
iterator old_finish = finish;
if (size_from_position_to_end > n){
__copy(finish - n, finish, finish);
finish += n;
__backCopy(position, old_finish - n, old_finish);
__fill(position, position + n, x_copy);
}
else{
__fill_n(finish, n - size_from_position_to_end, x_copy);
finish += n - size_from_position_to_end;
__copy(position, old_finish, finish);
finish += size_from_position_to_end;
__fill(position, old_finish, x_copy);
}
}
else{
//重新申请空间
const size_type old_size = size();
size_type _max = 0;
if (old_size > n) _max = old_size;
else _max = n;
const size_type len = old_size + _max;
iterator new_start = (iterator)malloc(len * sizeof(T));
iterator new_finish = new_start;
//内存的分配要有原子性,即:要么全部成功,要么全部失败。
try{
new_finish = __copy(begin(), position, new_start);//1.将原内容 至position的所有元素(不包含position) 拷贝到新的vector
new_finish = __fill_n(new_finish, n, value);//2.将position位置到后面的n个元素都填充为value
new_finish = __copy(position, end(), new_finish);//3.拷贝从 position位置到end()位置的原vector的所有剩余元素
}
catch (...)//如果失败了
{
destroy(new_start, new_finish);
free(new_start);//删除申请到的内存
new_start = new_finish = NULL;
throw; //抛出异常
}
//析构并释放原vector
destroy(begin(), end());
//删除内存
free(start);
//调整迭代器,指向新的vector
start = new_start;
finish = new_finish;
end_of_storage = new_start + len;
}
}
4.2 list
- list 的节点 (node)
每一个设计过list的人都知道,list本身于list的节点是不同的结构,需要分开设计。以下是list的节点结构
template <class T>
struct _list_node
{
typedef void* void_pointer;
void_pointer prev;
void_pointer next;
T data;
}
显然,这是一个双向链表
- list的迭代器
list不能够再像vector一样以普通指针作为迭代器,因为节点不保证在存储空间连续存在。list迭代器必须有能力指向list节点,并有能力进行正确的递增、递减、取值、成员存取等操作,并且由于STL list是一个环状双向链表,迭代器必须具备前移、后移的能力。所以list提供的迭代器是Bidirectional Iterator.。
template<typename _Tp>
struct _List_iterator
{
typedef _List_iterator<_Tp> _Self;
typedef _List_node<_Tp> _Node;
typedef ptrdiff_t difference_type;
typedef std::bidirectional_iterator_tag iterator_category;
typedef _Tp value_type;
typedef _Tp* pointer;
typedef _Tp& reference;
_List_iterator() _GLIBCXX_NOEXCEPT
: _M_node() { }
explicit
_List_iterator(__detail::_List_node_base* __x) _GLIBCXX_NOEXCEPT
: _M_node(__x) { }
_Self
_M_const_cast() const _GLIBCXX_NOEXCEPT
{ return *this; }
// Must downcast from _List_node_base to _List_node to get to _M_data.
reference
operator*() const _GLIBCXX_NOEXCEPT
{ return static_cast<_Node*>(_M_node)->_M_data; }
pointer
operator->() const _GLIBCXX_NOEXCEPT
{ return std::__addressof(static_cast<_Node*>(_M_node)->_M_data); }
_Self&
operator++() _GLIBCXX_NOEXCEPT
{
_M_node = _M_node->_M_next; //本质是链表节点的next指针操作
return *this;
}
_Self
operator++(int) _GLIBCXX_NOEXCEPT
{
_Self __tmp = *this;
_M_node = _M_node->_M_next;
return __tmp;
}
_Self&
operator--() _GLIBCXX_NOEXCEPT
{
_M_node = _M_node->_M_prev; //本质是链表节点的prev指针操作
return *this;
}
_Self
operator--(int) _GLIBCXX_NOEXCEPT
{
_Self __tmp = *this;
_M_node = _M_node->_M_prev;
return __tmp;
}
bool
operator==(const _Self& __x) const _GLIBCXX_NOEXCEPT
{ return _M_node == __x._M_node; }
bool
operator!=(const _Self& __x) const _GLIBCXX_NOEXCEPT
{ return _M_node != __x._M_node; }
// The only member points to the %list element.
__detail::_List_node_base* _M_node; //维护一个链表节点
};
- list的数据结构
list不仅是一个双向链表,还是一个环状双向链表。所以它只需要一个指针,就可以完整地表现整个链表。
- list的构造(constructor)
list提供了许多的constructors,其中一个是default constructor,允许我们不指定任何参数做出一个空的list出来
public:
list() {empty_initialize();}//产生一个空链表
protected:
void empty_initialize()
{
node = get_node();//配置一个节点控件,令node指向它
node->next = node;//令node的头尾都指向自己,不设元素值
node->prev = node;
}
- list 的元素操作
iterator begin() _T_STD_NOEXCEPT
{
return iterator(pHeadNode->Next);
}
iterator end() _T_STD_NOEXCEPT
{
return iterator(pHeadNode);
}
//在迭代器position所指位置插入元素
iterator insert(iterator position, const T& x)
{
list_node* tmp = new list_node();
tmp->dataEntry = x;
tmp->Next = position.m_smartPtr;
tmp->Prev = position.m_smartPtr->Prev;
position.m_smartPtr->Prev->Next = tmp;
position.m_smartPtr->Prev = tmp;
++_size;
return iterator(tmp);
}
//插入一个节点,作为尾节点
void push_back(const T & value)
{
insert(end(), value);
}
//插入一个节点,作为头节点
void push_front(const T & value)
{
insert(begin(), value);
}
//移除迭代器position所指节点
iterator erase(iterator position)
{
list_node_base* next_node = position.m_smartPtr->Next;
list_node_base* prev_node = position.m_smartPtr->Prev;
prev_node->Next = next_node;
next_node->Prev = prev_node;
delete position.m_smartPtr;
position.m_smartPtr = nullptr;
if(_size > 0)
{
_size--;
}
return iterator(next_node);
}
//移除头节点
void pop_front()
{
erase(begin());
}
//移除尾节点
void pop_back()
{
iterator tmp = end();
erase(--tmp);
}
T & front()
{
return *begin();
}
T & back()
{
return *(--end());
}
unsigned int remove(const T & value)
{
unsigned int count = 0;
iterator itrBegin = begin();
while(itrBegin != end())
{
if(*itrBegin == value)
{
itrBegin = erase(itrBegin);
++count;
}
else
{
++itrBegin;
}
}
return count;
}
void clear()
{
iterator itrBegin = begin();
while(itrBegin != end())
{
list_node* tmp = static_cast<list_node *>(itrBegin.m_smartPtr);
++itrBegin;
if(tmp)
{
delete tmp; // 差点犯了一个错误,delete会对用析构函数,并且释放内存。 需要析构子类还是父类,一定要传入正确类型
}
}
pHeadNode->Next = pHeadNode;
pHeadNode->Prev = pHeadNode;
_size = 0;
}
int size()
{
return _size;
}
4.3 deque
vector 是单向开口的连续线性空间,dequeue则是双向开口的连续线性空间。所谓双向开口,就是可以在头尾两端分别做元素的插入和删除操作。
- dequeue与vector的差异
- 一在于dequeue允许常数时间内对头端进行元素的插入与删除操作;
- 在于deque没有所谓容量(capacity)观念,因为它是动态地以分段连续空间组合而成,随时可以增加一段新地空间并链接起来,换句话说,像vector那样,因旧空间不足而重新配置一块更大的空间,然后复制元素,再释放旧空间,这样的事情在deque中不会发生。
- deque缺点
deque是由一段一段的定量连续空间构成,一旦有必要在deque的前端或尾端增加新空间,便配置一段定量连续空间,串接在整个deque的头端或尾端。deque的最大任务,便是在这些分段的连续空间上,维护其整体连续的假象,并提供随机存取的接口。因此,它的迭代器架构是非常复杂的。deque的实现代码分量远比vector和list多得多。
4.5 statck
stack(栈)是一种先进后出的数据结构,它只有一个出口。stack允许新增元素、移除元素、取得顶端元素,但是除了最顶端外,没有任何办法存取stack的其他元素,换言之,stack不允许有遍历操作。
将元素存入stack的操作为push,将元素推出stack的操作为pop
- stack 的完整定义列表
deque是双向开口的数据结构,若以deque为底部结构并封闭其头端开口,便轻而易举地形成了一个stack。因此,STL以deque为缺省情况下的stack底部结构,stack的实现非常简单。
由于stack是以底部容器(deque)完成所有工作,因此被称为container adapter(配接器),而不归类为container(容器)
public:
//下面对stack的维护完全依赖于底层容器的操作
stack() : c() {}
explicit stack(const _Sequence& __s) : c(__s) {}
//判断容器是否为空
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(); }
};
- stack没有迭代器
stack所有元素的进出都符合“先进后出”的条件,只有stack顶端的元素,才有机会被外界取用,stack不提供走访功能,也不提供迭代器。
4.6 queue
queue是一种先进先出的数据结构,它有两个出口,queue允许新增元素,移除元素,从最低端加入元素,取得顶端元素。但除了最低端可以加入,最顶端可以取出外,没有任何其他方法可以存取queue的其他元素。换言之,queue不允许有遍历行为。
将元素推入queue的操作成为push,将元素推出queue的操作成为pop。
- queue定义完整列表
deque是双向开口的数据结构,若以deque为底部结构并封闭其底端的出口和前端的入口,便轻而易举的形成了queue。因此,STL便以deque作为缺省情况下queue的底部结构,queue的实现因此非常简单。
由于queue是以底部容器(deque)完成所有工作,因此被称为container adapter(配接器),而不归类为container(容器)
#ifndef __STL_LIMITED_DEFAULT_TEMPLATES
template <class T, class Sequence = deque<T> >
#else
template <class T, class Sequence>
#endif
class queue {
// 定义友元函数
friend bool operator== __STL_NULL_TMPL_ARGS (const queue& x, const queue& y);
friend bool operator< __STL_NULL_TMPL_ARGS (const queue& x, const queue& y);
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;
public:
bool empty() const { return c.empty(); }
size_type size() const { return c.size(); }
// 调用deque的front函数
reference front() { return c.front(); }
const_reference front() const { return c.front(); }
reference back() { return c.back(); }
const_reference back() const { return c.back(); }
// 只封装push_back, pop_front函数
void push(const value_type& x) { c.push_back(x); }
void pop() { c.pop_front(); }
};
- queue没有迭代器
- queue的所有元素的进出都必须符合先进先出的条件,因此只有queue顶端的元素,才有机会被外界取用。queue不提供遍历功能,也不提供迭代器。
4.7 heap
4.8 priority_queue
priority_queue又称为优先队列,其底层是用堆来实现的。在优先队列中,队首元素一定是当前队列中优先级最高的一个。
在任何时候,往优先队列中加入(push)元素,而优先队列底层的数据结构堆(heap)会随时调整结构,使得每次的队首元素都是优先级最大的。
优先队列中只能通过top()函数访问队首元素(堆顶元素),也就是优先级最高的元素,在使用之前,必须用empty()函数判断优先队列是否为空。
(1)基本数据类型的优先级设置
int、double、char等可以直接使用的数据类型,优先队列对他们的优先级设置一般是数字大的优先级最高,因此队首元素就是队列中元素最大的那个,char类型则是字典序最大的。
priority_queue<int> q 和 priority_queue<int,vector<int>,greater<int>> q等价
vector<int>填写的来承载底层数据结构堆(heap)的容器
greater<int>表示数字小的优先级越大; less<int>表示数字大的优先级越大
5. 关联式容器
所谓关联式容器:每个元素都有一个键值(key)一个实值(value),当元素被插入到关联式容器中,容器内部结构(可能是RB-Tree,也可能是hash table)便按照其键值大小,以某种特定的规则将元素存放于适当位置。关联式容器没有所谓头尾,只有最大元素和最小元素,所以不会有push_back() push_front() pop_back() pop_front() begin() end()等操作行为。
一般而言,关联式容器的内部结构是一个balanced binary tree(平衡二叉树),以便获得良好的搜寻效率。balanced binary tree有许多类型,包括AVL-tree、BR-tree、AA-tree,其中最被广泛运用的就是BR-TREE(红黑树)
标准的STL关联式容器分为set(集合) 和map(映射)两大类。此外,STL还提供了一个不在标准规格之列的关联式容器:hash table(散列表),以及以此hash table为底层机制完成的hash set(散列集合)、hash map(散列映射表)。
注:set的键值就是实值,map的键值和实值可以分开,形成一种映射关系,故被称为映射表或字典
5.1 tree
红黑树的特性
5.2 set
set的特性是,所有元素都会根据元素的键值被自动排序。set的元素不像map那样可以同时拥有key value,set元素的键值key就是实值value,实值就是键值。set不允许两个元素有相同的键值。
set以BR-tree为底层机制,所有set的操作,都只是调用BR-tree的操作行为而已。
#ifndef SGI_STL_INTERNAL_SET_H
#define SGI_STL_INTERNAL_SET_H
STL_BEGIN_NAMESPACE
#if defined( sgi) && !defined( GNUC__) && (_MIPS_SIM != _MIPS_SIM_ABI32)
#pragma set woff 1174
#endif
#ifndef STL_LIMITED_DEFAULT_TEMPLATES
template <class Key, class Compare = less<Key>, class Alloc = alloc>
#else
template <class Key, class Compare, class Alloc = alloc>
#endif
class set {
public:
// typedefs:
typedef Key key_type;
typedef Key value_type;
// 注意,以下 key_compare 和 value_compare 使用相同的比較函式
typedef Compare key_compare;
typedef Compare value_compare;
private:
/* 注意,identity 定义于 <stl_function.h>,參見第 7 章,其定義為:
template <class T>
struct identity : public unary_function<T, T> {
const T& operator()(const T& x) const { return x; }
};
*/
// 以下,rb_tree<Key, Value, KeyOfValue, Compare, Alloc>
typedef rb_tree<key_type, value_type,
identity<value_type>, key_compare, Alloc> rep_type;
rep_type t; // 採用紅黑樹(RB-tree)來表現 set
public:
typedef typename rep_type::const_pointer pointer; typedef typename rep_type::const_pointer const_pointer; typedef typename rep_type::const_reference reference; typedef typename rep_type::const_reference const_reference; typedef typename rep_type::const_iterator iterator;
// 注意上一行,iterator 定義為 RB-tree 的 const_iterator,這表示 set 的
// 迭代器無法執行寫入動作。這是因為 set 的元素有一定次序安排,
// 不允許使用者在任意處做寫入動作。
typedef typename rep_type::const_iterator const_iterator;
typedef typename rep_type::const_reverse_iterator reverse_iterator; typedef typename rep_type::const_reverse_iterator const_reverse_iterator; typedef typename rep_type::size_type size_type;
typedef typename rep_type::difference_type difference_type;
// allocation/deallocation
// 注意, set 一定使用 insert_unique() 而不使用 insert_equal()。
// multiset 才使用 insert_equal()。
set() : t(Compare()) {}
explicit set(const Compare& comp) : t(comp) {}
#ifdef STL_MEMBER_TEMPLATES template <class InputIterator> set(InputIterator first, InputIterator last)
: t(Compare()) { t.insert_unique(first, last); }
template <class InputIterator>
set(InputIterator first, InputIterator last, const Compare& comp)
: t(comp) { t.insert_unique(first, last); }
#else
set(const value_type* first, const value_type* last)
: t(Compare()) { t.insert_unique(first, last); }
set(const value_type* first, const value_type* last, const Compare& comp)
: t(comp) { t.insert_unique(first, last); }
set(const_iterator first, const_iterator last)
: t(Compare()) { t.insert_unique(first, last); }
set(const_iterator first, const_iterator last, const Compare& comp)
: t(comp) { t.insert_unique(first, last); }
#endif /* STL_MEMBER_TEMPLATES */
set(const set<Key, Compare, Alloc>& x) : t(x.t) {}
set<Key, Compare, Alloc>& operator=(const set<Key, Compare, Alloc>& x) {
t = x.t;
return *this;
}
// 以下所有的 set 操作行為,RB-tree 都已提供,所以 set 只要轉呼叫即可。
// accessors:
key_compare key_comp() const { return t.key_comp(); }
// 以下注意,set 的 value_comp() 事實上為 RB-tree 的 key_comp()。
value_compare value_comp() const { return t.key_comp(); }
iterator begin() const { return t.begin(); } iterator end() const { return t.end(); } reverse_iterator rbegin() const { return t.rbegin(); } reverse_iterator rend() const { return t.rend(); }
bool empty() const { return t.empty(); } size_type size() const { return t.size(); } size_type max_size() const { return t.max_size(); }
void swap(set<Key, Compare, Alloc>& x) { t.swap(x.t); }
// insert/erase
typedef pair<iterator, bool> pair_iterator_bool;
pair<iterator,bool> insert(const value_type& x) {
pair<typename rep_type::iterator, bool> p = t.insert_unique(x);
return pair<iterator, bool>(p.first, p.second);
}
iterator insert(iterator position, const value_type& x) {
typedef typename rep_type::iterator rep_iterator;
return t.insert_unique((rep_iterator&)position, x);
}
#ifdef STL_MEMBER_TEMPLATES
template <class InputIterator>
void insert(InputIterator first, InputIterator last) {
t.insert_unique(first, last);
}
#else
void insert(const_iterator first, const_iterator last) {
t.insert_unique(first, last);
}
void insert(const value_type* first, const value_type* last) {
t.insert_unique(first, last);
}
#endif /* STL_MEMBER_TEMPLATES */
void erase(iterator position) {
typedef typename rep_type::iterator rep_iterator;
t.erase((rep_iterator&)position);
}
size_type erase(const key_type& x) {
return t.erase(x);
}
void erase(iterator first, iterator last) {
typedef typename rep_type::iterator rep_iterator;
t.erase((rep_iterator&)first, (rep_iterator&)last);
}
void clear() { t.clear(); }
// set operations:
iterator find(const key_type& x) const { return t.find(x); } size_type count(const key_type& x) const { return t.count(x); } iterator lower_bound(const key_type& x) const {
return t.lower_bound(x);
}
iterator upper_bound(const key_type& x) const {
return t.upper_bound(x);
}
pair<iterator,iterator> equal_range(const key_type& x) const {
return t.equal_range(x);
}
friend bool operator==
__STL_NULL_TMPL_ARGS (const set&, const set&);
friend bool operator< STL_NULL_TMPL_ARGS (const set&, const set&);
};
template <class Key, class Compare, class Alloc>
inline bool operator==(const set<Key, Compare, Alloc>& x, const set<Key, Compare, Alloc>& y) {
return x.t == y.t;
}
template <class Key, class Compare, class Alloc>
inline bool operator<(const set<Key, Compare, Alloc>& x, const set<Key, Compare, Alloc>& y) {
return x.t < y.t;
}
#ifdef STL_FUNCTION_TMPL_PARTIAL_ORDER
template <class Key, class Compare, class Alloc>
inline void swap(set<Key, Compare, Alloc>& x, set<Key, Compare, Alloc>& y) {
x.swap(y);
}
#endif /* STL_FUNCTION_TMPL_PARTIAL_ORDER */
#if defined( sgi) && !defined( GNUC__) && (_MIPS_SIM != _MIPS_SIM_ABI32)
#pragma reset woff 1174
#endif
STL_END_NAMESPACE
#endif /* SGI_STL_INTERNAL_SET_H */
// Local Variables:
// mode:C++
5.3 map
map 的特性是,所有元素都会根据元素的键值自动排序。map的所有元素都是pair,同时拥有实值(value)和键值(key)。pair的第一元素被视为键值,第二元素被视为实值。map不允许两个元素拥有相同的键值。
下面是<stl_pair.h>中pair的定义
template <class T1,class T2>
struct pair
{
typedef T1 first_type;
typedef T2 second_type;
T1 first; //注意,它是public
T2 second; //注意,它是public
pair() : first(T1()),second(T2()){}
pair(const T1& a,const T2& b):first(a),second(b) {}
}
标准的STL map以BR-tree为底层机制,由于map所开放的各种操作接口,BR-tree都提供了,所以几乎所有map操作行为都只是转调用BR-tree的操作行为而已。
//构造函数
map() : t(Compare()) {}
explicit map(const Compare& comp) : t(comp) {}
//插入操作
pair<iterator,bool> insert(const value_type& x) { return t.insert_unique(x); }
iterator insert(iterator position, const value_type& x) {
return t.insert_unique(position, x);
}
void insert(const value_type* first, const value_type* last) {
t.insert_unique(first, last);
}
void insert(const_iterator first, const_iterator last) {
t.insert_unique(first, last);
//map删除操作
void erase(iterator position) { t.erase(position); }
size_type erase(const key_type& x) { return t.erase(x); }
void erase(iterator first, iterator last) { t.erase(first, last); }
//map查找函数
//查找某个键值对应的节点迭代器
iterator find(const key_type& x) { return t.find(x); }
//查找某个键值的个数
size_type count(const key_type& x) const { return t.count(x); }
//查找不小于,即大于等于键值x的节点迭代器,
iterator lower_bound(const key_type& x) {return t.lower_bound(x); }
//查找大于键值x的节点迭代器
iterator upper_bound(const key_type& x) {return t.upper_bound(x); }
//返回lower_bound和upper_bound迭代器对.
pair<iterator,iterator> equal_range(const key_type& x) {
return t.equal_range(x);
5.4 hashtable
hashtable,即我们在数据结构中所说的散列表,也叫哈希表,在插入、删除、搜寻等操作上具有“常数平均时间”的表现,而且这种表现是以统计为基础,不需仰赖输入元素的随机性
使用hash function将某一元素映射为一个“大小可接受的索引”,通过该索引找到在array中的位置,从而构成一个hashtable;使用hash function可能带来一个问题:即不同的元素经过hash function的作用被映射到相同的位置,这样就产生了碰撞,我们应该采取什么方法来解决碰撞呢?
解决碰撞的方法有许多,我们在这主要分析三种: 1.线性探测 2.二次探测3.开链法
- 线性探测
1.利用hash function计算出某个元素的插入位置
2.若该位置上的空间不可用,则循序往下寻找,直到找到一个可用空间为止 - 二次探测
1.利用hash function计算出某个元素的插入位置H
2.若该位置实际上已被使用,则依序尝试H + 1 ^2、H + 2 ^2…H + i ^2,直到发现新空间
分析二次探测:二次探测是为了消除主集团,但却可能造成次集团:两个元素经hash function计算出来的位置若相同,则插入时所探测的位置也相同,形成某种浪费 - 开链法:
1.在每一个表格元素种维护一个list
2.hash function为我们分配某一个list,然后在list上执行元素的插入、搜寻、删除操作
SGI STL的hashtable正是采用这种做法实现的
下图是以开链法完成的hash table的图形描述。
hashtable表格内的元素为桶(bucket),此名称的意思是,表格内每个单元,涵盖的不只是节点(元素),可能是一 ‘桶’ 节点。
hashtable节点的定义
template <class Value>
struct _hashtable_node
{
_hashtable_node* next;
Value val;
};
注:hashtable的bucket所维护的linked list,并不采用STL的list,而是自行维护上述hashtable node。至于hashtable聚合体,由vector完成,以便有扩充能力。
- hashtable的迭代器
hashtable的迭代器必须永远维系着与整个“bucket vector"的关系,并记录当前所指系欸但。其前进操作是首先尝试从目前所指的节点触发,前进一个位置(节点),由于节点被安置在List内,所以利用节点的next指针可以轻易达成前进操作。如果目前节点正好是list的尾端,就跳至下一个bucket身上,那正是指向下一个list的头部节点。
template <class Value, class Key, class HashFcn,
class ExtractKey, class EqualKey, class Alloc>
struct __hashtable_iterator {
//...
typedef __hashtable_node <Value> node;
typedef forward_iterator_tag iterator_category; //hashtable没有后退操作
//...
node* cur; //迭代器所指节点
hashtable* ht; //连接容器
//构造函数
__hashtable_iterator(node* n, hashtable* tab) : cur(n), ht(tab) {}
__hashtable_iterator() {}
//取值
reference operator*() const { return cur->val; }
pointer operator->() const { return &(operator*()); }
//迭代器递增操作
iterator& operator++();
iterator operator++(int);
//判断迭代器是否相等
bool operator==(const iterator& it) const { return cur == it.cur; }
bool operator!=(const iterator& it) const { return cur!= it.cur; }
};
//递增操作实现
template <class V, class K, class HF, class ExK, class EqK, class A>
__hashtable_iterator<V, K, HF, ExK, EqK, A>&
__hashtable_iterator<V, K, HF, ExK, EqK, A>::operator++()
{
const node* old = cur;
cur = cur->next; //如果存在节点则就是该cur,不存在节点则进入if流程,找到下一个bucket
if (!cur) {
size_type bucket = ht->bkt_num(old->val); //找到当前值所对应的桶
while (!cur && ++ bucket < ht->buckets.size())
cur = ht->buckets[bucket]; //令cur指向下一个bucket
}
return *this;
}
template <class V, class K, class HF, class ExK, class EqK, class A>
__hashtable_iterator<V, K, HF, ExK, EqK, A>&
__hashtable_iterator<V, K, HF, ExK, EqK, A>::operator++(int)
{
iterator tmp = *this;
++*this;
return tmp;
{
- hashtable的数据结构
bucket的聚合体由vector完成,以利于动态扩充; - hashtable的构造与内存管理
构造hanshtable
hashtable(size_type n, const HashFcn& hf, const EqualKey& eql)
: hash(hf), equals(eql), get_key(ExtractKey()), num_elements(0)
{
initialize_buckets(n);
}
void initialize_buckets(size_type n)
{
buckets.reserve(n_bckets);
buckets.insert(buckets.end(), n_buckets, (node*) 0);
num_elements = 0;
}
元素操作
操作 | 功能 |
---|---|
copy_from | 复制整个hashtable |
clear | 整体删除 |
find | 搜寻键值为key的元素 |
count | 计算键值为key的元素个数 |
5.7 hash_set
与set不同的是,set以BR-tree为底层机制而hashset以hashtable为底层机制,hashset的所有操作行为都只是转调用hashtable的操作行为而已。
运用set,为的就是能偶快速搜寻元素。这一点,不论其底层是BR-TREE还是hashtable,都可以达成任务。但请注意,BR-TREE有自动排序功能而hashtable没有,反映出来的结果就是,set的元素能自动排序,而hashset不能。
set的元素不像map那样可以同时拥有key value,set元素键值就是实值,实值就是键值,这一点在hashset中也是一样的。
5.8 hashmap
与map不同的是,map以BR-tree为底层机制而hashmap以hashtable为底层机制,hashmap的所有操作行为都只是转调用hashtable的操作行为而已。
运用map,为的就是能根据键值快速搜寻元素。这一点,不论其底层是BR-TREE还是hashtable,都可以达成任务。但请注意,BR-TREE有自动排序功能而hashtable没有,反映出来的结果就是,map的元素能自动排序,而hashmap不能。
map的特性是:每一个元素可以同时拥有key value,这一点在hashmap中也是一样的。