跟我學STL系列(1)——STL入門介紹
一、引言
最近這段時間一直都在自學C++,所以這裡總結下自己這段時間的學習過程,通過這種方式來鞏固自己學到的內容和以備後面複習所用,另外,希望這系列文章可以幫助到其他自學C++的朋友們。
由於本人之前主要研究C#語言,在自學C++的過程中,經常會把C++中內容與C#中內容進行對比來理解,所以這系列文章的內容也會與C#進行比較,從而來說明語言都是想通的,只要你掌握好一門語言,學習其他語言都可以舉一反三。
二、STL是什麼
STL全稱為Standard Template Library,即標準模板庫,該庫提供一些常用的容器物件和一些通用的演算法等,大家可以理解STL就是一個庫,該庫幫我們封裝了很多容器類和通用的方法,我們可以通過呼叫該庫中封裝好的方法和容器類來進行程式設計,相比C#而言,STL就好比.NET類庫中的某個DLL,例如,C# 中,List<T>類存在於mscorlib.dll中System.Collections.Generic名稱空間下,C++ 中,list<T>存在於list標頭檔案中std名稱空間下,所以C++程式碼中要使用list<T>容器(在C++中把list成為容器,即一系列元素的集合),必須先通過#include
<list>引入list標頭檔案,類似與C#中的新增mscorlib.dll引言,再使用use namespace std引入名稱空間,類似與C#中using System.Collections.Generic程式碼。
三、STL 六大元件
STL通過模板抽象了基於資料結構之上的普遍行為,形成了獨特的STL演算法。在STL中,這些資料結構成為容器。在容器和演算法之間通過中間體:迭代器來進行連線,迭代器可以看做是資料結構和演算法之間的紐帶,它降低了資料結構和演算法之間的耦合度。STL中國又包括六大核心元件,它們分別是:
- 容器(Container)
- 演算法(Algorithm)
- 迭代器(Iterator)
- 函式物件,又稱仿函式(Function object)
- 介面卡(Adaptor)
- 空間配置器(Allocator)
3.1 容器
STL中容器可分為序列式容器和關聯式容器,其中,序列式容器的每個元素的位置取決於元素被插入時設定的位置,和元素值本身無關,而,關聯式容器的元素位置取決於特定的排序規則,和插入順序無關。意思就說,序列式容器中每個元素的位置與插入的順序對應,而關聯式容器中元素會根據特定的排序規則對每個元素進行排序,與元素插入的順序無關,更多內容可以參考本系列後面文章介紹。
容器 | 特徵 | 記憶體結構 | 可隨機存取 | 元素搜尋速度 | 標頭檔案 |
vector | 在序列尾部進行插入和刪除,訪問和修改元素的時間複雜度為O(1), 但插入和刪除的時間複雜度與到末尾的距離成正比。 |
單端陣列 | 可以 | 慢 | <vector> |
list | 對任意元素的訪問與兩端的距離成正比,但對某個位置的插入和刪除 花費為常數時間,即O(1) |
雙向連結串列 | 否 | 非常慢 | <list> |
deque | 與vector基本相同,唯一不同的是,在序列頭部插入和刪除的 時間複雜度也是O(1) | 雙端陣列 | 可以 | 慢 | <deque> |
set | 由節點組成的紅黑樹,具有快速查詢的功能 | 二叉樹 | 否 | 快 | <set> |
multiset | 可以支援重複元素,同樣具有快速查詢能力 | 二叉樹 | 否 | 快 | <set> |
map | 由{鍵,值}對組成的集合,同樣具有快速查詢能力 | 二叉樹 | 對key而言可以 | 對key而言快 | <map> |
multimap | 一個鍵可以對應於多個值,同樣具有快速查詢能力 | 二叉樹 | 否 | 對key而言快 | <map> |
上面列出的所有容器在C#中都有相應的對應形式,它們之間的對應關係為: std::vector——List<T>類 std::set——HashSet<T>類,但這裡需要明確,STL中的set是以紅黑樹作為底層資料結構,而C#中HashSet<T>類是以雜湊表作為底層資料結構,因為其兩者使用資料結構的不同,從而導致查詢效率不同,set查詢的花費時間為O(logn),這也是紅黑樹查詢時間,而HashSet的查詢花費時間為O(1)。 std::multimap——Dictionary<TKey,List<TValue>>,該類在C#中也不存在的,也需要自己實現 std::multiset——Dictionary<TKey,int>(第二個引數儲存著Key的數量) C++中的std::deque在C#中並沒有找到相對應地實現,不過我們可以自己實現,具體實現可以參考文章:A Deque Class in C#。 在上面的對應關係中,C#中的SortedDictionary<TKey,TValue>類是以二叉查詢樹作為底層資料結構的,而Dictionary<TKey,TValue>類是以雜湊表作為底層資料結構的。因為其資料結構的不同從而導致操作效率的不同,下表列出了兩者各種操作的區別。
操作 | Dictionary<Key,Value> | SortedDictionary<Key,Value> |
this[key] | O(1) | O(logn) |
Add(key,value) | O(1) | O(logn) |
Remove(key) | O(1) | O(logn) |
ContainsKey(key) | O(1) | O(logn) |
ContainsValue(value) | O(1) | O(n) |
3.2 演算法
演算法是用來操作容器中資料的模板函式,它抽象了對資料結構的操作行為。要使用STL中定義的演算法,應首先引入<algorithm>標頭檔案。例如STL中的sort()函式可以對容器中的資料進行排序,可以使用find()函式來搜尋容器中的某個元素。這裡的演算法可以與C#中泛型方法進行對比來理解。3.3 迭代器
STL實現要點是將容器和演算法分開,使兩者彼此獨立。迭代器使兩個聯絡起來,迭代器提供訪問容器中的方法。迭代器實質上是一種智慧指標,它過載了->和*操作符。事實上,C++指標也是一種迭代器。在C#中同樣有迭代器的概念,具體參考MSDN:http://msdn.microsoft.com/zh-cn/library/dscyy5s0(v=vs.90).aspx,不同的是,在C++ 中迭代器分為五類,這五類分別為:
- 輸入迭代器(Input Iterator)——提供對資料的只讀訪問;
- 輸出迭代器(Output Iterator)——提供對資料的只寫訪問;
- 前推迭代器(Forward Iterator)——提供對資料的讀寫操作,並能向前推進的迭代器;
- 雙向迭代器(Bidirectional Iterator)——提供對資料的讀寫操作,並能向前和向後操作;
- 隨機訪問迭代器(Random Access Iterator)——提供對資料的讀寫操作,並能在資料中隨機移動。
3.4 函式物件
函式物件,又稱為仿函式,STL中的函式物件就是過載了運算子()的模板類的物件,因為該類物件的呼叫方式類似與函式的呼叫方式,所以稱為函式物件,函式物件類似於C#中的委託物件,熟悉C#的朋友肯定知道,我們可以隱式地呼叫委託,即委託物件(實參),更多關於委託內容可以參考我的博文:委託的本質論。
3.5 介面卡
介面卡是用來修改其他元件介面,與設計模式中的介面卡模的達到的效果是一樣的。STL中定義了3種形式的介面卡:容器介面卡、迭代器適配和函式介面卡。
容器介面卡——包括棧(stack)、佇列(queue)和優先佇列(priority_queue),容器介面卡是對基本容器型別進行進一步的封裝,從而轉換為新的介面型別。
迭代器介面卡——對STL中基本迭代器的功能進行擴充套件,該類介面卡包括反向迭代器、插入迭代器和流迭代器。
函式介面卡——通過轉換或修改來擴充套件其他函式物件的功能。該類介面卡有否定器、繫結器和函式指標介面卡。函式物件介面卡的作用就是使函式轉化為函式物件,或將多引數的函式物件轉換為少引數的函式物件,如STL中bind2nd()就是繫結器。
3.6 空間配置器
當容器中儲存的是使用者自定義型別資料時,有的資料型別結構簡單,佔用的空間很小,而有的資料型別結構複雜,佔用的記憶體空間較大;並且有的應用程式需要頻繁地進行資料的插入刪除操作,這樣就需要對記憶體空間進行頻繁地申請和釋放工作,然而對記憶體的頻繁操作,會產生嚴重的效能問題,為了解決這個問題,STL中提供了兩個空間配置器,一個是簡單空間配置器,僅僅對C執行庫中malloc和free進行了簡單的封裝操作,另一個是“基於記憶體池的控制元件配置器”,即容器在每次申請記憶體的時候,記憶體池會基於一定的策略,向作業系統申請交大的記憶體空間,從而避免每次都向OS申請記憶體。STL中的空間配置器就是負責記憶體的分配和釋放的工作。
四、STL中容器使用示例
看完STL元件之後,現在我們具體看看如何使用STL中的容器進行程式設計,下面示例是對vector容器進行簡單的幾個操作。具體程式碼如下:
#include <iostream>
// 要使用vector容器必須加入vector標頭檔案
#include <vector>
// 引入演算法標頭檔案
#include <algorithm>
// 引入std名稱空間,如果不引入名稱空間,則必須像std::vector這樣方式來使用vector容器
using namespace std;
void main()
{
// 初始化vector容器物件
vector<int> vec;
for(int i=0;i<10;i++)
{
// 向vector中新增一個元素
vec.push_back(i);
}
// 使用陣列初始化vector容器
int a[]={3,2,1,0,9,5};
vector<int> vec2(a,a+6); // 使用a陣列第一位到第六位來初始化vector容器
// 定義迭代器物件
vector<int>::iterator begin;
// 遍歷vector集合
for(begin =vec.begin();begin!=vec.end();begin++)
{
// 輸出vector容器中的元素
cout<<*begin<<" ";
}
cout<<endl;
// 對vec2容器排序
sort(vec2.begin(),vec2.end());
// 遍歷vector集合
cout<<"對vec2容器排序後的結果:"<<endl;
for(begin =vec2.begin();begin!=vec2.end();begin++)
{
// 輸出排序後vec2容器中的元素
cout<<*begin<<" ";
}
cout<<endl;
}
上面程式碼的輸出結果如下圖所示: