Effective STL:雜記(一)

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 函數來判斷。 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章