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
这里参考的是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私有继承自_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源码时的收获,如有兴趣,请自己阅读源码:)