STL六大組件


容器(Container)

算法(Algorithm)

迭代器(Iterator)

仿函數(Function object)

適配器(Adaptor)

空間配置器(allocator)

1、容器

作為STL的最主要組成部分--容器,分為向量(vector),雙端隊列(deque),表(list),隊列(queue),堆棧(stack),集合(set),多重集合(multiset),映射(map),多重映射(multimap)。

容器

特性

所在頭文件

向量vector

可以用常數時間訪問和修改任意元素,在序列尾部進行插入和刪除時,具有常數時間復雜度,對任意項的插入和刪除就有的時間復雜度與到末尾的距離成正比,尤其對向量頭的添加和刪除的代價是驚人的高的

<vector>

雙端隊列deque

基本上與向量相同,唯一的不同是,其在序列頭部插入和刪除操作也具有常量時間復雜度

<deque>

表list

對任意元素的訪問與對兩端的距離成正比,但對某個位置上插入和刪除一個項的花費為常數時間。

<list>

隊列queue

插入只可以在尾部進行,刪除、檢索和修改只允許從頭部進行。按照先進先出的原則。

<queue>

堆棧stack

堆棧是項的有限序列,並滿足序列中被刪除、檢索和修改的項只能是最近插入序列的項。即按照后進先出的原則

<stack>

集合set

由節點組成的紅黑樹,每個節點都包含着一個元素,節點之間以某種作用於元素對的謂詞排列,沒有兩個不同的元素能夠擁有相同的次序,具有快速查找的功能。但是它是以犧牲插入刪除操作的效率為代價的

<set>

多重集合multiset

和集合基本相同,但可以支持重復元素具有快速查找能力

<set>

映射map

由{鍵,值}對組成的集合,以某種作用於鍵對上的謂詞排列。具有快速查找能力

<map>

多重集合multimap

比起映射,一個鍵可以對應多了值。具有快速查找能力

<map>

STL容器能力表:

 

 

2、算法

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

STL的算法也是非常優秀的,它們大部分都是類屬的,基本上都用到了C++的模板來實現,這樣,很多相似的函數就不用自己寫了,只要用函數模板就可以了。

我們使用算法的時候,要針對不同的容器,比如:對集合的查找,最好不要用通用函數find(),它對集合使用的時候,性能非常的差,最好用集合自帶的find()函數,它針對了集合進行了優化,性能非常的高。

 

3、迭代器

它的具體實現在<itertator>中,我們完全可以不管迭代器類是怎么實現的,大多數的時候,把它理解為指針是沒有問題的(指針是迭代器的一個特例,它也屬於迭代器),但是,決不能完全這么做。

迭代器功能

輸入迭代器

Input iterator

、Reads forward

istream

輸出迭代器

Output iterator

向前寫

Writes forward

ostream,inserter

前向迭代器

Forward iterator

向前讀寫

Read and Writes forward

 

雙向迭代器

Bidirectional iterator

向前向后讀寫

Read and Writes forward and

backward

list,set,multiset,map,mul

timap

隨機迭代器

Random access iterator

隨機讀寫

Read and Write with random

access

vector,deque,array,string

 

4、仿函數

1仿函數(functor)的概念編輯

仿函數(functor),就是使一個類的使用看上去象一個函數。其實現就是類中實現一個operator(),這個類就有了類似函數的行為,就是一個仿函數類了
在我們寫代碼時有時會發現有些功能的實現的代碼,會不斷的在不同的成員函數中用到,但是又不好將這些代碼獨立出來成為一個類的一個成員函數。但是又很想復用這些代碼。寫一個公共的函數,可以,這是一個解決方法,不過函數用到的一些 變量,就可能成為公共的 全局變量,再說為了復用這么一片代碼,就要單立出一個函數,也不是很好維護。這時就可以用仿函數了,寫一個簡單類, 除了那些維護一個類的成員函數外,就只是實現一個operator(),在類實例化時,就將要用的,非參數的元素傳入類中。這樣就免去了對一些公共 變量的全局化的維護了。又可以使那些代碼獨立出來,以便下次復用。而且這些仿函數,還可以用關聯,聚合,依賴的類之間的關系,與用到他們的類組合在一起,這樣有利於資源的管理(這點可能是它相對於函數最顯著的優點了)。如果在配合上模板技術和policy編程思想,那就更是威力無窮了,大家可以慢慢的體會。

