STL容器的實現原理 

 
STL共有六大元件
 1、容器。2、演算法。3、迭代器。4、仿函式。6、介面卡。

STL容器的實現原理

STL來管理資料十分方便,省去了我們自己構建資料結構的時間.其實,STL的實現也是基於我們常見的資料結構.


序列式容器:
vector-陣列,元素不夠時再重新分配記憶體,拷貝原來陣列的元素到新分配的陣列中。
list-單鏈表。
deque-分配中央控制器map(並非map容器),map記錄著一系列的固定長度的陣列的地址.記住這個map僅僅儲存的是陣列的地址,真正的資料在陣列中存放著.deque先從map中央的位置(因為雙向佇列,前後都可以插入元素)找到一個數組地址,向該陣列中放入資料,陣列不夠時繼續在map中找空閒的陣列來存資料。當map也不夠時重新分配記憶體當作新的map,把原來map中的內容copy的新map中。所以使用deque的複雜度要大於vector,儘量使用vector。

stack-基於deque。

queue-基於deque。
heap-完全二叉樹,使用最大堆排序,以陣列(vector)的形式存放。
priority_queue-基於heap。
slist-雙向連結串列。

關聯式容器:
set,map,multiset,multimap-基於紅黑樹(RB-tree),一種加上了額外平衡條件的二叉搜尋樹。


hash table-散列表。將待存資料的key經過對映函式變成一個數組(一般是vector)的索引,例如:資料的key%陣列的大小=陣列的索引(一般文字通過演算法也可以轉換為數字),然後將資料當作此索引的陣列元素。有些資料的key經過演算法的轉換可能是同一個陣列的索引值(碰撞問題,可以用線性探測,二次探測來解決),STL是用開鏈的方法來解決的,每一個數組的元素維護一個list,他把相同索引值的資料存入一個list,這樣當list比較短時執行刪除,插入,搜尋等演算法比較快。


hash_map,hash_set,hash_multiset,hash_multimap-基於hash table。

綜上所述大家應該知道了什麼情況下該使用哪一個STL容器更合適,可以在適當時候避免使用一些影響效率的STL容器.


這裡我們不涉及容器的基本操作之類,只是要討論一下各個容器其各自的特點STL中的常用容器包括:順序性容器(vector、deque、list)、關聯容器(map、set)、容器介面卡(queue、stac)

STL是C/C++開發中一個非常重要的模板,而其中定義的各種容器也是非常方便我們大家使用。下面,我們就淺談某些常用的容器。這裡我們不涉及容器的基本操作之類,只是要討論一下各個容器其各自的特點。STL中的常用容器包括:順序性容器(vector、deque、list)、關聯容器(map、set)、容器介面卡(queue、stac)。

1、順序性容器

(1)vector
vector是一種動態陣列,在記憶體中具有連續的儲存空間,支援快速隨機訪問。由於具有連續的儲存空間,所以在插入和刪除操作方面,效率比較慢。vector有多個建構函式,預設的建構函式是構造一個初始長度為0的記憶體空間,且分配的記憶體空間是以2的倍數動態增長的,即記憶體空間增長是按照20,21,22,23.....增長的,在push_back的過程中,若發現分配的記憶體空間不足,則重新分配一段連續的記憶體空間,其大小是現在連續空間的2倍,再將原先空間中的元素複製到新的空間中,效能消耗比較大,尤其是當元素是非內部資料時(非內部資料往往構造及拷貝建構函式相當複雜)。vector的另一個常見的問題就是clear操作。clear函式只是把vector的size清為零,但vector中的元素在記憶體中並沒有消除,所以在使用vector的過程中會發現記憶體消耗會越來越多,導致記憶體洩露,現在經常用的方法是swap函式來進行解決:  

vector<int> V;V.push_back(1); V.push_back(2);V.push_back(1); V.push_back(2);
vector<int>().swap(V); 或者 V.swap(vector<int>());

利用swap函式,和臨時物件交換,使V物件的記憶體為臨時物件的記憶體,而臨時物件的記憶體為V物件的記憶體。交換以後,臨時物件消失,釋放記憶體。

-------------------

STL: 知道vector嗎,一個自然數陣列,要刪除其中的奇數,寫出程式碼。這個需要注意的是erase刪除結點,則該結點的iterator會失效,同時erase會返回下一個有效迭代器,所以iter++只有在偶數的時候才執行。 繼續問到iter++和++iter的區別,回答前者會產生一個臨時變數,後者的效率更高,如果前面有=的話,得到的值不一樣。 vector是怎麼儲存的,如果讓你實現vector,你怎麼做,首先說了下STL裡面的vector,記憶體如何分配的,建構函式等等。開始用陣列實現vector,然後問一定要寫成類似MS提供的STL裡面的vector,提示說不用,寫個基本框架就可以了,就直接寫vector程式碼,然後寫vector的插入操作,注意vector滿時

-------------------


(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<Key>, class Allocator=allocator< pair<const Key,T> > > class map;

從模板中我們可以看出,再構造map時,是按照一定的順序進行的。map的插入和刪除效率比其他序列的容器高,因為對關聯容器來說,不需要做記憶體的拷貝和移動,只是指標的移動。由於map的每個資料對應紅黑樹上的一個節點,這個節點在不儲存你的資料時,是佔用16個位元組的,一個父節點指標,左右孩子指標,還有一個列舉值(標示紅黑色),所以map的其中的一個缺點就是比較佔用記憶體空間。

map簡介

 map是STL的一個關聯容器associativecontainer)之一,它提供一對一(其中第一個可以稱為關鍵字,每個關鍵字只能在map中出現一次,第二個可能稱為該關鍵字的值)的資料處理能力,由於這個特性,它完成有可能在我們處理一對一資料的時候,在程式設計上提供快速通道。這裡說下map內部資料的組織,map內部自建一顆紅黑樹(一種非嚴格意義上的平衡二叉樹),這顆樹具有對資料自動排序的功能,所以在map內部所有的資料都是有序的。

一個map是一個鍵值對序列,
         即(key ,value)對。它提供基於key的快速檢索能力,在一個map中key值是唯一的。map提供雙向迭代器,即有從前往後的(iterator),也有從後往前的(reverse_iterator)。

map中key值是唯一 

    例子:如果已存在一個鍵值對(編號,使用者名稱):(1001,"jack"),而我們還想插入一個鍵值對(1001,"mike") 執行時會報錯(不是報錯,準確的說是,返回插入不成功!)。而我們又的確想這樣做,即一個鍵對應多個值,幸運的是multimap可是實現這個功能。

map中的型別定義

關聯陣列(associative array)是最有用的使用者定義型別之一,經常內建在語言中用於文字處理等。一個關聯陣列通常也稱為map,有時也稱字典(dictionary),儲存一對值。第一個值稱為key、第二個稱為對映值mapped-value。

  1. // map::begin/end
  2. #include <iostream>
  3. #include <map>
  4. int main ()  
  5. {  
  6.   std::map<char,int> mymap;  
  7.   std::map<char,int>::iterator it;  
  8.   mymap['z'] = 100;  
  9.   mymap['a'] = 200;  
  10.   mymap['z'] = 300;  
  11.   // show content:
  12.   for (std::map<char,int>::iterator it=mymap.begin(); it!=mymap.end(); ++it)  
  13.     std::cout << it->first << " => " << it->second << '\n';  
  14.   return 0;  
  15. }  
// map::begin/end
#include <iostream>
#include <map>

int main ()
{
  std::map<char,int> mymap;
  std::map<char,int>::iterator it;

  mymap['z'] = 100;
  mymap['a'] = 200;
  mymap['z'] = 300;

  // show content:
  for (std::map<char,int>::iterator it=mymap.begin(); it!=mymap.end(); ++it)
    std::cout << it->first << " => " << it->second << '\n';

  return 0;
}

  1. a => 200  
  2. z => 300  
a => 200
z => 300



(2)set
set也是一種關聯性容器,它同map一樣,底層使用紅黑樹實現,插入刪除操作時僅僅移動指標即可,不涉及記憶體的移動和拷貝,所以效率比較高。set中的元素都是唯一的,而且預設情況下會對元素進行升序排列。所以在set中,不能直接改變元素值,因為那樣會打亂原本正確的順序,要改變元素值必須先刪除舊元素,再插入新元素。不提供直接存取元素的任何操作函式,只能通過迭代器進行間接存取。set模板原型:

template <class Key, class Compare=class<Key>, 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相同。

下面是他們的區別:

我們常用到的STL容器有vector、list、deque、map、multimap、set和multiset,它們究竟有何區別,各自的優缺點是什麼,為了更好的揚長避短,提高程式效能,在使用之前需要我們瞭解清楚。

verctor

vector類似於C語言中的陣列,它維護一段連續的記憶體空間,具有固定的起始地址,因而能非常方便地進行隨機存取,即 [] 操作符,但因為它的記憶體區域是連續的,所以在它中間插入或刪除某個元素,需要複製並移動現有的元素。此外,當被插入的記憶體空間不夠時,需要重新申請一塊足夠大的記憶體並進行記憶體拷貝。值得注意的是,vector每次擴容為原來的兩倍,對小物件來說執行效率高,但如果遇到大物件,執行效率就低了。

list

list類似於C語言中的雙向連結串列,它通過指標來進行資料的訪問,因此維護的記憶體空間可以不連續,這也非常有利於資料的隨機存取,因而它沒有提供 [] 操作符過載。

deque

