CUJ:標準庫:bitset和bit vector (轉)

amyz發表於2007-08-14
CUJ:標準庫:bitset和bit vector (轉)[@more@]

The Standard Librarian: Bitsets and Bit Vectors:namespace prefix = o ns = "urn:schemas--com::office" />

Matt Austern

http://www.cuj.com/experts/1905/austern.htm?topic=experts

在 C++裡,你能如願地玩弄位元,而且甚至不用到宏。

------------------------------------------------------------------------------

  編過程的人都熟悉布林選項標誌:將一組選項處理成一個整體,將它們打包進一個,為每個選項使用一個位。比如,要設定的許可,你可能類似於這樣寫:

  chmod("my_file",

  S_IWUSR | S_IRUSR |

  S_IRGRP | S_IROTH);

  每個常量對應一個位;透過用“位或”操作組合它們,你能夠一次就指定很多選項。

  將多個選項位打包進一個word的行為非常常見。這個技巧被用於很多地方,在Unix和的中,在C++標準執行庫的ios_base格式化標誌中,並且它的一些形式容易在大型的中出現。位元的集合是很重要的。

  不難理解為什麼這個技巧很常見:另外一種實現方法是,用陣列或結構,每個選項對應一個不同的欄位,這比較笨拙並且浪費。然而,有時候這個技巧會造成麻煩。首先,某些運算可能是笨拙的:設定一個被命名的位還比較直接(flags |= S_IRGRP),但清除一個位(flages &= ~S_IWGRP)多少有些醜陋。你能測試一個位是否被置位,透過將它掩模:if (falgs & S_IWUSR);但當心犯“顯式”測試的錯誤:if ((flags & S_IWUSR) == true),或更糟的if (flags & S_IWUSR == ture)。對應於命名的位,對於編號的位,同樣笨拙:需要使用類似於flags &= ~(1 << n)這樣的,通常還要加上強制型別轉換。最後,這個技巧難以括大到有很多選項的情況:一旦標誌的數目超過long的位數,就需要另謀它路了。

  因為位元的集合很重要,所以C++標準執行庫提供了對它們的顯式支援--事實上,有幾種支援。有時你將會仍然想使用低層次的位操作(並且,某些時候你不得不這麼做,如果你正在和C語言API互動),但是在絕大部份情況下,C++執行庫中的版本會更合適。它們有一些小問題,但絕大部份很容易繞過。 

bitset

類std::bitset出現在C++標準第23章“關聯容器”中。這不是它應該出現的正確位置,因為bitset與set和map這樣的關聯容器沒有任何關係,它甚至沒有滿足STL容器的最基本的需求。把bitset當做一個整數會更好,它的每個的位都能單獨訪問--但它不受long的長度的限制。bitset的長度大小在編譯期決定(位元的數目是模板引數),但是沒有上限:bitset<32>是32位長,bitset<1000>是1000。

  你用過的整型位操作對bistset繼續有效,並且,為了方便,還新增了一些操作。舉例來說,你能寫b1 ^ b2來進行“位或”運算(至少在b1和 b2長度相同時)。操作單個位有兩個不同的介面:你可以用b.set(n)設定第n個位,用b.reset(n)清除它,並且用if (b.test(n))測試它;或者,幾乎等價地,你可以把bitset當做一個陣列,用b[n] = true,b[n] = false,和if (b[n])來實現相同的操作。(稱“幾乎”,是因為有一個小小的差異:陣列版本不進行越界檢查,而set()/reset()/test()版本進行。如果傳給set()/reset()/test()的引數太大,將得到out_of_range異常。)

  如果你用的bitset大小適當,直覺上你能將它當作一個整數:有一個構造以從unsigned long建立一個bitset,還有一個成員函式to_ulong()以從bitset獲得一個unsigned long。當然,你不能直接使用這個建構函式以初始化超過unsigned long範圍的位;同樣,你不能用to_ulong()提取超過unsigned long的位。(如果你試著做了, 並且超過unsigned long的任何一位被置位了,那麼to_ulong()將會丟擲一個異常)。然而,如果需要,你能透過使用移位和掩模來繞開這些限制: 

const int N =

  sizeof(unsigned long) * CHAR_BIT;

unsigned long high = 0x7B62;

unsigned long low = 0x1430;

std::bitset<2*N> b

  = (std::bitset<2*N>(high) << N) |

  std::bitset<2*N>(low);

...

const std::bitset<2*N>

  mask((unsigned long)(-1));

low = (b & mask).to_ulong();

high = (b >> N).to_ulong();

  第0個位被定義為最低有效位,所以,舉例來說,如果你寫:

std::bitset<4> b(0xA);

被置位的位是b[1]和b[3]。

  很容易用bitset替代傳統的選項標誌:只要在標頭檔案中申明一個bitset以替代整數常量。我們已經說到了使用bitset的兩個好處:你得到比long所能表示的更多的標誌,你能用更容易和更的方法來操作每個位。另外一個是,bitset給你一個轉換機制,以在bitset和文字表述間雙向轉換。

  首先,bitset提供了常用的I/O操作。這個程式,

#include