仿函數,又或叫做函數對象,是STL六大組件之一;仿函數雖然小,但卻極大的拓展了算法的功能,幾乎所有的算法都有仿函數版本。例如,查找算法find_if就是對find算法的擴展,標准的查找是兩個元素相等就找到了,但是什么是相等在不同情況下卻需要不同的定義,如地址相等,地址和郵編都相等,雖然這些相等的定義在變,但算法本身卻不需要改變,這都多虧了仿函數。仿函數(functor)又稱之為函數對象(function object),其實就是重載了()操作符的struct,沒有什么特別的地方

仿函數(functor)在各編程語言中的應用編輯

C

C語言使用 函數指針回調函數來實現仿函數,例如一個用來排序的函數可以這樣使用仿函數
#include <stdlib.h>
/* Callback function */
int compare_ints_function (void*A,void*B)
{
return*((int*)(A))<*((int*)(B));
}
/* Declaration of C sorting function */
void sort(void*first_item,size_t item_size,void*last_item, int(*cmpfunc)(void*,void*));
int main(void)
{
int items[]={4,3,1,2};
sort((void*)(items),sizeof(int),(void*)(items +3), compare_ints_function);
return 0;
}

C++

在C++里,我們通過在一個類中 重載括號 運算符的方法使用一個 函數對象而不是一個普通函數。
class compare_class
{
public:
bool operator()( int A, int B)const{return A < B;}
};
// Declaration of C++ sorting function.
template<class ComparisonFunctor>
void sort_ints(int* begin_items, int num_items, ComparisonFunctor c);
int main()
{
int items[]={4, 3, 1, 2};
compare_class functor;
sort_ints(items, sizeof(items)/sizeof(items[0]), functor);
}
 

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

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

最基本的仿函數舉例:

/**

Function Object

*/

class PrintInt
{
public:
    void operator()(int elem) const
    {
        std::cout<<elem<<' ';
    }
};

void testPrintInt()
{
    std::vector<int> intVec;
    for(int i=1;i<10;++i)
        intVec.push_back(i);
    for_each(intVec.begin(),intVec.end(),
             PrintInt());    ///PrintInt()產生此類型的一個臨時對象,當for_each算法的一個參數
    std::cout<<std::endl;
}

STL中for_each()算法大致實現如下:
template <typename Iterator, typename operation>
operation for_each(Iterator b,Iterator e,operation op)
{
    while(b++ != e)
    {
        op(*b);
    }
    return op;
}

在本例中,for_each()調用PrintInt::operator(*b);

為什么要使用仿函數呢?

1).仿函數比一般的函數靈活

2).仿函數有類型識別,可以作為模板參數

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

怎么使用仿函數?

除了在STL里,別的地方你很少會看到仿函數的身影。而在STL里仿函數最常用的就是作為函數的參數,或者模板的參數。

在STL里有自己預定義的仿函數,比如所有的運算符,=,-,*,、比如'<'號的仿函數是less

template<class _Ty>
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<...>繼承來的,那么binary_function又是什么的?

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只是做一些類型聲明而已,別的什么也沒做,但是在STL里為什么要做這些呢?如果你要閱讀過STL的源碼,你就會發現,這樣的用法很多,其實沒有別的目的,就是為了方便,安全,可復用性等。但是既然STL里面內定如此了,所以作為程序員你必須要遵循這個規則,否則就別想安全的使用STL。

比如我們自己定一個仿函數。可以這樣:

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==

             }
}

我們看這一行: inline bool operator()(type1 t1,type2 t2) const//這里的const不能少
inline是聲明為內聯函數,我想這里應該不用多說什么什么了,關鍵是為什么要聲明為const的?要想找到原因還是看源碼,加入如果我們這里寫一行代碼,find_if(s.begin(),s.end(),bind2nd(func_equal(),temp)),在bind2nd函數里面的參數是const類型的,const類型的對象,只能訪問cosnt修飾的函數

binary_function(二元函數)相對的是unary_function(一元函數),其用法同binary_function

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

注:仿函數就是重載()的class,並且重載函數要為const的,如果要自定義仿函數,並且用於STL接配器,那么一定要從binary_function或者,unary_function繼承

 

5、適配器

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

容器適配器:包括棧(stack)、隊列(queue)、優先(priority_queue)。使用容器適配器,stack就可以被實現為基本容器類型(vector,dequeue,list)的適配。可以把stack看作是某種特殊的vctor,deque或者list容器,只是其操作仍然受到stack本身屬性的限制。queue和priority_queue與之類似。容器適配器的接口更為簡單,只是受限比一般容器要多。

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

函數適配器:通過轉換或者修改其他函數對象使其功能得到擴展。這一類適配器有否定器(相當於"非"操作)、綁定器、函數指針適配器。函數對象適配器的作用就是使函數轉化為函數對象,或是將多參數的函數對象轉化為少參數的函數對象。

例如:

