STL学习——Hashtable篇

STL学习——Hashtable篇

  • 概述

    hash table(散列表)是一种在插入,删除,搜索等操作“常数平均时间”完成的数据结构。它是一种字典结构。哈希函数是一种映射函数,它能够将大数映射为小数。负责将某一元素映射为一个“大小可接受之索引”。哈希函数使用过程中可能会出现不同的元素被映射到相同的位置(相同的索引),称为哈希碰撞。解决哈希碰撞的方法:线性探测,二次探测,开链等等。

    负载系统:指元素个数除以表格大小。负载系数永远在0-1之间——除非采用开链的策略。哈希过程中,如果平均插入成本的成长幅度,远高于负载系数的成长长度则为主集团现象。线性探测解决哈希冲突过程中可能会出现主集团现象。

    二叉探测主要用来解决主集团问题。如果表格大小为质数,且永远保持负载系数在0.5以下,就可以确定每插入一个新元素所需要的探测次数不多于2。线性探测的做法是一个加法(加1)、一个测试(看是否需要绕转回头),以及一个偶需为之的减法(用以绕转回头)。二次探测需要的是一个加法(从i-1到i),一个乘法(i^2),另一个加法,以及一个mod运算。计算复杂度大,可以利用前一个的H值减后一个H值,然后做移位即乘2和减法操作即可。

    二次探测可以消除主集团,却可能造成次集团:两个元素经hash function计算出来的位置若相同,则插入时所需探测的位置也相同,形成某种浪费。消除次集团方法可以用复式散列。

    开链法消除哈希碰撞,原理是在每一个表格元素中维护一个list:哈希函数为某一个list,然后在list身上执行插入,搜寻,删除等操作。使用开开链法,表格的负载系数将大于1。

  • hashtable的桶子与节点

    开链法实现的哈希表,其表格内的每个单元,涵盖的不只是节点(元素),甚至是一个桶节点。下面是hashtable节点的定义。

    template <class _Val>
    struct _Hashtable_node
    {
      _Hashtable_node* _M_next;
      _Val _M_val;
    }; 
    
  • hashtable迭代器

    template <class _Val, class _Key, class _HashFcn,
              class _ExtractKey, class _EqualKey, class _Alloc>
    struct _Hashtable_iterator;
    
    template <class _Val, class _Key, class _HashFcn,
              class _ExtractKey, class _EqualKey, class _Alloc>
    struct _Hashtable_const_iterator;
    
    template <class _Val, class _Key, class _HashFcn,
              class _ExtractKey, class _EqualKey, class _Alloc>
    struct _Hashtable_iterator {
      typedef hashtable<_Val,_Key,_HashFcn,_ExtractKey,_EqualKey,_Alloc>
              _Hashtable;
      typedef _Hashtable_iterator<_Val, _Key, _HashFcn, 
                                  _ExtractKey, _EqualKey, _Alloc>
              iterator;
      typedef _Hashtable_const_iterator<_Val, _Key, _HashFcn, 
                                        _ExtractKey, _EqualKey, _Alloc>
              const_iterator;
      typedef _Hashtable_node<_Val> _Node;
    
      typedef forward_iterator_tag iterator_category;
      typedef _Val value_type;
      typedef ptrdiff_t difference_type;
      typedef size_t size_type;
      typedef _Val& reference;
      typedef _Val* pointer;
    
      _Node* _M_cur;          // 迭代器目前所指之节点
      _Hashtable* _M_ht;      // 保持对容器的连结关系(因为可能需要从bucket跳到bucket)
    
      _Hashtable_iterator(_Node* __n, _Hashtable* __tab) 
        : _M_cur(__n), _M_ht(__tab) {}
      _Hashtable_iterator() {}
      reference operator*() const { return _M_cur->_M_val; }
    #ifndef __SGI_STL_NO_ARROW_OPERATOR
      pointer operator->() const { return &(operator*()); }
    #endif /* __SGI_STL_NO_ARROW_OPERATOR */
      iterator& operator++();
      iterator operator++(int);
      bool operator==(const iterator& __it) const
        { return _M_cur == __it._M_cur; }
      bool operator!=(const iterator& __it) const
        { return _M_cur != __it._M_cur; }
    };
    
    // 注意,hashtable迭代器必须永远维系着与整个“buckets vector”的关系,并记录目前所指的节点。其前进操作是首先
    // 尝试从目前所指的节点出发,前进一个位置(节点),由于节点被安置于list内,所以利用节点的next指针即可轻易达
    // 成前进操作。如果目前节点正巧是list的尾端,就跳至下一个bucket身上,那正是指向下一个list的头部节点。
    template <class _Val, class _Key, class _HF, class _ExK, class _EqK, 
              class _All>
    _Hashtable_iterator<_Val,_Key,_HF,_ExK,_EqK,_All>&
    _Hashtable_iterator<_Val,_Key,_HF,_ExK,_EqK,_All>::operator++()
    {
      const _Node* __old = _M_cur;     
      _M_cur = _M_cur->_M_next;        // 如果存在,就是他。否则进入以下if流程
      if (!_M_cur) {
        // 根据元素值,定位出下一个bucket。其起头出就是我们的目的地
        size_type __bucket = _M_ht->_M_bkt_num(__old->_M_val);
        while (!_M_cur && ++__bucket < _M_ht->_M_buckets.size())   // 注意,operator++
          _M_cur = _M_ht->_M_buckets[__bucket];
      }
      return *this;
    }
    
    template <class _Val, class _Key, class _HF, class _ExK, class _EqK, 
              class _All>
    inline _Hashtable_iterator<_Val,_Key,_HF,_ExK,_EqK,_All>
    _Hashtable_iterator<_Val,_Key,_HF,_ExK,_EqK,_All>::operator++(int)
    {
      iterator __tmp = *this;
      ++*this;                         // 调用operator++()
      return __tmp;
    }
    // hashtable的迭代器没有后退操作(operator--()),hashtable也没有定义所谓的逆向迭代器(reverse iterator)。
    
  • hashtable数据结构

    // hashtable的模板参数相当多,包括:
    // 1)Value:节点的实值型别;
    // 2)Key:节点的键值型别;
    // 3)HashFcn:hash function的函数型别;
    // 4)ExtractKey:从节点中取出键值的方法(函数或仿函数);
    // 5)EqualKey:判断键值相同与否的方法(函数或仿函数);
    // 6)Alloc:空间配置器,缺省使用std::alloc。
    template <class _Val, class _Key, class _HashFcn,
              class _ExtractKey, class _EqualKey, class _Alloc>   // 先前声明时,已给予Alloc默认值alloc
    class hashtable {
    public:
      typedef _Key key_type;
      typedef _Val value_type;
      typedef _HashFcn hasher;                                    // 为template型别参数重定义一个名称
      typedef _EqualKey key_equal;                                // 为template型别参数重定义一个名称
    
      typedef size_t            size_type;
      typedef ptrdiff_t         difference_type;
      typedef value_type*       pointer;
      typedef const value_type* const_pointer;
      typedef value_type&       reference;
      typedef const value_type& const_reference;
    
      hasher hash_funct() const { return _M_hash; }
      key_equal key_eq() const { return _M_equals; }
    
    private:
      typedef _Hashtable_node<_Val> _Node;
    ...
    private:
      // 以下三者都是function objects。<stl_hash_fun.h>中定义有数个标准型别(如int,c-style,string等)的hasher
      hasher                _M_hash;
      key_equal             _M_equals;
      _ExtractKey           _M_get_key;
      vector<_Node*,_Alloc> _M_buckets;            // 以vector完成
      size_type             _M_num_elements;
    ...
    
    // 注意:假设long之后又32位。
    // 虽然开链法并不要求表格大小必须为指数,但SGI STL仍然以质数来设计表格大小,并且先将28个质数(逐渐呈现大约两倍
    // 的关系)计算好,以备随时访问,同时提供一个函数,用来查询在这28个质数之中,“最接近某个数并大于某个数”的质数。
    enum { __stl_num_primes = 28 };
    
    static const unsigned long __stl_prime_list[__stl_num_primes] =
    {
      53ul,         97ul,         193ul,       389ul,       769ul,
      1543ul,       3079ul,       6151ul,      12289ul,     24593ul,
      49157ul,      98317ul,      196613ul,    393241ul,    786433ul,
      1572869ul,    3145739ul,    6291469ul,   12582917ul,  25165843ul,
      50331653ul,   100663319ul,  201326611ul, 402653189ul, 805306457ul, 
      1610612741ul, 3221225473ul, 4294967291ul
    };
    // 以下找出上述28个质数之中,最接近大于或等于n的那个质数
    inline unsigned long __stl_next_prime(unsigned long __n)
    {
      const unsigned long* __first = __stl_prime_list;
      const unsigned long* __last = __stl_prime_list + (int)__stl_num_primes;
      const unsigned long* pos = lower_bound(__first, __last, __n);
      // 以上,lower_bound()是泛型算法,使用lower_bound(),序列需先排序。没问题,上述数组已排序。
      return pos == __last ? *(__last - 1) : *pos;
    }
     size_type bucket_count() const { return _M_buckets.size(); }   // bucket个数即buckets vector的大小
    
      size_type max_bucket_count() const
        { return __stl_prime_list[(int)__stl_num_primes - 1]; }      // 总共可以有多少个buckets,其值为4294967291-1 
    
  • hashtable的构造与内存管理

    // 内存构造
    private:
      typedef _Hashtable_node<_Val> _Node;
    
    #ifdef __STL_USE_STD_ALLOCATORS
    public:
      typedef typename _Alloc_traits<_Val,_Alloc>::allocator_type allocator_type;
      allocator_type get_allocator() const { return _M_node_allocator; }
    private:
      typename _Alloc_traits<_Node, _Alloc>::allocator_type _M_node_allocator;
      _Node* _M_get_node() { return _M_node_allocator.allocate(1); }
      void _M_put_node(_Node* __p) { _M_node_allocator.deallocate(__p, 1); }
    # define __HASH_ALLOC_INIT(__a) _M_node_allocator(__a), 
    #else /* __STL_USE_STD_ALLOCATORS */
    public:
      typedef _Alloc allocator_type;
      allocator_type get_allocator() const { return allocator_type(); }
    private:
      typedef simple_alloc<_Node, _Alloc> _M_node_allocator_type;
      _Node* _M_get_node() { return _M_node_allocator_type::allocate(1); }
      void _M_put_node(_Node* __p) { _M_node_allocator_type::deallocate(__p, 1); }
    # define __HASH_ALLOC_INIT(__a)
    #endif /* __STL_USE_STD_ALLOCATORS */
    // 构造函数
    hashtable(size_type __n,
                const _HashFcn&    __hf,
                const _EqualKey&   __eql,
                const _ExtractKey& __ext,
                const allocator_type& __a = allocator_type())
        : __HASH_ALLOC_INIT(__a)
          _M_hash(__hf),
          _M_equals(__eql),
          _M_get_key(__ext),
          _M_buckets(__a),
          _M_num_elements(0)
      {
        _M_initialize_buckets(__n);
      }
    
      hashtable(size_type __n,
                const _HashFcn&    __hf,
                const _EqualKey&   __eql,
                const allocator_type& __a = allocator_type())
        : __HASH_ALLOC_INIT(__a)
          _M_hash(__hf),
          _M_equals(__eql),
          _M_get_key(_ExtractKey()),
          _M_buckets(__a),
          _M_num_elements(0)
      {
        _M_initialize_buckets(__n);
      }
    
      hashtable(const hashtable& __ht)
        : __HASH_ALLOC_INIT(__ht.get_allocator())
          _M_hash(__ht._M_hash),
          _M_equals(__ht._M_equals),
          _M_get_key(__ht._M_get_key),
          _M_buckets(__ht.get_allocator()),
          _M_num_elements(0)
      {
        _M_copy_from(__ht);
      }
    
      size_type _M_next_size(size_type __n) const          // 返回最接近n并大于或等于n的质数
        { return __stl_next_prime(__n); }     
    
      void _M_initialize_buckets(size_type __n)
      {
        const size_type __n_buckets = _M_next_size(__n);   // 举例:传入50,返回53。以下首先保留53个元素空间,然后将其全部填0
        _M_buckets.reserve(__n_buckets);
        _M_buckets.insert(_M_buckets.end(), __n_buckets, (_Node*) 0);
        _M_buckets.insert(_M_buckets.end(), __n_buckets, (_Node*) 0);
        _M_num_elements = 0;
      }
    
    // 插入元素,不允许重复
      pair<iterator, bool> insert_unique(const value_type& __obj)
      {
        resize(_M_num_elements + 1);           // 判断是否需要重建表格,如需要就扩充
        return insert_unique_noresize(__obj);
      }
      // 插入元素,允许重复
      iterator insert_equal(const value_type& __obj)
      {
        resize(_M_num_elements + 1);           // 判断是否需要重建表格,如需要就扩充
        return insert_equal_noresize(__obj);
      }
    
    // 以下函数判断是否需要重建表格。如果不需要,立刻回返。如果需要,就动手...
    template <class _Val, class _Key, class _HF, class _Ex, class _Eq, class _All>
    void hashtable<_Val,_Key,_HF,_Ex,_Eq,_All>
      ::resize(size_type __num_elements_hint)
    {
      // 表格重建与否原则:是拿元素个数(把新增元素计入后)和bucket vector的大小来比。如果前者大于后者,就重建表格
      // 由此可判知,每个bucket(list)的最大容量和buckets vector的大小相同
      const size_type __old_n = _M_buckets.size();
      if (__num_elements_hint > __old_n) {                                   // 确定真的需要重新配置
        const size_type __n = _M_next_size(__num_elements_hint);             // 找出下一个质数
        if (__n > __old_n) {
          vector<_Node*, _All> __tmp(__n, (_Node*)(0),                       // 设立新的buckets
                                     _M_buckets.get_allocator());
          __STL_TRY {
            // 以下处理每一个旧的bucket
            for (size_type __bucket = 0; __bucket < __old_n; ++__bucket) {
              _Node* __first = _M_buckets[__bucket];                         // 指向节点所对应串行的起始节点
              // 以下处理每一个旧bucket所含(串行)的每一个节点
              while (__first) {                                              // 串行还没结束时
                size_type __new_bucket = _M_bkt_num(__first->_M_val, __n);   // 找出节点落在哪一个新bucket内
                _M_buckets[__bucket] = __first->_M_next;                     // 令旧的bucket指向其所对应之串行的下一个节点(以便迭代处理)
                __first->_M_next = __tmp[__new_bucket];                      // 将当前节点插入到新bucket内,成为其对应串行的第一个节点
                __tmp[__new_bucket] = __first;
                __first = _M_buckets[__bucket];                              // 回到旧bucket所指的待处理串行,准备处理下一个节点         
              }
            }
            _M_buckets.swap(__tmp);         // vector::swap。新旧两个buckets对调,注意,对调两方如果大小相同,大的会变小,小的会变大,离开时释放local tmp的内存
          }
    #         ifdef __STL_USE_EXCEPTIONS
          catch(...) {
            for (size_type __bucket = 0; __bucket < __tmp.size(); ++__bucket) {
              while (__tmp[__bucket]) {
                _Node* __next = __tmp[__bucket]->_M_next;
                _M_delete_node(__tmp[__bucket]);
                __tmp[__bucket] = __next;
              }
            }
            throw;
          }
    #         endif /* __STL_USE_EXCEPTIONS */
        }
      }
    }
    
    // 在不需要重建表格的情况下插入新节点。键值不允许重复
    template <class _Val, class _Key, class _HF, class _Ex, class _Eq, class _All>
    pair<typename hashtable<_Val,_Key,_HF,_Ex,_Eq,_All>::iterator, bool> 
    hashtable<_Val,_Key,_HF,_Ex,_Eq,_All>
      ::insert_unique_noresize(const value_type& __obj)
    {
        const size_type __n = _M_bkt_num(__obj);                        // 决定obj应位于#n bucket
      _Node* __first = _M_buckets[__n];                                 // 令first指向bucket对应之串行头部
      // 如果bucket[n]已被占用,此时first将不为0,于是进入以下循环,走过bucket所对应的整个链表
      for (_Node* __cur = __first; __cur; __cur = __cur->_M_next) 
        if (_M_equals(_M_get_key(__cur->_M_val), _M_get_key(__obj)))    // 如果发现与链表中的某键值相同,就不插入,立刻返回
          return pair<iterator, bool>(iterator(__cur, this), false);
      // 离开以上循环(或根本未进入循环)时,first指向bucket所指链表的头部节点
      _Node* __tmp = _M_new_node(__obj);                                // 产生新节点
      __tmp->_M_next = __first;                                         
      _M_buckets[__n] = __tmp;                                          // 令新节点成为链表的第一个节点
      ++_M_num_elements;                                                // 节点个数累加1
      return pair<iterator, bool>(iterator(__tmp, this), true);
    }
    // 在不需要重建表格的情况下插入新节点。键值允许重复
    template <class _Val, class _Key, class _HF, class _Ex, class _Eq, class _All>
    typename hashtable<_Val,_Key,_HF,_Ex,_Eq,_All>::iterator 
    hashtable<_Val,_Key,_HF,_Ex,_Eq,_All>
      ::insert_equal_noresize(const value_type& __obj)
    {
        const size_type __n = _M_bkt_num(__obj);                         // 决定obj应位于#n bucket
      _Node* __first = _M_buckets[__n];                                  // 令first指向bucket对应链表头部 
      // 如果bucket[n]已被占用,此时first将不为0,于是进入以下循环,走过bucket所对应的整个链表
      for (_Node* __cur = __first; __cur; __cur = __cur->_M_next) 
        if (_M_equals(_M_get_key(__cur->_M_val), _M_get_key(__obj))) {   // 如果发现与链表中的某键值相同,就马上插入,然后返回
          _Node* __tmp = _M_new_node(__obj);                             // 产生新节点
          __tmp->_M_next = __cur->_M_next;                               // 将新节点插入于目前为止之后
          __cur->_M_next = __tmp;                                        
          ++_M_num_elements;                                             // 节点个数累加1
          return iterator(__tmp, this);                                  // 返回一个迭代器,指向新增节点
        }
      // 进行至此,表示没有发现重复的键值
      _Node* __tmp = _M_new_node(__obj);                                 // 产生新节点
      __tmp->_M_next = __first;                                          // 将新节点插入于链表头部
      _M_buckets[__n] = __tmp;                                           
      ++_M_num_elements;                                                 // 节点个数累加1
      return iterator(__tmp, this);                                      // 返回一个迭代器,指向新增节点
    }
    
     // 判知元素的落脚处(bkt_num)
      // 程序代码中许多地方都需要知道某个元素值落脚于哪一个bucket之内。此为hash函数的责任,SGI把这个任务包装了一层,先交给bkt_num()
      // 再由此函数调用hash function,取得一个可执行modulus(取模)运算的数值。这么做原因:有些元素类型无法直接拿来对hashtable的大
      // 小进行模运算,如字符串const char *,此时需要做一些转换。
      // 只接受键值
      size_type _M_bkt_num_key(const key_type& __key) const
      {
        return _M_bkt_num_key(__key, _M_buckets.size());
      }
    
      // 只接受实值(value)
      size_type _M_bkt_num(const value_type& __obj) const
      {
        return _M_bkt_num_key(_M_get_key(__obj));
      }
    
      // 接受键值和buckets个数
      size_type _M_bkt_num_key(const key_type& __key, size_t __n) const
      {
        return _M_hash(__key) % __n;
      }
    
      // 接受实值(value)和buckets个数
      size_type _M_bkt_num(const value_type& __obj, size_t __n) const
      {
        return _M_bkt_num_key(_M_get_key(__obj), __n);
      }
    
    // 整体删除(clear)由于整个hash table由vector和link-list组合而成,故复制和整体删除,都需特征注意内存的释放问题
    template <class _Val, class _Key, class _HF, class _Ex, class _Eq, class _All>
    void hashtable<_Val,_Key,_HF,_Ex,_Eq,_All>::clear()
    {
      for (size_type __i = 0; __i < _M_buckets.size(); ++__i) {      // 针对每一个bucket
        _Node* __cur = _M_buckets[__i];
        while (__cur != 0) {                                         // 将bucket list中的每一个节点删除掉
          _Node* __next = __cur->_M_next;
          _M_delete_node(__cur);                                     // buckets vector并未释放掉空间,仍保有原来大小
          __cur = __next;
        }
        _M_buckets[__i] = 0;                                         // 令bucket内容为null指针
      }
      _M_num_elements = 0;                                           // 令总节点个数为0
    }
    
    // 复制操作   
    template <class _Val, class _Key, class _HF, class _Ex, class _Eq, class _All>
    void hashtable<_Val,_Key,_HF,_Ex,_Eq,_All>
      ::_M_copy_from(const hashtable& __ht)
    {
      // 先清除己方的buckets vector。这操作是调用vector::clear,将整个容器清空buckets.clear()
      _M_buckets.clear();  
      // 为己方的buckets vector保留空间,使与对方相同,如果己方空间大于对方,就不动,如果己方空间小于对方,就会增大。
      _M_buckets.reserve(__ht._M_buckets.size());
      // 从己方的buckets vector尾端开始,插入n个元素,其值为null指针
      // 注意,此时buckets vector为空,所以所谓尾端,就是起头处
      _M_buckets.insert(_M_buckets.end(), __ht._M_buckets.size(), (_Node*) 0);
      __STL_TRY {
        for (size_type __i = 0; __i < __ht._M_buckets.size(); ++__i) {  // 针对buckets vector
          const _Node* __cur = __ht._M_buckets[__i];                    // 复制vector的每一个元素(是个指针,指向hashtable节点)
          if (__cur) {
            _Node* __copy = _M_new_node(__cur->_M_val);
            _M_buckets[__i] = __copy;
    
            for (_Node* __next = __cur->_M_next;                        // 针对同一个buckets list,复制每一个节点
                 __next; 
                 __cur = __next, __next = __cur->_M_next) {
              __copy->_M_next = _M_new_node(__next->_M_val);
              __copy = __copy->_M_next;
            }
          }
        }
        _M_num_elements = __ht._M_num_elements;                         // 重新登录节点个数(hashtable的大小)
      }
      __STL_UNWIND(clear());
    }
    
    // hashtable中的find函数
      iterator find(const key_type& __key) 
      {
        size_type __n = _M_bkt_num_key(__key);         // 首先寻找落在哪一个bucket内
        _Node* __first;
        for ( __first = _M_buckets[__n];               // 以下,从bucket list的头开始,一一比对每个元素的键值。对比成功就跳出
              __first && !_M_equals(_M_get_key(__first->_M_val), __key);
              __first = __first->_M_next)
          {}
        return iterator(__first, this);
      } 
    
    // hashtable中的count函数
      size_type count(const key_type& __key) const
      {
        const size_type __n = _M_bkt_num_key(__key);   // 首先寻找在哪一个bucket内
        size_type __result = 0;
        // 以下,从bucket list的头开始,一一比对每个元素的键值。比对成功就累加1。
        for (const _Node* __cur = _M_buckets[__n]; __cur; __cur = __cur->_M_next)
          if (_M_equals(_M_get_key(__cur->_M_val), __key))
            ++__result;
        return __result;
      }
    
    // 哈希函数,下面定义为仿函数。大部分哈希函数中什么也没做,只是忠实返回原值。
    template <class _Key> struct hash { };
    
    inline size_t __stl_hash_string(const char* __s)
    {
      unsigned long __h = 0; 
      for ( ; *__s; ++__s)
        __h = 5*__h + *__s;
    
      return size_t(__h);
    }
    
    __STL_TEMPLATE_NULL struct hash<char*>
    {
      size_t operator()(const char* __s) const { return __stl_hash_string(__s); }
    };
    
    __STL_TEMPLATE_NULL struct hash<const char*>
    {
      size_t operator()(const char* __s) const { return __stl_hash_string(__s); }
    };
    
    __STL_TEMPLATE_NULL struct hash<char> {
      size_t operator()(char __x) const { return __x; }
    };
    __STL_TEMPLATE_NULL struct hash<unsigned char> {
      size_t operator()(unsigned char __x) const { return __x; }
    };
    __STL_TEMPLATE_NULL struct hash<signed char> {
      size_t operator()(unsigned char __x) const { return __x; }
    };
    __STL_TEMPLATE_NULL struct hash<short> {
      size_t operator()(short __x) const { return __x; }
    };
    __STL_TEMPLATE_NULL struct hash<unsigned short> {
      size_t operator()(unsigned short __x) const { return __x; }
    };
    __STL_TEMPLATE_NULL struct hash<int> {
      size_t operator()(int __x) const { return __x; }
    };
    __STL_TEMPLATE_NULL struct hash<unsigned int> {
      size_t operator()(unsigned int __x) const { return __x; }
    };
    __STL_TEMPLATE_NULL struct hash<long> {
      size_t operator()(long __x) const { return __x; }
    };
    __STL_TEMPLATE_NULL struct hash<unsigned long> {
      size_t operator()(unsigned long __x) const { return __x; }
    };
    
  • 参考文献

    STL源码剖析——侯捷

    STL源码

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值