前面章節中,已經詳細介紹了 vector<T> 容器的功能和用法。特別需要提醒的是,在使用 vector 容器時,要盡量避免使用該容器儲存 bool 型別的元素,即避免使用 vector<bool>。
具體來講,不推薦使用 vector<bool> 的原因有以下 2 個:
-
嚴格意義上講,vector<bool> 並不是一個 STL 容器;
-
vector<bool> 底層儲存的並不是 bool 型別值。
讀者可能會感到有些困惑,別著急,繼續往下讀。
vector<bool>不是容器
值得一提的是,對於是否為 STL 容器,C++ 標準庫中有明確的判斷條件,其中一個條件是:如果 cont 是包含物件 T 的 STL 容器,且該容器中過載了 [ ] 運算子(即支援 operator[]),則以下程式碼必須能夠被編譯:
T *p = &cont[0];
此行程式碼的含義是,借助 operator[ ] 獲取一個 cont<T> 容器中儲存的 T 物件,同時將這個物件的地址賦予給一個 T 型別的指標。
這就意味著,如果 vector<bool> 是一個 STL 容器,則下面這段程式碼是可以通過編譯的:
//建立一個 vector<bool> 容器
vector<bool>cont{0,1};
//試圖將指標 p 指向 cont 容器中第一個元素
bool *p = &cont[0];
但不幸的是,此段程式碼不能通過編譯。原因在於 vector<bool> 底層採用了獨特的儲存機制。
實際上,為了節省空間,vector<bool> 底層在儲存各個 bool 型別值時,每個 bool 值都只使用一個位元位(二進位制位)來儲存。也就是說在 vector<bool> 底層,一個位元組可以儲存 8 個 bool 型別值。在這種儲存機制的影響下,operator[ ] 勢必就需要返回一個指向單個位元位的參照,但顯然這樣的參照是不存在的。
C++ 標準中解決這個問題的方案是,令 operator[] 返回一個代理物件(proxy object)。有關代理物件,由於不是本節重點,這裡不再做描述,有興趣的讀者可自行查閱相關資料。
同樣對於指標來說,其指向的最小單位是位元組,無法另其指向單個位元位。綜上所述可以得出一個結論,即上面第 2 行程式碼中,用 = 賦值號連線 bool *p 和 &cont[0] 是矛盾的。
由於 vector<bool> 並不完全滿足 C++ 標準中對容器的要求,所以嚴格意義上來說它並不是一個 STL 容器。可能有讀者會問,既然 vector<bool> 不完全是一個容器,為什麼還會出現在 C++ 標準中呢?
這和一個雄心勃勃的試驗有關,還要從前面提到的代理物件開始說起。由於代理物件在 C++ 軟體開發中很受歡迎,引起了 C++ 標準委員會的注意,他們決定以開發 vector<bool> 作為一個樣例,來演示 STL 中的容器如何通過代理物件來存取元素,這樣當使用者想自己實現一個基於代理物件的容器時,就會有一個現成的參考模板。
然而開發人員在實現 vector<bool> 的過程中發現,既要建立一個基於代理物件的容器,同時還要求該容器滿足 C++ 標準中對容器的所有要求,是不可能的。由於種種原因,這個試驗最終失敗了,但是他們所做過的嘗試(即開發失敗的 vector<bool>)遺留在了 C++ 標準中。
至於將 vector<bool> 遺留到 C++ 標準中,是無心之作,還是有意為之,這都無關緊要,重要的是讓讀者明白,vector<bool> 不完全滿足 C++ 標準中對容器的要求,盡量避免在實際場景中使用它!
如何避免使用vector<bool>
那麼,如果在實際場景中需要使用 vector<bool> 這樣的儲存結構,該怎麼辦呢?很簡單,可以選擇使用 deque<bool> 或者 bitset 來替代 vector<bool>。
要知道,deque 容器幾乎具有 vecotr 容器全部的功能(擁有的成員方法也僅差 reserve() 和 capacity()),而且更重要的是,deque 容器可以正常儲存 bool 型別元素。
有關 deque 容器的具體用法,後續章節會做詳細講解。
還可以考慮用 bitset 代替 vector<bool>,其本質是一個模板類,可以看做是一種類似陣列的儲存結構。和後者一樣,bitset 只能用來儲存 bool 型別值,且底層儲存機制也採用的是用一個位元位來儲存一個 bool 值。
和 vector 容器不同的是,bitset 的大小在一開始就確定了,因此不支援插入和刪除元素;另外 bitset 不是容器,所以不支援使用疊代器。
有關 bitset 的用法,感興趣的讀者可查閱 C++ 官方提供的 bitset使用手冊。