1. 避免使用vector<bool>
vector<bool>實際上並不能算是一個STL容器,實際上也並不存儲bool。因爲一個對象要成爲STL容器,就必須滿足C++標準的第23.1節列出的所有條件,其中一個條件是,如果 c 是包含對象T的容器,而且 c 支持operator[],則必須能夠編譯下面代碼:
T *p = &c[0];
也就是說容器中應該是存儲對象 T,這樣子 operator[] 才能返回容器中實際存儲的對象,你也可以通過取它的地址得到一個指向該對象的指針。(這裏需要假定 T 沒有用非常規的方式對 operator & 做重載。)但是對於vector<bool>,底層是用類似於位圖的方式存儲的。
對於vector<bool>的operator[],返回的是一個代理對象(proxy project),這個對象表現得像是一個指向單位的引用。爲這個代理對象添加一個隱式轉向bool的函數就可以讓 operator[] 返回一個bool值。所以對於 operator[] 返回值做 operator&,得到的並不是一個bool指針,所以將其賦給bool指針顯然是編譯不過的。
#include <iostream>
#include <map
#include <vector>
using namespace std;
int main() {
vector<bool> vb;
vb.push_back(false);
vb.push_back(true);
vb.push_back(false);
// bool *pb = &vb[0];
bool pb1 = vb[1];
cout << pb1 << endl;
cout << *vb.begin() << endl;
return 0;
}
上面的代碼輸出分別爲1和0。但是如果去掉被註釋語句的註釋符號,則編譯是無法通過的。這裏vb[1]和*vb.begin()能正常工作應該是對返回值進行了特殊處理。詳細的情況需要分析源代碼了。
可以替代vector<bool>的有deque<bool>和bitset,deque<bool>就的確是存儲bool的,bitset不是STL容器,是標準C++庫的一部分,但是它的大小在編譯時就確定了。
2. swap技巧除去多餘的容量
我們知道,隨着vector元素的增加,存在一個翻倍擴容的操作,此時會導致vector的長度越來越大,即使我們調用pop_back將元素彈出,其容量也不會變小。最終我們可能需要用resize操作來減少容量。比起resize操作,我們可以用更簡單的方式來除去多餘的容量,那就是用swap函數來交換兩個vector的內容。
#include <iostream>
#include <vector>
using namespace std;
int main() {
vector<int> vi;
for (int i = 0; i < 10; ++i)
vi.push_back(i);
vi.pop_back();
cout << vi.capacity() << endl; // 輸出 16
vector<int>(vi).swap(vi);
cout << vi.capacity() << endl; // 輸出 9
return 0;
}
上面的代碼分別輸出 16 和 9。最主要的語句是“vector<int>(vi).swap(vi)”,這裏用 vi 的內容來初始化一個臨時vector<int>臨時變量,則該臨時變量將只含有 vi 中實際存在的元素,沒有被設置的容量內容並不會被賦值過去,即該臨時變量只含有 9 個元素。然後再將其與 vi 交換內容,這個時候 vi 中便僅有9個元素了,不存在多出來的容量。
3. map中operater[]與insert
map::operator[] 的設計目的是爲了提供“添加和更新”(add or update)的功能。operator[] 會返回一個引用,它指向與 k 相關聯的值對象。然後 v 被賦給該引用(operator[] 返回那個引用)所指向的對象。如果鍵 k 已經有了先關聯的值,則該值被更新,但問題在於如果 k 還沒有在映射表中,它會使用值類型的默認構造函數創建一個新的對象,然後 operator[] 就能返回一個指向該新對象的引用了。
也就是說,如果鍵 k 對應的 v 不存在,operator[] 會導致新的對象被創建,不管有沒有新的 v 被賦給。
#include <iostream>
#include <map>
using namespace std;
int main() {
map<int, int> m;
cout << m.size() << endl; // 輸出:0
m[10];
cout << m.size() << endl; // 輸出:1
if (m.find(10) != m.end()) {
cout << "has 10" << endl; // 輸出:has 10
}
return 0;
}
通過上面的輸出,可以看到雖然在執行了 m[10] 後,m 中新增了一個對象。
而對於 operator[] 與 insert ,《Effective STL》中講到對於新增對象,insert函數的效率要高,而更新操作,則是 operator[] 效率要高。書中有講到一些例子,還沒完全消化好,所以直接不分析了。
通過上面的例子,我們也可以知道不能通過 operator[] 去判斷某個鍵是否存在對應的值,而是應該用 find 函數來判斷。