STL标准库中的bitset原理解析

STL标准库中的bitset原理解析

bitset简介

  bitset,中文叫位图,类似于每一个元素都是0或者1的数组,但位图的空间利用率比数组高很多。在Linux系统中,位图广泛应用于进程id的分配和文件描述符(file describer)的分配。并且,位图可以用于排序。位图的基本原理是用一个bit位代表一个整数,如果位图中的第N为1,那么就表示整数N存在。

  在c++的STL中实现了位图,包含在头文件中。bitset与vector很相似,都是位的集合,并且提供常数时间的特定位访问。他们之间的差别来自于主要体现在两个方面。首先,bitset的大小是不可变的,它的大小由模板参数N确定,N是整数常量。而vector的大小是可变的;其次,bitset并不是一个序列式容器,更准确地说,bitset并不是标准的STL容器。bitset并没有iterator,也没有begin()和end()。这样设计的理由是bitset中的元素就是bit位,不能容纳任何其他元素,并且bitset中bit位有特定的操作都需要实现,例如与、或、非以及异或。因此,不能够也不需要把bitset设计成STL标准容器。

  关于bitset的用法,请参考http://www.cplusplus.com/reference/bitset/bitset/?kw=bitset

STL中的bitset原理解析

bitset的基类_Base_bitse解析

  这里参考的是sgi版本的STL实现。bitset继承自_Base_bitse。_Base_bitse包含了一个unsigned long的数组,一般地,一个unsigned long有32位,可以表示32个数,最低位的数最小,最高位的数最大。

template
struct _Base_bitset {
  typedef unsigned long _WordT;

  _WordT _M_w[_Nw];                // 0 is the least significant word.

  _Base_bitset( void ) { _M_do_reset(); }
  _Base_bitset(unsigned long __val) {
    _M_do_reset();
    _M_w[0] = __val;
  }

  在_Base_bitse中有一个对数组_M_w中所有bit为1的位的出现次数的统计函数,函数名为M_do_count,一般的统计方式都是逐位统计。为了提高效率,这里用了一个辅助数组_S_bit_count,这个数组记录了一个0-255中的每个数的二进制表示中有几个1。_S_bit_count的一部分代码如下。

