STL详解--阅读笔记

本文深入探讨了STL的六大组件:容器、算法、迭代器、仿函数、配接器和配置器。重点介绍了容器中的序列式容器如vector、list、deque、stack、queue和priority_queue的特点和操作,以及关联式容器set、map和hash表的实现机制。文章还讨论了空间配置器allocator的角色以及迭代器在容器和算法间的桥梁作用。
摘要由CSDN通过智能技术生成

1. STL六大组件–功能与运用

  1. 容器(containers):各种数据结构:如vector、list、deque、map、set用来存放数据。
  2. 算法(algorithms):各种常用算法:sort、search、copy、erase、等
  3. 迭代器(iterator):扮演容器和算法之间的胶合剂,是所谓的泛型指针
  4. 仿函数(functor)
  5. 配接器(adapters)
  6. 配置器(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的构造与内存管理
  1. 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();
    } 
  1. 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出来
publiclist() {empty_initialize();}//产生一个空链表
protectedvoid 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的差异
  1. 一在于dequeue允许常数时间内对头端进行元素的插入与删除操作;
  2. 在于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中也是一样的。

6. 算法

7. 仿函数

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值