deque類似於C語言中的雙向佇列,即兩端都可以插入或者刪除的佇列。queue支援 [] 操作符,也就是支援隨機存取,而且跟vector的效率相差無幾。它支援兩端的操作:push_back,push_front,pop_back,pop_front等,並且在兩端操作上與list的效率
也差不多。或者我們可以這麼認為,deque是vector跟list的折中。

map

map類似於資料庫中的1:1關係,它是一種關聯容器,提供一對一(C++ primer中文版中將第一個譯為鍵,每個鍵只能在map中出現一次,第二個被譯為該鍵對應的值)的資料處理能力,這種特性了使得map類似於資料結構裡的紅黑二叉樹。

multimap

multimap類似於資料庫中的1:N關係,它是一種關聯容器,提供一對多的資料處理能力。

set

set類似於數學裡面的集合,不過set的集合中不包含重複的元素,這是和vector的第一個區別,第二個區別是set內部用平衡二叉樹實現,便於元素查詢,而vector是使用連續記憶體儲存,便於隨機存取。

multiset

multiset類似於數學裡面的集合,集合中可以包含重複的元素。

小結

在實際使用過程中,到底選擇這幾種容器中的哪一個,應該根據遵循以下原則:

1、如果需要高效的隨機存取,不在乎插入和刪除的效率,使用vector;

2、如果需要大量的插入和刪除元素,不關心隨機存取的效率,使用list;

3、如果需要隨機存取,並且關心兩端資料的插入和刪除效率,使用deque;

4、如果打算儲存資料字典,並且要求方便地根據key找到value,一對一的情況使用map,一對多的情況使用multimap;

5、如果打算查詢一個元素是否存在於某集合中,唯一存在的情況使用set,不唯一存在的情況使用multiset。

常用STL容器舉例

一 常用容器舉例 

1 vector:

       vector類似於動態陣列,直接訪問元素,從後面快速插入或者刪除,示例程式碼如下:

  1. #include <iostream>
  2. #include <vector>//包含vector
  3. usingnamespace std;//指定名稱空間
  4. int main()  
  5. {  
  6.     cout<<"----------vector test-----------"<<endl;  
  7.     //定義一個vector
  8.     vector <int> vect;  
  9.     vector <int> vect1(12);//12個int型別元素,每個元素的初始值均為0
  10.     vector <int> vect2(12,9);//12個int,初試值均為9
  11.     //使用陣列初始化vector
  12.     int a[]={0,1,2,3,4,5,6,7,8,9,0};  
  13.     //vector <資料型別> <容器名> (<開始地址>,<結束地址的下一個地址> )。執行過vt中元素為1,2,3
  14.     vector <int> vt(a+1,a+4);  
  15.     //在尾部壓入3個值
  16.     vt.push_back(1);  
  17.     vt.push_back(2);  
  18.     vt.push_back(3);  
  19.     //定義迭代器iterator
  20.     vector <int>::iterator iter=vt.begin();//起始地址
  21.     vector <int>::iterator iter_end=vt.end();//結束地址,兩個地址都是指標型別
  22.     //遍歷vt
  23.     for(;iter!=iter_end;iter++)  
  24.     {  
  25.         cout<<*iter<<endl;  
  26.     }  
  27.     //彈出一個元素
  28.     vt.pop_back();  
  29.     //以下兩行重新獲得起始和結尾地址
  30.     iter=vt.begin();  
  31.     iter_end=vt.end();  
  32.     cout<<"----------executed pop_back------"<<endl;  
  33.     for(;iter!=iter_end;iter++)  
  34.     {  
  35.         cout<<*iter<<endl;  
  36.     }  
  37.     //插入元素
  38.     cout<<"----------insert into------------"<<endl;  
  39.     //插入格式:vector.insert(<起始地址>,<插入的數量>,<元素值>);如果插入的數量為1,則第二個引數可以被省略
  40.     vt.insert(vt.begin()+1,3,9);  
  41.     iter=vt.begin();  
  42.     iter_end=vt.end();  
  43.     for(;iter!=iter_end;iter++)  
  44.     {  
  45.         cout<<*iter<<endl;  
  46.     }  
  47.     //刪除元素
  48.     cout<<"----------erase-------------------"<<endl;  
  49.     //刪除格式1為:vector.erase(<刪除元素的地址>);
  50.     //刪除格式2為:vector.erase(<刪除元素的起始地址>,<終止地址>);
  51.     iter=vt.begin();  
  52.     iter_end=vt.end();  
  53.     vt.erase(iter+1,iter_end);//刪除第二個到最後一個的元素
  54.     iter_end=vt.end();  
  55.     for(;iter!=iter_end;iter++)  
  56.     {  
  57.         cout<<*iter<<endl;  
  58.     }  
  59.     return 1;  
  60. }  

   2  list

  list 為雙向連結串列,可以從任何地方插入或者刪除的,其示例程式碼如下: