學習STL-介紹一下STL

從大學時就開始學習C++,到如今近5年的時間了卻不多用到STL。如今想一想真得是對不起這門語言,也對不起寶貴的五年光陰。我鍾愛C++,因此必定要徹底搞懂它,理解它。愛一我的的前提是要懂他(她),愛一門語言也是這樣。鄭重地向C++說聲「對不起!」。我會把不懂你的方面慢慢彌補,作到真正懂你。爲了更好地學習STL,我採用邊學習,邊總結,邊寫博客的方法,但願可以造成一個學習專欄。這樣既能夠便於本身隨時翻閱,又能夠分享給有須要的人。固然在博客中,我有可能會引用到其它大牛博友的文章。爲了尊重原創,我會給出參考博文的連接地址。另外,若是你們在文章中發現錯誤,但願在評論下方給出提示建議。算法

Now,開始學習C++中重要的標準庫STL。編程

STL簡介

STL的原名是「Standard Template Library」,翻譯過來就是標準模板庫。STL是C++標準庫的一個重要組成部分,主要由六大組件構成。這六大組件是:
數組

容器(Container)、算法(algorithm)、迭代器(iterator)、仿函數(functor)、適配器(adapter)、配置器(allocator)安全

一、容器(container)

容器能夠分爲三類即序列容器、關聯容器和容器適配器。各種具體包含以下所示:數據結構

序列容器:vector、list、dequeapp

關聯容器:set、map、multiset、multimapless

適配器容器:stack、queue、priority_queue數據結構和算法

容器  特性 所在頭文件
向量vector 在常數時間訪問和修改任意元素,在序列尾部進行插入和刪除時,具備常數時間複雜度。對任意項的插入和刪除的時間複雜度與到末尾的距離成正比,尤爲對向量頭的添加和刪除代價比較高。 <vector>
雙端隊列deque 基本上與向量相同,不一樣點是,雙端隊列的兩端插入和刪除操做也是常量的時間複雜度。 <deque>
表list 對任意元素的訪問與兩端的距離成正比,但對某個位置上插入和刪除時間複雜度爲常數。 <list>
隊列queue 插入只能夠在尾部進行,刪除、檢索和修改只容許從頭部進行。遵循FIFO的原則。 <queue>
棧stack LIFO:先進後出原則。只容許在序列頭部進行插入、刪除、檢索和修改操做,時間複雜度爲常數。 <stack>
集合set 內部實現機制是紅黑樹,每一個結點都包含一個元素,結點之間以某種做用於元素對的謂詞排列,沒有兩個不一樣的元素可以擁有相同的次序,具備快速查找的功能。 <set>
多重集合multiset 和集合基本相同,但能夠支持重複元素。 <set>
映射map 由(鍵,值)對組成的集合,以某種做用於鍵對上的謂詞排序。具備快速查找特性。 <map>
多重映射multimap 支持一鍵對應多個值的特性,具備快速查找功能。 <map>

二、算法(Algorithm)

算法部分主要在頭文件<algorithm>,<numeric>,<functional>中。<algoritm>是全部STL頭文件中最大的一個,它是由一大堆模版函數組成的,能夠認爲每一個函數在很大程度上都是獨立的,其中經常使用到的功能範 圍涉及到比較、交換、查找、遍歷操做、複製、修改、移除、反轉、排序、合併等等。<numeric>體積很小,只包括幾個在序列上面進行簡單數學運算的模板函數,包括加法和乘法在序列上的一些操做。<functional>中則定義了一些模板類,用以聲明函數對象。函數

三、迭代器(Adapter)

迭代器是用類模板(class template)實現的.重載了* ,-> ,++ ,-- 等運算符。工具

迭代器分5種:輸入迭代器、輸出迭代器、 前面迭代器、雙向迭代器、 隨機訪問迭代器。

輸入迭代器:向前讀(只容許讀);

輸出迭代器:向前寫(只容許寫);

前向迭代器:向前讀寫;

雙向迭代器:向先後讀寫;

隨機迭代器:隨機讀寫;

四、仿函數(Functor)

仿函數用類模板實現,重載了符號"()"。仿函數,又或叫作函數對象,是STL六大組件之一;仿函數雖然小,但卻極大的拓展了算法的功能,幾乎全部的算法都有仿函數版本。

