容器(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)的概念編輯
仿函數,又或叫做函數對象,是STL六大組件之一;仿函數雖然小,但卻極大的拓展了算法的功能,幾乎所有的算法都有仿函數版本。例如,查找算法find_if就是對find算法的擴展,標准的查找是兩個元素相等就找到了,但是什么是相等在不同情況下卻需要不同的定義,如地址相等,地址和郵編都相等,雖然這些相等的定義在變,但算法本身卻不需要改變,這都多虧了仿函數。仿函數(functor)又稱之為函數對象(function object),其實就是重載了()操作符的struct,沒有什么特別的地方。
仿函數(functor)在各編程語言中的應用編輯
C
C++
如以下代碼定義了一個二元判斷式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)
