STL簡介
STL(Standard Template Library),即標準模板庫。該庫提供了常用的數據結構和算法。
STL三種基本組件:
1、容器(container):容器是容納、包含一組元素的對象。容器類庫包括7種基本容器:向量(vector)、雙端隊列(deque)、列表(list)、集合(set)、多重集合(multiset)、映射(map)和多重映射(multimap)。其中容器又分爲順序容器和關聯容器。
順序容器:將一組具有相同的類型的元素已嚴格的線性形式組織起來(vector、deque、list)。
關聯容器:具有根據一組索引來快速提取元素的能力(set、,multiset、map、multimsp)。
2、迭代器(iterator):迭代器提供了訪問容器種每個元素的方法。對迭代器可以使用“++”運算符來獲得指向下一個元素的迭代器,可以使用“*”運算符訪問一個迭代器所指向的元素,也可以使用“->”運算符來訪問類的成員,和指針的使用比較相似。指針本身也是一種迭代器,迭代器就是泛化的指針。
3、算法(algorithm):STL種包含七十多種算法,包括查找、排序、消除、計數等算法。
除了上面的三種基本組件,STL還包括三個輔助組件。
1、函數對象(function object):函數對象又被稱爲“仿函數”,可以像使用函數那樣去使用它。重載“()”運算符的類的對象都可以被當作函數對象使用,函數對象是泛化的函數。用來擴展算法。
2、適配器(adapter):用於爲已有對象提供新的接口的對象,適配器本身一般並不提供新的功能,只是爲了改變對象的接口而存在的。
3、分配器(allocator):負責空間的配置和管理。
一、STL容器的底層實現和適用的場景
(1)vector(向量)
vector的底層實現爲數組,和一般的數組不同的是,一般的數組在需要在定義時指定的數組的大小,一旦定義好之後數組的大小就不能發生改變,所以需要程序員在定義時,就確定數組的大小。而vector的大小是動態的,即可以在適用時自動擴容(只能增大,不能減少,即一旦容量升上去,就不會在降下來。)因爲vector的底層是數組實現的,所以要求內存必須連續。但內存分配時,並不是在原數組的地址上向後擴容,而是會重新選擇一塊空間,將原來的數組拷貝過來(原因是:該數組原來地址的後面不一定有足夠的空間)。所以vector在擴容上就比較浪費時間,所以在使用vector時,最好先指定數組的大小,以避免頻繁的擴容浪費時間。在刪除容器中元素時,和普通的數組的處理方式相同,將從該元素後面的元素全部向前移動一個位置,所以如果vector的元素類型時類時,就會導致對象的構造和析構,就會浪費大量的時間。所以當需要使用vector存儲類時,一般建議使用對象的指針。
總結:適用於數據的頻繁數據隨機訪問,且不需要頻繁的插入和刪除。
(2)list(鏈表)
list的底層實現爲雙向鏈表,和普通的鏈表相同,即插入和刪除的時間複雜度都是O(1),但不支持數據元素的隨機訪問。
總結:適用於數據的頻繁插入和刪除,且不需要頻繁的隨機訪問。
(3)deque(雙向隊列)
deque是一種雙向開口(先入先出,即:頭刪尾插)的連續性空間。所謂的連續性,不過時讓用戶感覺爲連續的,實際上是不連續的。duque的底層採用了“中央控制器”和緩衝區的結合方式,對外造成了整體連續的假象。“中央控制器”實際上就是使用了map。map佔用一小段連續的空間,其中每個元素都是一個指針,用來記錄每個緩衝區的地址,而且緩衝區的大小是固定的,默認爲512b。所以每一塊中存儲的元素個數是相同的。
deque支持[]運算符,支持數據隨機訪問,在隊尾插入和隊頭刪除的效率都是O(1)。
總結:適用於需要隨機存取,而且兩端的數據需要插入和刪除。
(4)set(集合)和 multiset(多重集合)
set的底層的數據結構是採用insert_unique方式插入紅黑樹(自平衡二叉查找樹)
set總結:適用於去除數據中重複的元素的情況。
multiset的底層數據結構是採用insert_equal方式插入的紅黑樹。
(5)map(映射)和multimap(多重映射)
map的底層數據結構和set相同使用的是insert_unique方式插入紅黑樹,和set不同map的元素是鍵值對,鍵值對由鍵值(key)和實值(value)構成的,set的元素的只有一個實值。
map的用法很多:容器內元素頻繁的查找,且數據的下標不一定是整形的情況。
這裏需要注意的map和set內部的採用的結構是紅黑樹,所以會對加入容器的數據自動排序。
(6)hash_set(哈希集合)和hash_multiset(哈希多重集合)
hash_set和hash_multiset的底層實現是哈希表,由於哈希表是通過散列函數對數據進行分配空間的,所以在數據的查找上的速度爲常數級。它和set和multiset的區別是它對存入容器的數據不排序。
(7)hash_map(哈希映射)和hash_multimap(哈希多重映射)
hash_map和hash_multimap的底層實現是哈希表,它和map和multimap的區別也是對容器中的數據不排序。
這裏需要注意一點:現在的c++11規定無序容器的同一命名規則爲unordered_xx,所以hash_set,hash_multimap,hash_map和hash_multimap在現在c++11中的名稱爲:unordered_set,unordered_multiset,unordered_map和unordered_multimap。
二、適配器(adapter)
適配器的底層並沒有具體數據結構實現,而是對其他STL的組件進行的一定的修改和封裝。適配器的優點就在於可以讓程序員選擇最合適的容器來使用。
適配器分爲三類:1、容器適配器 2、迭代器適配器 3、仿函數適配器
1、容器適配器
(1)stack(棧,默認基於deque(雙向隊列)實現)
(2)queue(隊,默認基於deque(雙向隊列)實現)
(3)priority_queue(優先隊列,默認基於vector(向量)實現)
三、分配器(allocaotr)
負責空間配置與管理。從實現的角度來看,配置器是一個實現動態空間配置、空間管理、空間釋放的模板類。
STL分配器將內存管理中的分配和釋放分開,其中內存分配交給alloc::allocate()負責,內存釋放由alloc::dealloccate()負責;對像的構造由::construct()負責,對象的析構::destroy()負責。
同時爲了提高內存管理的效率,減少申請內存造成的內存碎片問題,STL採用了兩級配置器,當分配的內存大於等於128kb時,會使用第一級的空間配置器,在第一級空間適配器中內存是利用malloc()、realloc(),free()函數進行分配和釋放的。當空間小於128k時,採用第二級空間配置器,第二級空間配置器採用了內存池技術,通過空閒鏈表來管理內存。
四、迭代器失效
迭代器失效有兩種表現形式:
(1)無法通過迭代器++,--,操作遍歷整個容器(第一層失效)。
(2)無法通過迭代器存取迭代器所指向的內存(第二層失效)。
因爲迭代器適合容器中的元素綁定的,而是和內存綁定的,所以在刪除容器中元素時,幾乎都會導致迭代器失效,需要重新定位的情況(第一層失效),而對於部分容器來說,插入操作可能也會導致迭代器失效(第二層失效)。
(注意:在使用的erase刪除容器的元素時,erase函數會返回下一個有效的迭代器)
迭代器是失效對於不同的容器有不同的表現形式。
(1)vector、deque
對於這兩種順序容器來說,在刪除元素時會導致當前未知之後的迭代器都會失效。vector和deque中的元素在刪除之後,當前位置之後的元素的位置會向前移動(第一層失效)。而對於插入操作來說,由於vector、deque的存在空間時連續的,所以在插入元素時可能會導致內存重新分配的問題(第二層失效),如果內存沒有重新分配,迭代器就不會失效。
(2)list
list的空間時動態分配的,空間本身就不連續,所以插入一般不會產生迭代器失效問題,在刪除時會導致第一層迭代器失效問題。
(3)map、set
刪除map和set的元素,會導致當前的迭代器失效,但不會影響下一個元素的迭代器。插入也不會導致迭代器失效。
五、map和set又什麼區別,分別是如何實現的?
map和set都是c++的關聯容器,其底層都是紅黑樹(RB-Tree)。由於map和set所開放的各種操作接口,紅黑數也都提供了,索引幾乎所有的map和set的操作行爲,都只是轉調紅黑數的操作行爲。
map和set的區別在於:
(1)map中的元素使key-value(關鍵字-值)對,關鍵字起到索引的作用,值則表示與索引相關聯的數據;set與之相對的就是關鍵字的簡單集合,set中每個元素只包含一個關鍵字。
(2)set的迭代器使const的,不允許修改元素的值;map允許修改value,但不允許修改key。其原因是因爲map和set是根據關鍵字來保證其有序性,如果允許修改key值的,就會破壞底層紅黑樹的結構,而這樣就會導致迭代器失效,所以STL不允許修改關鍵字,而map的數據是k-v鍵值對,是通過k值來索引v的值,所有v的修改,並不會影響到底層紅黑樹的結構,所以STL允許修改map的value值
(3)map支持下標操作,set不支持下表操作。map可以用key做下標,map的下標運算符[]將關鍵字作爲下標去查找。(注意:如果map索引的下標並不存在時,map並不會報錯,而是會在自動添加該關鍵字,併爲該關鍵字對應的value賦予一個默認的值)
六、vector和list的區別?
(1)vector的底層實現是數組;而list的底層實現是雙向鏈表。
(2)vector的內存的空間時連續的;而list的內存空間時不連續的。
(3)vector支持數據的隨機訪問;而list不支持數據的隨機訪問。
(4)vector在插入時如果空間不夠纔會自動申請新的空間的擴容,但容器中數據刪除時不會釋放空間;而list在每次插入和刪除都會申請和釋放空間。
(5)vector在數據的插入或刪除都可能會導致迭代器失效(插入或刪除位置後面額迭代器全部失效);而list的在插入時不會導致迭代器失效,而在刪除時會導致當前迭代器失效。
(6)vector在刪除和插入元素時會導致內存拷貝(插入位置之後的元素需要全部後移或刪除位置的元素需要全部前移);而list不會。
(7)vector適用於對容器中元素進行頻繁的訪問的情況;而list適用於對容器中元素進行頻繁插入和刪除的情況。
七、vector和deque的區別?
(1)vector的底層實現時數組;而deque的底層實現時“中央控制區”和緩衝的結構。
(2)vector的內存空間時連續的;而deque的內存空間是一種假的連續,實際上是不連續的。
(3)vecotr的支持數組的隨機訪問;而deque雖然也是支持使用[]的運算符,但實際上是需要通過中央控制器二次尋址的,效率比vector略低。
(4)vector在空間不夠重新申請新空間策略是先申請一塊比原來空間大的空間(1.5倍或兩倍)將原來的數據拷貝一份到新的空間中,然後將原來的空間的釋放;而deque的策略是申請一塊固定大小(默認大小:512bytes)的空間,然後將該空間的地址存放在中央控制器中,並沒有數據拷貝和空間的釋放的過程,所以擴容時deque的效率要高於vector。
(5)vector在對尾部進行插入和刪除的效率時O(1);而deque在尾部和頭部的插入和刪除的效率都是O(1)。
(6)vector適用於對容器中元素進行頻繁的訪問的情況;而deque的適用於元素的頻繁訪問,並且在數據需要在頭部和尾部進行插入和刪除的情況。
八、容器容量和容器大小(capacity和size)
(1)容器容量:容器所能容納元素的個數,通常大於容器的實際存儲元素的個數。
s.capacity();//查看容器容量
s.reserve(len);//擴展當前容器的容量,如果len大於當前容器容量,那麼將會將容器的容量擴展爲len,且不會對新擴展的空間進行初始化;當len小於等於當前容器容量時,什麼也不做。
(2)容器大小:容器中當前存在元素的個數。
s.size();//查看當前容器的中元素的個數
s.resize(len);//設置當前容器的容量,如個len大於當前容器中元素的個數,新增的元素將並初始化爲0。len小於容器的實際大小時,會將len之後的元素的全部清除爲0。
通過情況向容器的容量是大於等於容器的大小的,就像杯子和杯子中的水的關係。一般需要容器的大小不會引起內存的重新分配(向杯子中加水),但修改容器的容量就會導致容器內存的重新分配(原來的杯子太小了,需要換新的杯子)。
#include <iostream>
#include <vector>
using namespace std;
int main()
{
vector<int> intArr;
intArr.push_back(1);
intArr.push_back(2);
intArr.push_back(3);
intArr.push_back(4);
intArr.push_back(5);
intArr.push_back(6);
cout << "1、修改前:容器size:" << intArr.size() << ",容器capacity:" << intArr.capacity() << endl;
vector<int>::iterator viter = intArr.begin();
for (; viter != intArr.end(); ++viter)
{
cout << *viter << ",";
}
cout << endl;
intArr.resize(3);
intArr.reserve(3);
cout << "2、修改後、容器size:" << intArr.size() << ",容器capacity:" << intArr.capacity() << endl;
viter = intArr.begin();
for (; viter != intArr.end();++viter)
{
cout << *viter << ",";
}
cout << endl;
intArr.resize(6);
intArr.reserve(10);
cout << "3、修改後、容器size:" << intArr.size() << ",容器capacity:" << intArr.capacity() << endl;
viter = intArr.begin();
for (; viter != intArr.end(); ++viter)
{
cout << *viter << ",";
}
cout << endl;
return 0;
}