例如,查找算法find_if就是對find算法的擴展,標準的查找是兩個元素相等就找到了,可是什麼是相等在不一樣狀況下卻須要不一樣的定義,如地址相等,地址和郵編都相等,雖然這些相等的定義在變,但算法自己卻不須要改變,這都多虧了仿函數。仿函數(functor)又稱之爲函數對象(function object),其實就是重載了()操做符的struct,沒有什麼特別的地方。

如如下代碼定義了一個二元判斷式functor:

struct IntLess
{
    bool operator()(int left, int right) const
    {
        return (left < right);
    }
};

仿函數的優點:

1)仿函數比通常函數靈活。

2)仿函數有類型識別。能夠用做模板參數。

3)執行速度上仿函數比函數和指針要更快。

在STL裏仿函數最經常使用的就是做爲函數的參數,或者模板的參數。

在STL裏有本身預約義的仿函數,好比全部的運算符=,-,*,、好比'<'號的仿函數是less。

        // TEMPLATE STRUCT less
template<class _Ty = void>
    struct less
        : public binary_function<_Ty, _Ty, bool>
    {    // functor for operator<
    bool operator()(const _Ty& _Left, const _Ty& _Right) const
        {    // apply operator< to operands
        return (_Left < _Right);
        }
    };

less繼承binary_function<_Ty,_Ty,bool>

template<class _Arg1, class _Arg2, class _Result>
struct binary_function
{ // base class for binary functions
        typedef _Arg1 first_argument_type;
        typedef _Arg2 second_argument_type;
        typedef _Result result_type;
};

從定義中能夠知道binary_function知識作了一些類型的聲明,這樣作就是爲了方便安全,提升可複用性。

按照這個規則,咱們也能夠自定義仿函數:

template <typename type1,typename type2>
class func_equal :public binary_function<type1,type2,bool>
{
        inline bool operator()(type1 t1,type2 t2) const//這裏的const不能少
        {
                 return t1 == t2;//固然這裏要overload==
        }
}

之因此const關鍵字修飾函數,是由於const對象只能訪問const修飾的函數。若是一個const對象想使用重載的()函數,編譯過程就會報錯。

小結一下:仿函數就是重載()的class,而且重載函數要有const修飾。自定義仿函數必需要繼承binary_function(二元函數)或者unary_function(一元函數)。其中unary_function的定義以下:

struct unary_function { 
    typedef _A argument_type; 
    typedef _R result_type; 
};

五、適配器(Adapter)

適配器是用來修改其餘組件接口的STL組件,是帶有一個參數的類模板(這個參數是操做的值的數據類型)。STL定義了3種形式的適配器:容器適配器,迭代器適配器,函數適配器。

1)容器適配器:棧(stack)、隊列(queue)、優先(priority_queue)。使用容器適配器,stack就能夠被實現爲基本容器類型(vector,dequeue,list)的適配。能夠把stack看做是某種特殊的vctor,deque或者list容器,只是其操做仍然受到stack自己屬性的限制。queue和priority_queue與之相似。容器適配器的接口更爲簡單,只是受限比通常容器要多。

2)迭代器適配器:修改成某些基本容器定義的迭代器的接口的一種STL組件。反向迭代器和插入迭代器都屬於迭代器適配器,迭代器適配器擴展了迭代器的功能。

3)函數適配器:經過轉換或者修改其餘函數對象使其功能獲得擴展。這一類適配器有否認器(至關於"非"操做)、綁定器、函數指針適配器。函數對象適配器的做用就是使函數轉化爲函數對象,或是將多參數的函數對象轉化爲少參數的函數對象。

例如:

在STL程序裏,有的算法須要一個一元函數做參數,就能夠用一個適配器把一個二元函數和一個數值,綁在一塊兒做爲一個一元函數傳給算法。 

find_if(coll.begin(), coll.end(), bind2nd(greater <int>(), 42)); 
這句話就是找coll中第一個大於42的元素。 
greater <int>(),其實就是">"號,是一個2元函數 
bind2nd的兩個參數,要求一個是2元函數,一個是數值,結果是一個1元函數。
bind2nd就是個函數適配器。

六、空間配置器(Allocator)

STL內存配置器爲容器分配並管理內存。統一的內存管理使得STL庫的可用性、可移植行、以及效率都有了很大的提高。

SGI-STL的空間配置器有2種,一種僅僅對c語言的malloc和free進行了簡單的封裝,而另外一個設計到小塊內存的管理等,運用了內存池技術等。在SGI-STL中默認的空間配置器是第二級的配置器。