  size_t _M_do_count() const {
    size_t __result = 0;
    const unsigned char* __byte_ptr = (const unsigned char*)_M_w;
    const unsigned char* __end_ptr = (const unsigned char*)(_M_w+_Nw);

    while ( __byte_ptr < __end_ptr ) {
      __result += _Bit_count<true>::_S_bit_count[*__byte_ptr];
      __byte_ptr++;
    }
    return __result;
  }
template<bool __dummy>
unsigned char _Bit_count<__dummy>::_S_bit_count[] = {
  0, /*   0 */ 1, /*   1 */ 1, /*   2 */ 2, /*   3 */ 1, /*   4 */
  2, /*   5 */ 2, /*   6 */ 3, /*   7 */ 1, /*   8 */ 2, /*   9 */
  2, /*  10 */ 3, /*  11 */ 2, /*  12 */ 3, /*  13 */ 3, /*  14 */
  4, /*  15 */ 1, /*  16 */ 2, /*  17 */ 2, /*  18 */ 3, /*  19 */
  2, /*  20 */ 3, /*  21 */ 3, /*  22 */ 4, /*  23 */ 2, /*  24 */
  3, /*  25 */ 3, /*  26 */ 4, /*  27 */ 3, /*  28 */ 4, /*  29 */
  4, /*  30 */ 5, /*  31 */ 1, /*  32 */ 2, /*  33 */ 2, /*  34 */
  3, /*  35 */ 2, /*  36 */ 3, /*  37 */ 3, /*  38 */ 4, /*  39 */
  2, /*  40 */ 3, /*  41 */ 3, /*  42 */ 4, /*  43 */ 3, /*  44 */
  4, /*  45 */ 4, /*  46 */ 5, /*  47 */ 2, /*  48 */ 3, /*  49 */
  3, /*  50 */ 4, /*  51 */ 3, /*  52 */ 4, /*  53 */ 4, /*  54 */
  5, /*  55 */ 3, /*  56 */ 4, /*  57 */ 4, /*  58 */ 5, /*  59 */
  ......

  在_Base_bitse中有一个找数组_M_w中第一个为1的bit的位的位置的函数,函数名为M_do_find_first。为了提高效率,在函数中使用了另外一个辅助数组_S_first_one,_S_first_one中记录了0-255中每个数的二进制表示中,从低位到高位,第一个为1的bit位的位置。_S_first_one的一部分代码如下所示。

template
size_t _Base_bitset<_Nw>::_M_do_find_first(size_t __not_found) const 
{
  for ( size_t __i = 0; __i < _Nw; __i++ ) {
    _WordT __thisword = _M_w[__i];
    if ( __thisword != static_cast<_WordT>(0) ) {
      // find byte within word
      for ( size_t __j = 0; __j < sizeof(_WordT); __j++ ) {
        unsigned char __this_byte
          = static_cast<unsigned char>(__thisword & (~(unsigned char)0));
        if ( __this_byte )
          return __i*__BITS_PER_WORD + __j*CHAR_BIT +
            _First_one<true>::_S_first_one[__this_byte];

        __thisword >>= CHAR_BIT;
      }
    }
  }
  // not found, so return an indication of failure.
  return __not_found;
}
template<bool __dummy>
unsigned char _First_one<__dummy>::_S_first_one[] = {
  0, /*   0 */ 0, /*   1 */ 1, /*   2 */ 0, /*   3 */ 2, /*   4 */
  0, /*   5 */ 1, /*   6 */ 0, /*   7 */ 3, /*   8 */ 0, /*   9 */
  1, /*  10 */ 0, /*  11 */ 2, /*  12 */ 0, /*  13 */ 1, /*  14 */
  0, /*  15 */ 4, /*  16 */ 0, /*  17 */ 1, /*  18 */ 0, /*  19 */
  2, /*  20 */ 0, /*  21 */ 1, /*  22 */ 0, /*  23 */ 3, /*  24 */
  0, /*  25 */ 1, /*  26 */ 0, /*  27 */ 2, /*  28 */ 0, /*  29 */
  1, /*  30 */ 0, /*  31 */ 5, /*  32 */ 0, /*  33 */ 1, /*  34 */
  0, /*  35 */ 2, /*  36 */ 0, /*  37 */ 1, /*  38 */ 0, /*  39 */
  3, /*  40 */ 0, /*  41 */ 1, /*  42 */ 0, /*  43 */ 2, /*  44 */
  0, /*  45 */ 1, /*  46 */ 0, /*  47 */ 4, /*  48 */ 0, /*  49 */
  1, /*  50 */ 0, /*  51 */ 2, /*  52 */ 0, /*  53 */ 1, /*  54 */
  0, /*  55 */ 3, /*  56 */ 0, /*  57 */ 1, /*  58 */ 0, /*  59 */
  ......

  此外_Base_bitse对数组_M_w大小为1的情况进行了特化。此处我并没有发现一般的_Base_bitse版本在数组_M_w大小为1有任何不适合,我猜想特化的原因是当数组_M_w大小为1时,就不需要数组了,直接设置一个unsigned long即可,这样可以提高效率。

//
// Base class: specialization for a single word.
//

__STL_TEMPLATE_NULL struct _Base_bitset<1> {
  typedef unsigned long _WordT;
  _WordT _M_w;

  _Base_bitset( void ) : _M_w(0) {}
  _Base_bitset(unsigned long __val) : _M_w(__val) {}

  static size_t _S_whichword( size_t __pos )
    { return __pos / __BITS_PER_WORD; }
  static size_t _S_whichbyte( size_t __pos )
    { return (__pos % __BITS_PER_WORD) / CHAR_BIT; }
  static size_t _S_whichbit( size_t __pos )
    {  return __pos % __BITS_PER_WORD; }
  static _WordT _S_maskbit( size_t __pos )
    { return (static_cast<_WordT>(1)) << _S_whichbit(__pos); }

  _WordT& _M_getword(size_t)       { return _M_w; }
  _WordT  _M_getword(size_t) const { return _M_w; }

  _WordT& _M_hiword()       { return _M_w; }
  _WordT  _M_hiword() const { return _M_w; }

  void _M_do_and(const _Base_bitset<1>& __x) { _M_w &= __x._M_w; }
  void _M_do_or(const _Base_bitset<1>& __x)  { _M_w |= __x._M_w; }
  void _M_do_xor(const _Base_bitset<1>& __x) { _M_w ^= __x._M_w; }
  void _M_do_left_shift(size_t __shift)     { _M_w <<= __shift; }
  void _M_do_right_shift(size_t __shift)    { _M_w >>= __shift; }
  void _M_do_flip()                       { _M_w = ~_M_w; }
  void _M_do_set()                        { _M_w = ~static_cast<_WordT>(0); }
  void _M_do_reset()                      { _M_w = 0; }

bitset原理解析

  bitset私有继承自_Base_bitset,两者之间的区别是,bitset中操作的最小单位是位,所以bitset的模板参数_Nb表示的是位的数量;而_Base_bitset中操作的最小单位是unsigned long,它的模板参数_Nw是unsigned long的数量。

template
class bitset : private _Base_bitset<__BITSET_WORDS(_Nb)>
{
private:
  typedef _Base_bitset<__BITSET_WORDS(_Nb)> _Base;
  typedef unsigned long _WordT;

private:
  void _M_do_sanitize() {
    _Sanitize<_Nb%__BITS_PER_WORD>::_M_do_sanitize(this->_M_hiword());
  }

  bitset中有一个友元类reference,reference是某一位的引用,在函数reference operator[](size_t n)中用到。在reference类中,包含了两个成员:_WordT *_M_wp以及size_t _M_bpos;分别表示了位数组以及位所在的位置。并且,类中定义了对这一位的与或非等操作。

// bit reference:
  class reference;
  friend class reference;

  class reference {
    friend class bitset;

    _WordT *_M_wp;
    size_t _M_bpos;

    // left undefined
    reference();

  public:
    reference( bitset& __b, size_t __pos ) {
      _M_wp = &__b._M_getword(__pos);
      _M_bpos = _Base::_S_whichbit(__pos);
    }

    ~reference() {}

    // for b[i] = __x;
    reference& operator=(bool __x) {
      if ( __x )
        *_M_wp |= _Base::_S_maskbit(_M_bpos);
      else
        *_M_wp &= ~_Base::_S_maskbit(_M_bpos);

      return *this;
    }

  这是我阅读bitset源码时的收获,如有兴趣,请自己阅读源码:)

你可能感兴趣的:(C-C++,STL)