#include

  int main() {

  std::bitset<12> b(3432);

  std::cout << "3432 in binary is "

  <

}

給出了直觀的結果:

3432 in binary is 110101101000.

  輸入操作以同樣的方法工作:它讀入一個“1”和“0”組成的字串,將它們轉換為一個bitset。

  其次,你能將bitsets轉換成字串或從字串轉換而來:有一個接受一個string引數的建構函式,和bitset<>::to_string()成員函式。唉,雖然這些轉換很有用,但細節表明它非常不方便。接受string的建構函式和to_string()成員函式都是成員模板,因為執行庫的std::basic_string類本身就是模板;平常的字串類,std::string是basic_string的一個別名。

  這些成員模板的通用性受C++的一些晦澀的規則的不幸影響。你必須寫:

std::bitset<6>(std::string("110101"));

而不是 

std::bitset<6>("110101");

  僅將字串文字“110101”直接傳入的版本,將會給出編譯期錯誤,因為不知道該例項化出成員模板的什麼版本。同樣地,如果b 是bitset,你不能只是寫: 

std::string s = b.to_string();

  你必須改用這種實在恐怖的形式:

std::string s

  = b.template to_string

  std::char_traits,

 

  std::allocator >();

  (是的,那個看起來可笑的template關鍵字真的是必須的。)

  當然,在實踐中,你不應該用這樣的東西汙染你的程式碼。除非真的需要和多種字元型別合作,你可以將恐怖的語法細節封裝入輔助函式: 

template <:size_t n="">

std::bitset

from_string(const std::string& s) {

  return std::bitset(s);

}

 

template <:size_t n="">

std::string

to_string (const std::bitset& b) {

  return b.template to_string

  std::char_traits,

  std::allocator >();

}

vector

  bitset確實有一個重要限制:它有一個固定的長度。你可以有比long更長的bitset,但你必須事先指定它的大小。對選項標誌集之類的東西,這很好,但用於其它目的就不怎麼合適了。假如,你正在以複雜的順序處理一個巨大的條款集,並且你需要掌握已經看過哪些。這要求一個布林值的陣列,有理由使用“”的陣列,每個元素用一個位,但bitset就不再是合理的選擇了。你正在處理的條款的數目直到執行時才能知道,並且條款甚至可能增加或移除。

  C++標準執行庫中另外一個管理位集合的機制是vector,vector<>模板的一個特化。在某些方面,vector與bitset非常象:每個元素用一個位表示,讓你能使用陣列的語法(比如,v[3] = true)來訪問單個位。不同之處是bitset使用它自己的特有機制,而vector使用平常的STL介面。你能使用resize()成員函式改變元素的數目,或用push_back()增加新元素,就象其它vector一樣。

  雖然vector沒有對位運算提供特別的支援,你仍然可以使用平常的STL泛型演算法和functor來完成這些操作。比如,不是寫 v3 = v1 & v2 ,你可以寫:

std::vector v3(v1.size());

std::tranorm(v1.begin(), v1.end(),

  v2.begin(), v3.begin(),

  std::logical_and());

  同樣地,將vector按bitset的operator<

std::copy(v.rbegin(), v.rend(),

  std::ostream_iterator (std::cout));

  (這個程式碼依賴於一個事實,預設情況,bool的輸出使用“1”和“0”而不是“true”和“false”。同樣也注意到,我們正在使用 rbegin()和rend()以按逆序複製vector。這是bitset輸出時的方式:bitset在列印時最左邊的數是b[N-1],而不是b[0]。)

  只要有可能,你應該總是優先使用bitset而不是vector:與支援通用目的的vector介面的資料結構相比,固定大小的資料結構會有更好的,同時在空間和時間上。(在我執行過的一個時間測試中,bitset比vector快了幾乎5倍。) 如果所管理的位集大小未不能事先預知,你就需要使用vector

  看起來還有一個情況應該用vector而不是bitset:當與STL泛型演算法互動很重要時。STL泛型演算法使用選擇子,而vector提供了選擇子(我們在上面的例子裡看到了v.begin()和v.end()),bitset沒有。你能用陣列語法訪問bitset中的單個位,但它沒有begin()和end()成員函式。

  然而,你不應該讓這個缺乏阻止你!雖然bitset沒有STL容器的介面,它仍然是一個非常好的(固定大小的)容器。如果使用bitset有意義,並且如果你還需要選擇子,那麼你可以定義一個簡單的“下標選擇子”介面卡,以將選擇子表述(比如*i)轉換為陣列表述(比如b[n])。

  實現很顯然:維護一個下標和指向容器的指標。其細節,大部分就是我們在實現ran iterator時所用到的,見於Listing 1。我們也定義一些非成員的輔助函式,begin()和end(),它們接受一個bitset為引數。(我們在Listing 1中顯示的iterator不像它可能的那樣通用:如果我們願意接受稍微有些笨重的介面,我們就能定義一個能與任何類似與陣列的型別合作的類了。一個通用目的下標選擇子介面卡在處理pre-STL容器類時常常有用,有時,即使是處理STL容器比如vector時都很有用。)

  使用bitset_iterator,bitset現在能與STL互動:比如,你能將一個bitset複製入vector