SGI使用時std::alloc做爲默認的配置器。

  • alloc把內存配置和對象構造的操做分開,分別由alloc::allocate()::construct()負責,一樣內存釋放和對象析夠操做也被分開分別由alloc::deallocate()和::destroy()負責。這樣能夠保證高效,由於對於內存分配釋放和構造析夠能夠根據具體類型(type traits)進行優化。好比一些類型能夠直接使用高效的memset來初始化或者忽略一些析構函數。對於內存分配alloc也提供了2級分配器來應對不一樣狀況的內存分配。
  • 第一級配置器直接使用malloc()和free()來分配和釋放內存。第二級視狀況採用不一樣的策略:當需求內存超過128bytes的時候,視爲足夠大,便調用第一級配置器;當需求內存小於等於128bytes的時候便採用比較複雜的memeory pool的方式管理內存。
  • 不管allocal被定義爲第一級配置器仍是第二級,SGI還爲它包裝一個接口,使得配置的接口可以符合標準即把配置單位從bytes轉到了元素的大小:
template<class T, class Alloc>
class simple_alloc
{
public:
     static T* allocate(size_t n)
     {
         return 0 == n ? 0 : (T*)Alloc::allocate(n * sizeof(T));
     }
 
     static T* allocate(void)
     {
         return (T*) Alloc::allocate(sizeof(T));
     }
 
     static void deallocate(T* p, size_t n)
     {
         if (0 != n) Alloc::deallocate(p, n * sizeof(T));
     }
 
     static void deallocate(T* p)
     {
         Alloc::deallocate(p, sizeof(T));
     }
}   

內存的基本處理工具,均具備commit或rollback能力。

template<class InputIterator, class ForwardIterator>
ForwardIterator
uninitialized_copy(InputIterator first, InputIterator last, ForwardIterator result);
 
template<class ForwardIterator, class T>
void uninitialized_fill(ForwardIterator first, ForwardIterator last, const T& x);
 
template<class ForwardIterator, class Size, class T>
ForwardIterator
uninitialized_fill_n(ForwardIterator first, ForwardIterator last, const T& x)

泛型技術

泛型技術的實現方法有:模板、多態等。模板是編譯時決定的,多態是運行時決定的,RTTI也是運行時肯定的。

多態是依靠虛表在運行時查表實現的。好比一個類擁有虛方法,那麼這個類的實例的內存起始地址就是虛表地址,能夠把內存起始地址強制轉換成int*,取得虛表,而後(int*)*(int*)取得虛表裏的第一個函數的內存地址,而後強制轉換成函數類型,便可調用來驗證虛表機制。

泛型編程(Generic Programming,如下直接以GP稱呼)是一種全新的程序設計思想,和OO,OB,PO這些爲人所熟知的程序設計想法不一樣的是GP抽象度更高,基於GP設計的組件之間耦合度低,沒有繼承關係,因此其組件間的互交性和擴展性都很是高。咱們都知道,任何算法都是做用在一種特定的數據結構上的,最簡單的例子就是快速排序算法最根本的實現條件就是所排序的對象是存貯在數組裏面,由於快速排序就是由於要用到數組的隨機存儲特性,便可以在單位時間內交換遠距離的對象,而不僅是相臨的兩個對象,而若是用鏈表去存儲對象,因爲在鏈表中取得對象的時間是線性的即O[n],這樣將使快速排序失去其快速的特色。也就是說,咱們在設計一種算法的時候,咱們老是先要考慮其應用的數據結構,好比數組查找,聯表查找,樹查找,圖查找其核心都是查找,但由於做用的數據結構不一樣將有多種不一樣的表現形式。數據結構和算法之間這樣密切的關係一直是咱們之前的認識。泛型設計的根本思想就是想把算法和其做用的數據結構分離,也就是說,咱們設計算法的時候並不去考慮咱們設計的算法將做用於何種數據結構之上。泛型設計的理想狀態是一個查找算法將能夠做用於數組,聯表,樹,圖等各類數據結構之上,變成一個通用的,泛型的算法。

六大組件的關係結構

Container 經過Allocator得到數據存儲空間;Algorithm 經過Iterator存取Container中的內容;Functor能夠協助Algoritm完成不一樣策略;Adapter能夠修飾Container、Algorithm、Iterator。

參考連接:

STL(Standard Template Library)簡介

STL學習小結

相關文章
相關標籤/搜索