# 【C++ 筆記】bitset 容器,C++ 二進位運算的絕佳利器 目錄(Table of Contents): [TOC] --- 很感謝你點進來這篇文章。 你好,我並不是什麼 C++、程式語言的專家,所以本文若有些錯誤麻煩請各位鞭大力一點,我極需各位的指正及指導!!本系列文章的性質主要以詼諧的口吻,一派輕鬆的態度自學程式語言,如果你喜歡,麻煩留言說聲文章讚讚吧! ## 什麼是 bitset? `std::bitset<N>` 是 C++ STL(位於標頭檔 `<bitset>`)中,一個用來表示固定長度為 $N$ 位元(bits)的位元序列(bit sequence)的類別模版。 假設 $N = 16$ ,那就表示有 16 位元,數字 1 就會表示成 "0000000000000001"。 bitset 也是 C++ 當中的一種容器,這個容器能夠做到單獨、有效的去操作每一個位元,也能解決位元運算、實現二進位表示法的工具,這也是為什麼需要用到它的原因。 ## 使用 bitset bitset 在使用之前需要引入標頭檔 `<bitset>`,另外以下是其基本語法: ```cpp= bitset<n> name; ``` 其中 `n` 是固定的位元數,而 `name` 則為 bitset 容器的名稱。 ### 初始化 bitset 可以透過建構子 `()` 對 bitset 進行初始化的動作,如下程式碼做的動作: ```cpp= #include <iostream> #include <bitset> using namespace std; int main(){ bitset <16> a(5); // 十進位數字 5 cout << a; return 0; } ``` Output: ``` 0000000000000101 ``` 由於給予的初始值是十進位的數字 5,所以理所當然會輸出 2 進位 16 bits 的 5。 初始值不一定是給予十進位數字,也可以是二進位字串: ```cpp= #include <iostream> #include <bitset> using namespace std; int main(){ bitset <16> a("101"); cout << a; return 0; } ``` Output: ``` 0000000000000101 ``` 你也可以選擇不加上初始值,那預設就會是 16 個 0: ```cpp= #include <iostream> #include <bitset> using namespace std; int main(){ bitset <16> a; cout << a; return 0; } ``` Output: ``` 0000000000000000 ``` 最後是比較奇特的自訂字元初始化,可以指定哪個字元代表 1,哪一個代表 0: ```cpp= #include <iostream> #include <bitset> using namespace std; int main(){ bitset<16> a("ooxxxoxoxoxoxoox", 16, 'o', 'x'); // o 指定 0, x 指定 1 cout << a; return 0; } ``` Output: ``` 0011101010101001 ``` ### 存取 bitset 的單一位元 使用 `[]` 運算子,裡面索引一樣是從 0 開始。 ```cpp= #include <iostream> #include <bitset> using namespace std; int main(){ bitset <6> a("101"); cout << a[0]; // 存取第一個位元 1 return 0; } ``` Output: ``` 1 ``` ## bitset 常用函數 ### 設定、重置操作 - `set()`:設定位元為 1。 ```cpp= #include <iostream> #include <bitset> using namespace std; int main(){ bitset <6> a(32); // 32 = 100000 a.set(); // 設定所有位元為 1 a.set(2); // 設定第 2 位為 1 a.set(2, 0); // 設定第 2 位為 0 cout << a; return 0; } ``` Output: ``` 111011 ``` - `reset()`:重置位元為 0。 ```cpp= #include <iostream> #include <bitset> using namespace std; int main(){ bitset <6> a(32); // 32 = 100000 a.reset(); // 所有位元設為 0 cout << "位元重置:" << a << endl; a.set(); // 所有位元設為 1 a.reset(3); // 第 3 位設為 0 cout << "第三位是 0:" << a << endl; return 0; } ``` Output: ``` 位元重置:000000 第三位是 0:110111 ``` - `flip()`:翻轉位元(0 變 1,1 變 0)。 ```cpp= #include <iostream> #include <bitset> using namespace std; int main(){ bitset <6> a(32); // 32 = 100000 a.flip(); // 翻轉所有位元 -> 011111 a.flip(1); // 翻轉第 32 位 -> 011101 cout << a; return 0; } ``` Output: ``` 011101 ``` ### 查詢操作 - `test()`:測試某位元是否為 1。 ```cpp= #include <iostream> #include <bitset> using namespace std; int main(){ bitset <6> a(32); // 32 = 100000 cout << "第一位是 1 嗎?" << (a.test(0) ? "Yes" : "No"); return 0; } ``` Output: ``` 第一位是 1 嗎?No ``` - `count()`:計算有多少個 1。 這蠻好用的,有些題目會考有多少個 1,直接用上這個就對了。 ```cpp= #include <iostream> #include <bitset> using namespace std; int main(){ bitset <6> a(32); // 32 = 100000 cout << "有多少個 1?" << a.count(); return 0; } ``` Output: ``` 有多少個 1?1 ``` - `size()`:回傳 bitset 的大小,這就不提供範例了。 - `any()`:是否有任何位元為 1。 ```cpp= #include <iostream> #include <bitset> using namespace std; int main(){ bitset <6> a(32); // 32 = 100000 cout << "有1嗎?" << (a.any() ? "Yes" : "No"); return 0; } ``` Output: ``` 全都是1?Yes ``` - `all()`:是否所有位元都為 1。 ```cpp= #include <iostream> #include <bitset> using namespace std; int main(){ bitset <6> a(32); // 32 = 100000 cout << "全都1?" << (a.all() ? "Yes" : "No"); return 0; } ``` Output: ``` 全都1?No ``` - `none()`:是否所有位元都為 0。 ```cpp= #include <iostream> #include <bitset> using namespace std; int main(){ bitset <6> a(32); // 32 = 100000 cout << "全都0?" << (a.none() ? "Yes" : "No"); return 0; } ``` Output: ``` 全都0?No ``` ## 型態轉換操作 - `to_string()`:轉成字串。 ```cpp= #include <iostream> #include <bitset> using namespace std; int main(){ bitset <6> a(32); // 32 = 100000 string str = a.to_string(); cout << str; return 0; } ``` Output: ``` 100000 ``` - `to_ulong()`:轉成 unsigned long。 ```cpp= #include <iostream> #include <bitset> using namespace std; int main(){ bitset <6> a(32); // 32 = 100000 unsigned long val = a.to_ulong(); cout << val; return 0; } ``` Output: ``` 32 ``` - `to_ullong()`:轉成 unsigned long long。 ```cpp= #include <iostream> #include <bitset> using namespace std; int main(){ bitset <6> a(32); // 32 = 100000 unsigned long long val = a.to_ullong(); cout << val; return 0; } ``` Output: ``` 32 ``` ## 支援的位元運算子 Bitset 支援以下這幾種位元運算子: - `&`:AND - `|`:OR - `^`:XOR - `~`:NOT - `>>=`:右移賦值運算子。 - `<<=`:左移賦值運算子。 - `&=`:AND 賦值運算子。 - `|=`:OR 賦值運算子。 - `^=`:XOR 賦值運算子。 範例: ```cpp= #include <iostream> #include <bitset> using namespace std; int main(){ bitset<8> b1("10101010"); bitset<8> b2("11110000"); bitset<8> result = b1 & b2; cout << result; return 0; } ``` Output: ``` 10100000 ``` 也可以做比較運算: ```cpp= #include <iostream> #include <bitset> using namespace std; int main(){ bitset<8> b1("10101010"); bitset<8> b2("11110000"); if (b1 == b2){ cout << "equal"; } else{ cout << "not equal"; } return 0; } ``` Output: ``` not equal ``` ## 為什麼要用 bitset? 首先是空間效率使用的問題,bitset 一個元素只占一個 bit 而已,而一個 char 是一個 byte,等於 8 個 bit,就可見他的使用效率,比什麼 bool 陣列來得更省記憶體。 再來是高效能的操作,因為在編譯時期(Runtime)就確定大小,能進行高效的位元儲存和操作。 最後就是因為他蠻好用的,不用自己手搓完整的位元運算出來。 ## 習題練習 ### Uva 10019 - Funny Encryption Method Uva Online Judge:https://onlinejudge.org/index.php?option=com_onlinejudge&Itemid=8&page=show_problem&problem=960 Zerojudge:https://zerojudge.tw/ShowProblem?problemid=e545 PDF Source:https://onlinejudge.org/external/100/10019.pdf 範例程式碼: ```cpp= #include <bits/stdc++.h> using namespace std; int main(){ int t; cin >> t; while (t--){ int N; cin >> N; bitset <16> b1(N); int N2 = stoi(to_string(N), nullptr, 16); bitset <16> b2(N2); cout << b1.count() << " " << b2.count() << "\n"; } return 0; } ``` ### Uva 10931 - Parity Uva Online Judge:https://onlinejudge.org/index.php?option=com_onlinejudge&Itemid=8&page=show_problem&category=24&problem=1872 Zerojudge:https://zerojudge.tw/ShowProblem?problemid=a132 PDF Source:https://onlinejudge.org/external/109/10931.pdf 範例程式碼: ```cpp= #include <bits/stdc++.h> using namespace std; int count_ones(int x) { int count = 0; while (x > 0) { count += x & 1; x >>= 1; } return count; } int main() { ios::sync_with_stdio(false), cin.tie(nullptr); int I; while (cin >> I && I != 0){ bitset<32> binary(I); string result = binary.to_string(); auto f1 = result.find('1'); result = result.substr(f1); cout << "The parity of " << result << " is " << count_ones(I) << " (mod 2)." << "\n"; } return 0; } ``` ## 總結 `std::bitset<N>` 是 C++ STL 中用來表示固定長度 N 位元的容器,需要引入 `<bitset>` 標頭檔。它能有效地操作每一個位元,是處理位元運算和二進位表示的實用工具。 四種初始化方法: 1. 十進位數字:`bitset<16> a(5)` 輸出 `0000000000000101` 2. 二進位字串:`bitset<16> a("101")` 輸出 `0000000000000101` 3. 預設值(全為 0):`bitset<16> a` 輸出 `0000000000000000` 4. 自訂字元:`bitset<16> a("ooxxxoxox", 16, 'o', 'x')` 可指定哪個字元代表 0 或 1 存取位元: - 使用 `[]` 運算子,索引從 0 開始,例如 `a[0]` 存取第一個位元。 設定與重置: 1. `set()`:將位元設為 1(可指定位置或全部) 2. `reset()`:將位元設為 0(可指定位置或全部) 3. `flip()`:翻轉位元(0 變 1,1 變 0) 查詢操作: 1. `test(pos)`:測試指定位元是否為 1 2. `count()`:計算有多少個 1 3. `any()`:檢查是否有任何位元為 1 4. `all()`:檢查是否所有位元都為 1 5. `none()`:檢查是否所有位元都為 0 6. `size()`:回傳 bitset 大小 型態轉換: 1. `to_string()`:轉換為字串 2. `to_ulong()`:轉換為 unsigned long 3. `to_ullong()`:轉換為 unsigned long long ## 參考資料 [C++ Bitset and its Application - GeeksforGeeks](https://www.geeksforgeeks.org/cpp/cpp-bitset-and-its-application/) [C++ std::bitset 用法與範例 | ShengYu Talk](https://shengyu7697.github.io/std-bitset/) [C++ 容器类 `<bitset>` | 菜鸟教程](https://www.runoob.com/cplusplus/cpp-libs-bitset.html)