本周課程主要內容分為:STL體系結構基礎介紹、容器之分類與各種測試和分配器之測試,其中容器之分類與各種測試是本周課程的重點難點。
1、STL體系結構基礎介紹
STL:標準模板庫,屬于標準庫的一部分,包含6大部件,彼此可以組合套用。
1、容器(Containers):各種數據結構,如:vector、list、deque、set、map。用來存放數據。從實現的角度來看,STL容器是一種class template。
2、算法(algorithms):各種常用算法,如:sort、search、copy、erase。從實現的角度來看,STL算法是一種function template。
3、迭代器(iterators):容器與算法之間的膠合劑,是所謂的“泛型指針”。共有五種類型,以及其他衍生變化。從實現的角度來看,迭代器是一種將operator*、operator->、operator++、operator- -等指針相關操作進行重載的class template。所有STL容器都有自己專屬的迭代器,只有容器本身才知道如何遍歷自己的元素。原生指針(native pointer)也是一種迭代器。
4、仿函數(functors):行為類似函數,可作為算法的某種策略(policy)。從實現的角度來看,仿函數是一種重載了operator()的class或class template。一般的函數指針也可視為狹義的仿函數。
5、配接器(adapters);一種用來修飾容器、仿函數、迭代器接口的東西。例如:STL提供的queue和stack,雖然看似容器,但其實只能算是一種容器配接器,因為它們的底部完全借助deque,所有操作都由底層的deque供應。改變functors接口者,稱為function adapter;改變Container接口者,稱為container adapter;改變iterator接口者,稱為iterator adapter。
6、配置器(allocators):負責空間配置與管理。從實現的角度來看,配置器是一個實現了動態空間配置、空間管理、空間釋放的class template。
STL6大部件之間的關系:container(容器)通過allocator(配置器)取得數據儲存空間,algorithm(算法)通過iterator(迭代器)存取container(容器)內容,functor(仿函數)可以協助algorithm(算法)完成不同的策略變化,adapter(配接器)可以修飾或套接functor(仿函數)。
2、容器之分類與各種測試
2.1 課堂知識
(1)容器結構與分類
STL容器主要分為序列式和關聯式。
(2)各類容器
主要包括array、vector、list、deque、set、map、multiset、multimap、unordered_set、unordered_map、unordered_multiset、unordered_multimap等容器,介紹各種容器的測試程序,測試內容包括元素大小、容器內存大小、查找元素所需時間等。
(3)總結
將上述內容進行總結如下:
a、序列式容器放入元素用c.push_back()或者c.push_front(),關聯式容器放入元素用c.insert();
b、對于元素擴充:容器array不能擴充,容器vector2倍擴充,容器list依次擴充,容器deque每次擴充一個buffet;
c、兩種查找方法:全局查找::find(),在全局中循序查找,花費時間較短;調用容器sort再查找,花費時間較長,因為大部分時間花在sort上;
d、clock()得到運行到此刻的時間,兩次用韻該函數可以得到某一步或某幾步程序所花費的時間;
e、容器set和map中放入元素不能重復,但是由于set中key就是value,map中key和value組成一個pair,因此1000000個元素中value是重復的,key不重復,導致set.size不是1000000,而map.size是1000000;
f、編程小技巧:
2.2 課后補充學習
1、順序性容器
(1)vector
vector是一種動態數組,在內存中具有連續的存儲空間,支持快速隨機訪問。由于具有連續的存儲空間,所以在插入和刪除操作方面,效率比較慢。vector有多個構造函數,默認的構造函數是構造一個初始長度為0的內存空間,且分配的內存空間是以2的倍數動態增長的,即內存空間增長是按照20,21,22,23.....增長的,在push_back的過程中,若發現分配的內存空間不足,則重新分配一段連續的內存空間,其大小是現在連續空間的2倍,再將原先空間中的元素復制到新的空間中,性能消耗比較大,尤其是當元素是非內部數據時(非內部數據往往構造及拷貝構造函數相當復雜)。vector的另一個常見的問題就是clear操作。clear函數只是把vector的size清為零,但vector中的元素在內存中并沒有消除,所以在使用vector的過程中會發現內存消耗會越來越多,導致內存泄露,現在經常用的方法是swap函數來進行解決:? vector V;
V.push_back(1); V.push_back(2);V.push_back(1); V.push_back(2);
vector().swap(V); 或者?V.swap(vector());
利用swap函數,和臨時對象交換,使V對象的內存為臨時對象的內存,而臨時對象的內存為V對象的內存。交換以后,臨時對象消失,釋放內存。
(2)deque
deque和vector類似,支持快速隨機訪問。二者最大的區別在于,vector只能在末端插入數據,而deque支持雙端插入數據。deque的內存空間分布是小片的連續,小片間用鏈表相連,實際上內部有一個map的指針。deque空間的重新分配要比vector快,重新分配空間后,原有的元素是不需要拷貝的。
(3)list
list是一個雙向鏈表,因此它的內存空間是可以不連續的,通過指針來進行數據的訪問,這使list的隨機存儲變得非常低效,因此list沒有提供[]操作符的重載。但list可以很好地支持任意地方的插入和刪除,只需移動相應的指針即可。
(4)在實際使用時,如何選擇這三個容器中哪一個,應根據你的需要而定,一般應遵循下面的原則:
1) 如果你需要高效的隨即存取,而不在乎插入和刪除的效率,使用vector;
2) 如果你需要大量的插入和刪除,而不關心隨即存取,則應使用list;
3) 如果你需要隨即存取,而且關心兩端數據的插入和刪除,則應使用deque。
2、關聯容器
(1)map
map是一種關聯容器,該容器用唯一的關鍵字來映射相應的值,即具有key-value功能。map內部自建一棵紅黑樹(一種自平衡二叉樹),這棵樹具有數據自動排序的功能,所以在map內部所有的數據都是有序的,以二叉樹的形式進行組織。這是map的模板:
template < class Key, class T, class Compare= less,?class Allocator=allocator< pair > > class map;
從模板中我們可以看出,再構造map時,是按照一定的順序進行的。map的插入和刪除效率比其他序列的容器高,因為對關聯容器來說,不需要做內存的拷貝和移動,只是指針的移動。由于map的每個數據對應紅黑樹上的一個節點,這個節點在不保存你的數據時,是占用16個字節的,一個父節點指針,左右孩子指針,還有一個枚舉值(標示紅黑色),所以map的其中的一個缺點就是比較占用內存空間。
(2)set
set也是一種關聯性容器,它同map一樣,底層使用紅黑樹實現,插入刪除操作時僅僅移動指針即可,不涉及內存的移動和拷貝,所以效率比較高。set中的元素都是唯一的,而且默認情況下會對元素進行升序排列。所以在set中,不能直接改變元素值,因為那樣會打亂原本正確的順序,要改變元素值必須先刪除舊元素,再插入新元素。不提供直接存取元素的任何操作函數,只能通過迭代器進行間接存取。set模板原型:
template , class Alloc=STL_DEFAULT_ALLOCATOR(Key) > class set;
set支持集合的交(set_intersection)、差(set_difference)、并(set_union)及對稱差(set_symmetric_difference) 等一些集合上的操作。
3、容器適配器
(1)queue
queue是一個隊列,實現先進先出功能,queue不是標準的STL容器,卻以標準的STL容器為基礎。queue是在deque的基礎上封裝的。之所以選擇deque而不選擇vector是因為deque在刪除元素的時候釋放空間,同時在重新申請空間的時候無需拷貝所有元素。其模板為:
template < TYPENAME _Sequence="deque<_TP" typeneam _Tp,> > class queue;
(2)stack
stack是實現先進后出的功能,和queue一樣,也是內部封裝了deque,這也是為啥稱為容器適配器的原因吧(純屬猜測)。自己不直接維護被控序列的模板類,而是它存儲的容器對象來為它實現所有的功能。stack的源代碼原理和實現方式均跟queue相同。
3、分配器之測試
分配器一般搭配在容器中自動使用,可以直接使用分配器,但是沒有必要,因為會造成多方面的負擔。
做自己的分配器的一些注意事項:
1.需要有自己的一些類型定義比如pointer
2.需要做自己的allocate和deallocate
3.一定要有rebind實現,如果不理解,請看一下標準庫里面的list,set等的實現,很容易的。