在STL程序里,有的算法需要一個一元函數作參數,就可以用一個適配器把一個二元函數和一個數值,綁在一起作為一個一元函數傳給算法。

bind1st和bind2nd函數用於將一個二元算子(binary functor,bf)轉換成一元算子(unary functor,uf)。

我們在做比較的時候所寫的表達式像 x > k ,x < k,這里的k是一個參數表示你程序里面的表達式要和k值去比較。上面這兩個表達式對應的應該是bind2nd ,簡單的理解就是把k作為比較表達式的第二個參數。如果使用bind1st則對應的表達式是 k > x,k < x,也就是把k作為比較表達式的第一個參數。
例如:
find_if(coll.begin(), coll.end(), bind2nd(greater <int>(), 42));
這句話就是找coll中第一個大於42的元素。
greater <int>(),其實就是">"號,是一個2元函數
bind2nd的兩個參數,要求一個是2元函數,一個是數值,結果是一個1元函數。
bind2nd就是個函數適配器

 

eg:

1、首先看一個容器的操作:
void f(std::vector<int> &vect)
{
    std::vector<int>::iterator firstOne;
    for (firstOne = vect.begin();
    firstOne != vect.end();
    ++firstOne)
    {
        doSomething(*firstOne, "Some string literal");
    }
}

這個f調用就是完成對容器vect的迭代,並在迭代過程中,處理iter值和一個不變的字符串,至於dosomething完成什么功能,根本不必關心。

這里有人肯定要說,不是用for_each就可完成這種功能嗎,可for_each只接受一個參數的函數。如下所示:
funcation for_each(iterator beg_it, iterator end_it, funcation func);

那么怎樣讓func能夠綁定當前iterator值和一個不變的字符串呢?如果成功,問題就OK了。

在解決這個問題必須要用到適配器函數,如bind1nd, bind2st之流的捆綁函數。在解析這兩個函數之前,先看看Funcation的類聲明:

2、Funcation的類聲明:
template <class Arg, class Arg2, class Res>
struct binary_function {
    typedef Arg first_argument_type;
    typedef Arg2 second_argument_type;
    typedef Res result_type;
};

ok, 我們自己的func也可繼承這個基類,哈哈,改進后的dosomething聲明:
class dosomething:public
std::binary_funcation<int, const char *, void>
{
//其中,int是我們當前iterator值類型,const char *是要傳遞的固定不變的字符串,void是我們func的返回值。看看下面的重載() 聲明,就明白了:
public:
       void  operator()(int ival, const char *s)
       {
             // 在這里添加你想干的事情,記住,ival就是當前iterator的值, s是需要綁定的不變字符串
     }
};


3、bind1st和bind2nd的選擇
從如上的dosomething可以看出,需要綁定的是s這個不變字符串,是第二個參數,所以當然選擇bind2nd,如果dosomething的聲明如下:

class dosomething:public
std::binary_funcation<const char *,  int , void>
{
//其中,int是我們當前iterator值類型,const char *是要傳遞的固定不變的字符串,void是我們func的返回值。看看下面的重載() 聲明,就明白了:
public:
       void  operator()(const char *s, int)
       {
             // 在這里添加你想干的事情,記住,ival就是當前iterator的值, s是需要綁定的不變字符串
     }
};

那么就當然選擇bind1st了,因為需要綁定的不變參數s是第一個參數。
我靠,原來這兩個函數沒什么本質區別,只是根據用戶定義函數參數的順序有關

 

6、空間配置器

STL的內存配置器在我們的實際應用中幾乎不用涉及,但它卻在STL的各種容器背后默默做了大量的工作,STL內存配置器為容器分配並管理內存。統一的內存管理使得STL庫的可用性、可移植行、以及效率都有了很大的提升。

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

SGI使用時std::alloc作為默認的配置器。

A).alloc把內存配置對象構造的操作分開,分別由alloc::allocate()::construct()負責,同樣內存釋放對象析夠操作也被分開分別由alloc::deallocate()和::destroy()負責。這樣可以保證高效,因為對於內存分配釋放和構造析夠可以根據具體類型(type traits)進行優化。比如一些類型可以直接使用高效的memset來初始化或者忽略一些析構函數。對於內存分配alloc也提供了2級分配器來應對不同情況的內存分配。

B).第一級配置器直接使用malloc()和free()來分配和釋放內存。第二級視情況采用不同的策略:當需求內存超過128bytes的時候,視為足夠大,便調用第一級配置器;當需求內存小於等於128bytes的時候便采用比較復雜的memeory pool的方式管理內存

C).無論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));

     }

}   

 

d).內存的基本處理工具,它們均具有commt or 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)


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM