17.2 BITSET類型
标準庫定義了
bitset
類,使得位運算的使用更為容易,并且能夠處理超過最長整型類型大小的位集合。
bitset
類定義在頭檔案
bitset
中。
17.2.1 定義和初始化bitset
bitset
類是一個類模闆,它類似
array
類,具有固定的大小。當我們定義一個
bitset
時,需要聲明它包含多少個二進制位:
這條語句定義
bitvec
為一個包含 32 位的
bitset
。就像
vector
包含未命名的元素一樣,
bitset
中的二進制位也是未命名的,我們通過為止通路它們。二進制位的位置是從
開始編号的。是以,
bitvec
包含編号從
到
31
的 32個二進制位。編号從
開始的二進制位被稱為 低位(low-order),編号到
31
結束的二進制位被稱為 高位(high-orderer)。
表17.2:初始化 bitset
的方法
bitset
-
bitset<n> b;
有b
位;每一位均為 。此構造函數是一個n
。constexpr
-
bitset<n> b(u);
是b
值unsigned logn long
的低u
位的拷貝。如果n
大于n
的大小,則unsigned long long
中超出b
的高位被置位 。此構造函數是一個unsigned long long
。constexpr
-
bitset<n> b(s, pos, m, zero, one);
是b
從位置string s
開始pos
個字元的拷貝。m
隻能包含字元 zero 或 one;如果s
包含任何其他字元,構造函數會抛出s
異常。字元在invalid_argument
中分别儲存為 zero 和 one。b
預設為 ,pos
預設為m
,zero 預設為string::npos
,one 預設為'0'
。'1'
-
與上一個構造函數相同,但從bitset<n> b(cp, pos, m, zeor, one);
指向的字元數組中拷貝字元。如果未提供cp
,則m
必須指向一個 C 風格字元串。如果提供了cp
,則從m
開始必須至少有cp
個 zero 或 one 字元。m
用 unsigned
值初始化 bitset
unsigned
bitset
- 當使用一個整型值來初始化
時,此值将被轉換為bitset
類型并被當做位模式來處理。unsigned long long
中的二進制位将是此模式的一個副本。bitset
- 如果
的大小大于一個bitset
中的二進制位數,則剩餘的高位被置為 。unsigned long long
- 如果
的大小小于一個bitset
中的二進制位數,則隻使用給定值中的低位,超出unsigned long long
大小的 高位被丢棄。bitset
// bitvec1 比初始值小;初始值中的高位被丢棄
bitset<13> bitvec1(0xbeef);
// 原本的二進制位序列為 1011111011101111
// 高位被丢棄後的二進制位序列 1111011101111
// bitvec2 比初始值小;它的高位被置為 0
bitset<20> bitvec2(0xbeef);
// 原本的二進制位序列為 1011111011101111
// 高位被置為0後的二進制位序列 00001011111011101111
// 在 64 位機器中,long long 0ULL 是 64 個 0 比特,是以 ~0ULL 是 64 個 1
bitset<128> bitvec3(~0ULL); // 0~63位為 1;63~127位為 0
從一個 string
初始化 bitset
string
bitset
我們可以從一個
string
或一個
字元數組指針
來初始化
bitset
。
兩種情況下,字元都直接表示位模式。
當我們使用字元串表示數時,字元串中下标最小的字元對應高位,反之亦然:
如果
string
包含的字元數比
bitset
少,則
bitset
的高位被置為
。
Note:
string
的下标編号習慣于
bitset
恰好相反:
string
中下标最大的字元(最右字元)用來初始化
bitset
中的低位(下标為
的二進制位)。當使用一個
string
初始化一個
bitset
時,要記住這個差别。
我們不必使用整個
string
來作為
bitset
的初始值,可以隻用一個子串作為初始值;
string str("1111111000000011001101");
bitset<32> bitvec5(str, 5, 4); // 從 str[5] 開始的四個二進制位,1100
bitset<32> bitvec6(str, str.size() - 4); // 使用最後四個字元(字元數組指針)
分析:
-
用bitvec5
中從str
開始的長度為str[5]
的子串進行初始化。與往常一樣,子串的最右字元表示最低位。是以,4
中第bitvec5
位到第 位被設定為3
,剩餘位被設定為 。1100
- 傳遞給
的初始值是一個bitvec6
和一個開始位置,是以string
用bitvec6
中倒數第四個字元開始的子串進行初始化。str
中剩餘二進制位被初始化為 。bitvec6
![](https://img.laitimes.com/img/_0nNw4CM6IyYiwiM6ICdiwiIyVGduV2YfNWawNCM38FdsYkRGZkRG9lcvx2bjxiNx8VZ6l2cs0TPB1UNZRVTx0keNBDOsJGcohVYsR2MMBjVtJWd0ckW65UbM5WOHJWa5kHT20ESjBjUIF2X0hXZ0xCMx81dvRWYoNHLrdEZwZ1Rh5WNXp1bwNjW1ZUba9VZwlHdssmch1mclRXY39CXldWYtlWPzNXZj9mcw1ycz9WL49zZuBnL3IDN1MTO1kDM1ATMwEjMwIzLc52YucWbp5GZzNmLn9Gbi1yZtl2Lc9CX6MHc0RHaiojIsJye.png)
練習 17.9:解釋下列每個
bitset
對象所包含的位模式:
(a)
bitset<64> bitvec(32);
bitvec
為
32
位,第
5
位 為
1
,剩餘位為
。// 疑問:這裡不确定是
32
位 還是
64
位
(b)
bitset<32> bv(1010101);
bv
為 32 位,0、2、4、6 這 4 位為
1
,剩餘位為
。
©
string bstr; cin >> bstr; bitset<8>bv(bstr);
bv
為
8
位,用
bstr
來對其進行初始化。若讀入的字元串不是單純的二進制字元串,程式會抛出
invalid_argument
異常。
17.2.2 bitset操作
表17.3:
bitset
操作
| 中是否存在置位的二進制位 |
---|---|
| 中所有位都置位 了嗎 |
| 中不存在置位的二進制位嗎 |
| 中置位的位數 |
| 中置位的位數 |
| 一個 函數,傳回 中的位數 |
| 若 位置的位是置位的,則傳回 ,否則傳回 |
| 将位置 處的位設定為 值 。 預設為 。 |
| 如果未傳遞實參,則将 中所有位置位 |
| 将位置 處的位複位或将 b 中所有位複位 |
| |
| 改變位置 處的位的狀态或改變 中每一位的狀态 |
| 通路 中位置 處的位;如果 是 的,則當該位置位時 傳回一個 值 ,否則傳回 |
| 傳回一個 或一個 值,其位模式與 相同。如果 中位模式不能放入指定的結果類型,則抛出一個 異常 |
| |
| 傳回一個 ,表示 中的位模式。 和 的預設值分别為 和 1,用來表示 中的 和 |
| 将 中二進制位列印為字元 或 ,列印到流 |
| 從 讀取字元存入 。當下一個字元不是 或 時,或是已經讀入 個位時,讀取過程停止 |
bitset<32> bitvec(1U); // 32 位;低位為 1,剩餘位為 0
bool is_set = bitvec.any(); // true,因為有1位置位
bool is_not_set = bitvec.none(); // false,因為有1位置位
bool all_set = bitvec.all(); // false,因為隻有1位置位
size_t onBits = bitvec.count(); // 傳回 1
size_t sz = bitvec.size(); // 傳回 32
bitvec.flip(); // 翻轉 bitvec 中的所有位
bitvec.reset(); // 将所有位複位
bitvec.set(); // 将所有位置位
- 當
對象的一個或多個位置位(即,等于 1)時,操作bitset
傳回any
。當所有位置位時,操作true
傳回all
。true
- 當所有位複位時,
傳回none
。true
- 操作
和count
傳回size
類型的值,分别表示對象中置位的位數或總位數。函數size_t
是一個size
函數constexpr
成員
flip
,
set
,
reset
及
test
允許我們讀寫指定位置的位:
bitvec.flip(0); // 翻轉第一位
bitvec.set(bitvec.size() - 1); // 置位最後一位
bitvec.set(0, 0); // 複位第一位
bitvec.reset(i); // 複位第 i 位
bitvec.test(0); // 傳回 false,因為第一位是複位的
下标運算符對
const
屬性進行了重載。
const
版本的下标運算符在指定位置位時傳回
true
,否則傳回
false
。非
const
版本傳回
bitset
定義的一個特殊類型,它允許我們操縱指定位的值:
bitvec[0] = 0; // 将第一位複位
bitvec[31] = bitvec[0]; // 将最後一位設定為與第一位一樣
bitvec[0].flip(); // 翻轉第一位
~bitvec[0]; // 等價操作,也是翻轉第一位
bool b = bitvec[0]; // 将 bitvec[0] 的值轉換為 bool 類型
提取 bitset
的值
bitset
沒看懂 P644
bitset
的 IO 運算符
bitset
輸入運算符從一個輸入流讀取字元,儲存到一個臨時的
string
對象中。
直到讀取的字元數達到對應
bitset
的大小時,
或是遇到不是
1
或
的字元時,
或是遇到檔案尾或輸入錯誤時,讀取過程才停止。
随即用臨時
string
對象來初始化
bitset
。
如果讀取的字元數小于
bitset
的大小,則與往常一樣,高位将被置位
。
// 輸出運算符列印一個 bitset 對象的位模式
bitset<16> bits;
cin >> bits; // 從 cin 讀取最多 16 個 0 或 1
cout << "bits: " << bits << endl; // 列印剛剛讀取的内容
使用 bitset
bitset
為了說明如何使用 bitset,用 bitset 代替 unsigned long 表示 30 個學生的測驗結果——“通過/失敗”:
bool status;
// 使用位運算符的版本
unsigned long quizA = 0; // 此值被當做位集合使用
quizA |= 1UL << 27; // 指出第 27 個學生通過了測驗
status = quizA & (1UL << 27); // 檢查第 27 個學生是否ton過了測驗
quizA &= ~(1UL << 27); // 第 27 個學生未通過測驗
// 使用标準庫類 bitset 完成等價的工作
bitset<30> quizB; // 每個學生配置設定一位,所有為都被初始化為 0
quizB.set(27); // 指出第 27 個學生通過了測驗
status = quizB[27]; // 檢查第 27 個學生是否通過了測驗
quizB.reset(27); // 第 27 個學生未通過測驗
練習17.10:使用序列 1、2、3、5、8、13、21 初始化一個 bitset,将這些位置置位。對另一個 bitset 進行預設初始化,并編寫一小段程式将其恰當的位置位。
/*練習17.10:使用序列 1、2、3、5、8、13、21 初始化一個 bitset,将這些位置置位。
對另一個 bitset 進行預設初始化,并編寫一小段程式将其恰當的位置位。*/
#include <iostream>
#include <bitset>
using namespace std;
int main(int argc, char **argv)
{
unsigned bp = 2 | (1 << 2) | (1 << 5) | (1 << 8) | (1 << 13) | (1 << 21);
bitset<32> bv(bp);
cout << bv << endl;
bitset<32> bv1;
bv1.set(1); bv1.set(2); bv1.set(3); bv1.set(5);
bv1.set(8); bv1.set(13); bv1.set(21);
cout << bv1 << endl;
return 0;
}
練習17.11:定義一個資料結構,包含一個整型對象,記錄一個包含10個問題的真假測驗的解答。如果測驗包含100道題,你需要對資料結構做出什麼改變(如果需要的話)。
【出題思路】 本題練習
bitset
的定義和使用。
【解答】 如果使用整數儲存測驗解答,那麼對于
10
個問題的測驗,隻需一個短整型對象即可。
如果改為
100
道題,則需
4
個
32
位整數或是
2
個
64
位整數。而且修改的并不僅僅是資料結構,所有對整型數進行操作來修改解答和評分的代碼都要相應進行修改,工作量很大。
采用
bitset
則有很明顯的優勢,當題目數改變時,我們隻需改變
bitset
的規模,而操作
bitset
來完成改答案、評分的代碼則隻需進行很小的修改。
最佳的方式是定義一個類模闆,它有一個模闆參數表示題目數,有一個
bitset
成員儲存解答,然後定義一些成員函數來完成改答案、評分等操作。當題目數發生變化,我們隻需執行個體化一個新版本即可,其他代碼均無須改動。
練習17.12:使用前一題中的資料結構,編寫一個函數,它接受一個問題編号和一個表示真/假解答的值,函數根據這兩個參數更新測驗的解答。
練習17.13:編寫一個整型對象.包含真/假測驗的正确答案.使用它來為前兩題中的資料結構生成測驗成績。
【出題思路】本題練習
bitset
的定義和使用。
/**
練習17.12:使用前一題中的資料結構,編寫一個函數,它接受一個問題編号和一個表示真/假解答的值,函數根據這兩個參數更新測驗的解答。
練習17.13:編寫一個整型對象.包含真/假測驗的正确答案.使用它來為前兩題中的資料結構生成測驗成績。
*/
#include <iostream>
#include <bitset>
using namespace std;
template <size_t N>
class exam {
public:
exam() : s() {}
size_t get_size() { return N; }
void set_solution(size_t n, bool b) { s.set(n, b); }
bitset<N> get_solution() const { return s; }
size_t score (const bitset<N> &a);
private:
bitset<N> s;
};
template <size_t N>
size_t exam<N>::score(const bitset<N> &a)
{
size_t ret = 0;
for (size_t i = 0; i < N; i++)
if (s[i] == a[i])
ret++;
return ret;
}
int main (int argc, char **argv)
{
exam<80> e;
e.set_solution(0, 1);
e.set_solution(79, 1);
cout << e.get_solution() << endl;
bitset<80> a;
cout << e.get_size() << " problems, you've got " << e.score(a) << " points." << endl;
return 0;
}
/* Output:
10000000000000000000000000000000000000000000000000000000000000000000000000000001
80 problems, you've got 78 points.
*/