std::bitset<10> b;

...

std::vector

  b(begin(b), end(b));

  然而,如果你仔細看過Listing 1,你可能已經注意到bitset_iterator的一個問題:名字是一個謊言,因為bitset_iterator並不真的是一個iterator。如果i是一個iterator,那麼*i應該返回i所指物件的引用。bitset_iterator沒有這樣做:const bitset_iterator返回bool,而不是const bool&,而可修改版本的bitset_iterator返回一個型別是bitset<>::reference的物件,而不是bool&。

  因為位不是可獨立定址的,這是我們所能做的最好了;事實上,vector::iterator也以完全相同的方式行動的--再一次,這意味著vector不是一個真正的STL容器。說bitset_iterator 和vecotr::iterator是iterator並不十分正確,但兩者都足夠接近於iterator了,所以它們能被用於很多( 不所有的!)期望iterator的地方。

總結

布林值的陣列在大程式中很常見,並且C++標準執行庫提供了幾個方法來表示這樣的陣列。我沒有列全所有的可能:比如,你能使用valarray,在某些情況下它很適於表示一個稀疏的位向量,就象set一樣。

  很多時候,無論如何,最容易的方法是使用std::bitset。如果你在編譯期知道你的布林陣列應該多大,或至少能指定一個合理的上限,那麼bitset更簡單,更有。在bitset的介面上有些討厭的問題,但藉由一些輔助函式,很容易繞過它們。

Listing 1 - bitset_iterator, an iterator adaptor class for std::bitset

template

struct IF;

 

template

struct IF {

  typedef IfTrue val;

};

 

template

struct IF {

  typedef IfFalse val;

};

 

template <:size_t n="" bool="" is_const="">

class bitset_iterator {

private:

  typedef std::bitset Bitset;

  typedef typename IF::val

  qBitset;

 

  typedef std::random_access_iterator_tag

  iterator_category;

  typedef bool value_type;

  typedef std::ptrdiff_t difference_type;

  typedef typename IF::val*

  pointer;

  typedef typename IF

  bool,

  typename bitset::reference>::val

   reference;

 

  qBitset* B;

  std::size_t n;

 

public:

  bitset_iterator() : B(), n() { }

  bitset_iterator(qBitset& b, std::size_t sz)

  : B(&b), n(sz) { }

  bitset_iterator(const bitset_iterator& x)

  : B(x.B), n(x.n) { }

 

  bitset_iterator& operator=(const bitset_iterator& x) {

  B = x.B;

  n = x.n;

  }

 

public:

  reference operator*() const { return (*B)[n]; }

  reference operator[](std::ptrdiff_t x) const {

  return (*B)[n + x];

  }

 

  bitset_iterator& operator++() { ++n; return *this; }

  bitset_iterator operator++(int) {

  ++n;

  return bitset_iterator(*B, n-1);

  }

  bitset_iterator& operator--() { --n; return *this; }

  bitset_iterator operator--(int) {

  --n;

  return bitset_iterator(*B, n+1);

  }

 

  bitset_iterator operator+(std::ptrdiff_t x) const {

  return bitset_iterator(*B, n + x);

  }

  bitset_iterator& operator+=(std::ptrdiff_t x) {

  n += x;

  return *this;

  }

  bitset_iterator operator-(std::ptrdiff_t x) const {

  return bitset_iterator(*B, n - x);

  }

  bitset_iterator& operator-=(std::ptrdiff_t x) {

  n -= x;

  return *this;

  }

 

public:

  friend bool operator==(bitset_iterator x,

  bitset_iterator y) {

  return x.B == y.B && x.n == y.n;

  }

  friend bool operator!=(bitset_iterator x,

  bitset_iterator y) {

  return !(x == y);

  }

 

  friend bool operator

  bitset_iterator y) {

  return x.n < y.n;

  }

  friend bool operator>(bitset_iterator x,

   bitset_iterator y) {

  return y < x;

  }

  friend bool operator<=(bitset_iterator x,

  bitset_iterator y) {

  return !(y < x);

  }

  friend bool operator>=(bitset_iterator x,

  bitset_iterator y) {

  return !(x < y);

  }

 

  friend std::ptrdiff_t operator-(bitset_iterator x,

  bitset_iterator y) {

  return x.n - y.n;

  }

  friend bitset_iterator operator+(std::ptrdiff_t n1,

   bitset_iterator x) {

  return bitset_iterator(*x.B, x.n + n1);

  }

};

 

 

template <:size_t n="">

bitset_iterator

begin(const std::bitset& b) {

  return bitset_iterator(b, 0);

}

 

template <:size_t n="">

bitset_iterator

end(const std::bitset& b) {

  return bitset_iterator(b, N);

}

 

template <:size_t n="">

bitset_iterator

begin(std::bitset& b) {

  return bitset_iterator(b, 0);

}

 

template <:size_t n="">

bitset_iterator

end(std::bitset& b) {

  return bitset_iterator(b, N);

}

— End of Listing —

 


來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/10752019/viewspace-956254/,如需轉載,請註明出處,否則將追究法律責任。

相關文章