CUJ:標準庫:bitset和bit vector (轉)
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 } template <:size_t n=""> std::string to_string (const std::bitset return b.template to_string std::char_traits std::allocator } bitset確實有一個重要限制:它有一個固定的長度。你可以有比long更長的bitset,但你必須事先指定它的大小。對選項標誌集之類的東西,這很好,但用於其它目的就不怎麼合適了。假如,你正在以複雜的順序處理一個巨大的條款集,並且你需要掌握已經看過哪些。這要求一個布林值的陣列,有理由使用“”的陣列,每個元素用一個位,但bitset就不再是合理的選擇了。你正在處理的條款的數目直到執行時才能知道,並且條款甚至可能增加或移除。 C++標準執行庫中另外一個管理位集合的機制是vector 雖然vector std::vector std::tranorm(v1.begin(), v1.end(), v2.begin(), v3.begin(), std::logical_and 同樣地,將vector std::copy(v.rbegin(), v.rend(), std::ostream_iterator (這個程式碼依賴於一個事實,預設情況,bool的輸出使用“1”和“0”而不是“true”和“false”。同樣也注意到,我們正在使用 rbegin()和rend()以按逆序複製vector 只要有可能,你應該總是優先使用bitset而不是vector 看起來還有一個情況應該用vector 然而,你不應該讓這個缺乏阻止你!雖然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 布林值的陣列在大程式中很常見,並且C++標準執行庫提供了幾個方法來表示這樣的陣列。我沒有列全所有的可能:比如,你能使用valarray 很多時候,無論如何,最容易的方法是使用std::bitset。如果你在編譯期知道你的布林陣列應該多大,或至少能指定一個合理的上限,那麼bitset更簡單,更有。在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 typedef typename IF qBitset; typedef std::random_access_iterator_tag iterator_category; typedef bool value_type; typedef std::ptrdiff_t difference_type; typedef typename IF 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 : 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 return bitset_iterator } template <:size_t n=""> bitset_iterator end(const std::bitset return bitset_iterator } template <:size_t n=""> bitset_iterator begin(std::bitset return bitset_iterator } template <:size_t n=""> bitset_iterator end(std::bitset return bitset_iterator } — End of Listing — vector
總結
Listing 1 - bitset_iterator, an iterator adaptor class for std::bitset
來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/10752019/viewspace-956254/,如需轉載,請註明出處,否則將追究法律責任。
相關文章
- c/c++ 標準庫 vectorC++
- MSVC2019的vector標準庫實現原始碼分析原始碼
- 什麼是 C 和 C ++ 標準庫?
- C++標準庫名字和標頭檔案--表C++
- C++標準庫、C++標準模版庫介紹C++
- stm32標準庫和HAL庫的關係
- Go 每日一庫之 bitsetGo
- 標準庫之template
- python常用標準庫Python
- Go標準庫ContextGoContext
- C++標準庫C++
- 模組轉測標準
- Linux的標準輸入、標準輸出和標準錯誤Linux
- go 語言位操作庫 bitsetGo
- C++標準庫:chronoC++
- C++標準庫:randomC++random
- golang標準庫之 fmtGolang
- C標準庫學習
- PHP 標準庫 SplStack 棧PHP
- Python標準庫(待續)Python
- python標準庫目錄Python
- 標準庫之 random 模組random
- C 標準庫 – ctype.h
- 標準庫~JSON物件詳解JSON物件
- Python標準庫06 子程式Python
- go語言標準庫 - timeGo
- go語言標準庫 - strconvGo
- go語言標準庫 - regexpGo
- go語言標準庫 - logGo
- python常用標準庫(壓縮包模組zipfile和tarfile)Python
- Swift標準庫原始碼閱讀筆記 - Array和ContiguousArraySwift原始碼筆記
- ECMA標準ECMAScript(JavaScript的一個標準)和C#JavaScriptC#
- /dev/null和標準*使用devNull
- C++標準庫有四種智慧指標C++指標
- Rust的標準庫為啥很小?Rust
- python官方標準庫(中文版)Python
- Profile標準化資料庫管理資料庫
- 標準庫 fmt 包的基本使用
- Golang標準庫學習—container/heapGolangAI