STL及其使用方法(很全)

1.    概述
泛型編程思想最早緣於A.Stepanov提出的部分算法可獨立於數據結構的論斷。20世紀90年代初A.Stepanov和Meng Lee根據泛型編程的理論用C++共同編寫了STL。但直至1998年,STL才成爲C++的正式標準。在後來的幾年中,各大主流編譯器也都相繼加入了對STL的支持,至此STL纔開始得到廣泛的應用。
STL體現的是泛型編程的核心思想:獨立數據結構和算法(這是一種獨立於OO的編程哲學)。
STL主要由幾個核心部件組成,即迭代器、容器、算法、函數對象、適配器。容器即物之所屬;算法是解決問題的方式;迭代器是對容器的訪問邏輯的抽象,是連接算法和容器的紐帶;迭代器通過添加了一種間接層的方式實現了容器和算法之間的獨立;函數對象,就是重載了operator()操作符的對象;適配器是通過組合特定的容器實現的一種新的數據結構。在後續的內容中,我們將對幾個核心部件的基礎應用進行詳細的描述。
C++產生的歷史背景賦予了C++太多的職責,比如兼容C、綜合的編程語言等,這些雖然賦予了C++強大的功能,但同時也扔給了極大的複雜度。在這篇文章中,我們並不打算將你帶入C++的複雜地帶,但是需要你有一定的C++基礎,比如類、結構等。
STL深深地植根於C++的基礎設施,這其中包括了內聯、函數對象、函數模板、類模板等。
內聯是C++中一種特殊的語言機制,使用inline來標識。C++在編譯inline標識的函數時,將根據特定的規則將inline函數的代碼直接插入到inline函數的調用位置,以此來提高程序的運行效率,但同時也在一定程度上導致了代碼的膨脹。請看下面的例子:
inline全局函數
inline void max{…};
inline成員函數
class A{
public:
         inline void max{…};
}
函數對象,就是重載了operator()操作符的對象。相對函數指針,函數對象可以具有狀態,更安全,更靈活,基本沒有額外的開銷。在STL中,函數對象經常被用作算法的輸入參數或容器的實例化的參數。請看下面的例子:
定義函數對象類
classs LessThan{
public:
         LessThan(int val): m_val(val){}
         bool operator()(int val){return m_val < val;}
private:
         int m_val;
};
定義函數對象
LessThan less(5);
調用定義函數對象
less(10);//返回爲true
C++中的模板是STL實現的技術基礎。C++中的模板可分爲函數模板和類模板。函數模板抽象了針對不同類型的同一性質的操作。如針對int/long/string的max操作。請看下面的例子:
定義求取兩個類型的值或對象的最大值的操作
template<typename T>
T max(T a,T b){return a>b ? a : b;}
求取兩個int值的最大值
max(0,10);//返回10
求取兩個string對象的最大值
max(string(“Hello”),string(“world”));//返回string(“world”)
C++中的模板是STL實現的技術基礎。C++中的模板可分爲函數模板和類模板。類模板抽象了針對不同類型的同一類事務。如針對int/long/string的Stack。請看下面的例子:
定義一個通用堆棧Stack
template<typename T>
class Stack{
public:
         inline void push(const T& value){…}
         T pop(){…}
void clear(){…}
bool empty() const {…}
};
聲明一個int類型的Stack
typdef Stack<int> IntStack;
聲明一個string類型的Stack
typdef Stack<string> IntStack;
STL中的迭代器是C++指針的泛化,它在算法和容器之間充當一箇中間層,爲處理不同的數據結構提供了一致的方式。迭代器也可以看作是對容器數據結構訪問的一種約束。STL中的迭代器可分爲類:隨機存取迭代器(random-access-iterator),雙向存取迭代器(bidirectional-access-iterator),前向迭代器(forward iterator),輸入迭代器(input-iterator),輸出迭代器(output-iterator)。它們之間的繼承關係如下圖:
 input-iterator 
output-iteartor      
forward-iterator:output-iteartor , input-iterator 
bidirectional-access-iterator : forward-iterator
random-access-iterator:bidirectional-access-iterator
圖一 迭代器關係圖
輸入迭代器也可以稱之爲前向的只讀訪問器,首先它提供了對容器的只讀訪問,其次它只能在容器中進行前向迭代(即只提供++操作)。所有的容器的迭代器都具有輸入迭代器的特徵。通過輸入迭代器你可以進行下面三種操作:
1.         V = *X++
2.         V = *X,X++
3.         V = *X,++X
注:V爲值,X爲迭代器
輸出迭代器也可以稱之爲前向的只寫訪問器,首先它提供了對容器的只寫訪問,其次它只能在容器中進行前向迭代(即只提供++操作)。通過輸出迭代器你可以進行下面三種操作:
1.         *X++ = V
2.         *X = V, X++
3.         *X = V, ++X
注:V爲值,X爲迭代器
前向迭代器繼承自輸入和輸出迭代器,因此具有輸入和輸出迭代器的所有特徵,也即提供對容器數據結構的讀寫訪問但也只具有前向迭代的能力(即只提供++操作)。因此,你可以對前向迭代器進行操作:R == S /++R == ++S。
請看下面的例子:
定義一個利用前向迭代器進行線性查找的算法
template<typename ForwardIterator,typename T>
ForwardIterator linear_search(ForwardIterator first,ForwardIterator last,const T& value)
{
         for (; first != last; ++first){
                   if (*first == value)
                            return first;
}
return last;
}
測試
int ia[] = {35,3,23};
vector<int> vec(ia,ia+3);
vector<int>::iterator it = linear_search(vec.begin(),vec.end(),100);
if (it != vec.end())
std::cout << “Found” << endl;
else
std::cout << “Not Found” << endl;
          測試結果爲:Not Found
雙向存取迭代器從前向迭代器繼承過來,因而具有前向迭代器的所有特徵,雙向存取迭代器還具有後向訪問能力(即只提供–操作)。請看下面的例子:
定義一個利用雙向迭代器排序算法
template<typename BidirectionalIterator,typename Compare>
void sort_me(BidirectionalIterator first,BidirectionalIterator last,Compare comp)
{
    for(BidirectionalIterator i = first; i != last; ++i)
    {
        BidirectionalIterator _last = last;
        while(i != _last–)
        {
            if (comp(*i,*_last))
                iter_swap(i,_last);
        }
    }
}
測試
int ia[] = {123,343,12,100,343,5,5};
vector<int> vec(ia,ia+7);
sort_me(vec.begin(),vec.end(),less<int>());
copy(vec.begin(),vec.end(),ostream_iterator<int>(cout,” “));
std::cout << endl;
         測試結果爲:5 5 12 100 123 343 343
隨機存取迭代器從雙向存取迭代器繼承過來,因而具有雙向存取迭代器的所有特徵。所不同的是,利用隨機存取迭代器你可以對容器數據結構進行隨機訪問,因而隨機存取迭代器還可以定義下面的操作:
1.         operator+(int)
2.         operator+=(int)
3.         operator-(int)
4.         operator-=(int)
5.         operator[](int)
6.         operator-(random-access-iterator)
7.         operator>(random-access-iterator)
8.         operator<(random-access-iterator)
9.         operator>=(random-access-iterator)
10.     operator<=(random-access-iterator)
在STL中,隨機存取雙向迭代器只能作用於順序容器。請看下面的例子:
         測試輸出vector裏面的數據
    int ia[] = {123,343,12,100,343,5,5};
    vector<int> vec(ia,ia+7);
    for(int i = 0; i < vec.size(); ++i)
        std::cout << vec[i] << ” “;
                   測試結果爲:123 343 12 100 343 5 5
容器即物之所在。容器是STL的核心部件之一,是迭代器的依附,是算法作用的目標。
STL中的容器可分爲順序容器(Sequence Container)和關聯容器(Associative Container)。容器適配器(Container Adaptor)是對順序容器(Sequence Container)或關聯容器(Associative Container)進行包裝而得到的一種具有更多約束力(或功能更強大)的容器。
下表列出的是STL中的主要(標準和非標準的)容器:

順序容器
(Sequence Container)


容器


備註


vector


 


stack


非STL標準
vector的適配器(Adaptor)


list


 


slist


非STL標準
list的適配器(Adaptor)


deque


 


priority_queue


非STL標準
deque的適配器(Adaptor)


queue


非STL標準,
deque的適配器(Adaptor)


關聯容器
(Associative Container)


set


底層數據結構是RB-tree(紅黑樹)


multiset


底層數據結構是RB-tree(紅黑樹)


map


底層數據結構是RB-tree(紅黑樹)


multimap


底層數據結構是RB-tree(紅黑樹)


hashtable


非STL標準


hash_set


非STL標準,底層數據結構是hashtable


hash_map


非STL標準,底層數據結構是hashtable


hash_multiset


非STL標準,底層數據結構是hashtable


hash_multimap


非STL標準,底層數據結構是hashtable)


所有的容器都是物之所在,這就決定了它們必然存在很多共性,這些共性包括迭代器、大小等屬性。容器與容器之間的主要區別體現在對數據的操作上。
每類容器都包含四個迭代器:iterator(正向迭代器)、const_iterator(常正向迭代器)、reverse_iterator(反向迭代器)、const_reverse_iterator(常反向迭代器)。因此你可以按照下面的方式獲取每個容器的相應的迭代器:
獲取正向迭代器
C<T>::iterator it = c.begin();
C <T>::iterator it = c.end();
獲取反向迭代器
C <T>::reverse_iterator it = c.rbegin();
C <T>:: reverse_iterator it = c.rend()
獲取常正向迭代器
C<T>::const_iterator it = c.begin();
C <T>:: const_iterator it = c.end();
獲取常反向迭代器
C <T>:: const_ reverse_iterator it = c.rbegin();
C <T>:: const_ reverse_iterator it = c.rend()
注:C爲容器類型,cC的實例
所有容器是數據的存在之處,可以看作的是數據的集合,因此它們都會有大小、是否爲空等屬性,因此你可以按照下面的方式獲取所有的容器的公共屬性:
獲取容器的大小
c.size();
判斷容器是否爲空
c.empty();
順序容器中所有的元素在容器中的物理位置都是按照特定的次序進行存放的,區別於關聯容器的是順序容器中的元素的位置都是既定的。被納入STL標準的順序容器包括vector、list、dequeue。
序列容器之間的共性除了容器之間應有的共性之外,還有對數據操作的接口(非實現)上:
c.push_back
c.pop_back
c.push_front
c.pop_front
c.back
c.front
c.erase
c.remove
vector和數組具有同樣的內存處理方式。不同於數組的是:數組是靜態空間,一旦分配了就不能被改變,因而空間的分配非常地不靈活;vector是動態空間,即空間可以被動態分配,因而空間的分配很靈活。可以說vector是相對數組的一種更高級的數據結構。
vector中的迭代器的種類爲隨機存取迭代器(random-access-iterator)。
vector不同於其它順序容器(Sequence Container) 的時,它具有capacity屬性,可以通過vec.capacity()來獲取。
請看下面的例子:
構造vector
int ia[] = {123,343,12,100,343,5,5};
vector<int> vec(ia,ia+7);//{ 123,343,12,100,343,5,5}
vector<int> vec1(2,4);//{4,4}
vector<int> vec2(4);{0,0,0,0}
輸出vector中的所有數據
vector<int>::iterator itEnd = vec.end();
for(vector<int>::iterator it = vec.begin(); it != itEnd; ++it)
std::cout << *it << endl;
添加數據
         vector<int>::iterator it(vec.rbegin().base());
vec.insert(it,1);
vector<int>::iterator it = vec.begin();
vec.insert(it,1);
list是鏈表的抽象數據結構(ADT)。list中的所有數據在空間分配上不一定是連續存放的。相對vector,list沒有capaciy屬性。
list中的迭代器的種類爲雙向存取迭代器(bidirectional -access-iterator)。
請看下面的例子:
構造list
int ia[] = {123,343,12,100,343,5,5};
list<int> ls;
list<int> ls(ia,ia+7); //{ 123,343,12,100,343,5,5}
list<int> ls(2,4); //{4,4}
list<int> ls(4);{ 0,0,0,0}
輸出list中的所有數據
list<int>::iterator itEnd = ls.end();
for(list<int>::iterator it = ls.begin(); it != itEnd; ++it)
std::cout << *it << endl;
deque,即雙向鏈表。相對vector,deque也是連續空間,但不是vector的連續線性空間。
deque中的迭代器的種類爲隨機存取迭代器(random-access-iterator)。
deque成員函數如下表:














































函數

描述

c.assign(beg,end)
c.assign(n,elem)

將[beg; end)區間中的數據賦值給c。
將n個elem的拷貝賦值給c。

c.at(idx)

傳回索引idx所指的數據,如果idx越界,拋出out_of_range。

c.back()

傳回最後一個數據,不檢查這個數據是否存在。

c.begin()

傳回迭代器重的可一個數據。

c.clear()

移除容器中所有數據。

deque<Elem> c
deque<Elem> c1(c2)
deque<Elem> c(n)
deque<Elem> c(n, elem)
deque<Elem> c(beg,end)
c.~deque<Elem>()

創建一個空的deque。
複製一個deque。
創建一個deque,含有n個數據,數據均已缺省構造產生。
創建一個含有n個elem拷貝的deque。
創建一個以[beg;end)區間的deque。
銷燬所有數據,釋放內存。

c.empty()

判斷容器是否爲空。

c.end()

指向迭代器中的最後一個數據地址。

c.erase(pos)
c.erase(beg,end)

刪除pos位置的數據,傳回下一個數據的位置。
刪除[beg,end)區間的數據,傳回下一個數據的位置。

c.front()

傳回地一個數據。

c.get_allocator

使用構造函數返回一個拷貝。

c.insert(pos,elem)
c.insert(pos,n,elem)
c.insert(pos,beg,end)

在pos位置插入一個elem拷貝,傳回新數據位置。
在pos位置插入>n個elem數據。無返回值。
在pos位置插入在[beg,end)區間的數據。無返回值。

c.max_size()

返回容器中最大數據的數量。

c.pop_back()

刪除最後一個數據。

c.pop_front()

刪除頭部數據。

c.push_back(elem)

在尾部加入一個數據。

c.push_front(elem)

在頭部插入一個數據。

c.rbegin()

傳回一個逆向隊列的第一個數據。

c.rend()

傳回一個逆向隊列的最後一個數據的下一個位置。

c.resize(num)

重新指定隊列的長度。

c.size()

返回容器中實際數據的個數。

C1.swap(c2)

將c1和c2元素互換。

關聯容器(Associative Container)提供了根據key快速檢索數據的能力。在關聯容器(Associative Container)中,key和元素都是成對(pair)存在的,你可以調用std::make_pair使用key和元素值來構建一個pair。
STL提供的關聯容器包括set、multiset、map、multimap。
set和map只支持唯一鍵(unique key),即對個key最多隻保存一個元素。multiset和multimap則支持多個key,一個key可以對應多個元素。
set和map的區別在於,在set裏面key和元素是同一個值,而在map裏面key和元素分開存儲。
set是集合的抽象數據結構(ADT)。不同於數學意義上的集合,STL中的set的所有的元素都是有序的而且set中所有的元素都是唯一的。
set中的迭代器的種類爲雙向存取迭代器(bidirectional-access-iterator)。
請看下面的例子:
構建一個set
set<string> fruits;
往set中添加數據
fruits.insert(“apple”);
fruits.insert(“orange”);
fruits.insert(“banana”);
輸出set
set<string>::iterator itEnd = fruits.end();
for (set<string>::iterator it=fruits.begin(); it != itEnd; it++)
cout << *it << ” “;
輸出結果
         apple banana orange
multiset和set基本相同,所不同的是,一個multiset中的元素是可以重複的。
multiset中的迭代器的種類爲雙向存取迭代器(bidirectional-access-iterator)。
multiset的操作同set。
請看下面的例子:
構建一個set
set<string> fruits;
往set中添加數據
fruits.insert(“apple”);
fruits.insert(“orange”);
fruits.insert(“apple”);
fruits.insert(“banana”);
fruits.insert(“banana”);
輸出set
set<string>::iterator itEnd = fruits.end();
for (set<string>::iterator it=fruits.begin(); it != itEnd; it++)
cout << *it << ” “;
輸出結果
         apple apple banana banana orange
map是字典的抽象數據結構(ADT)。map中的所有的元素都會根據key自動進行排序,而且所有的元素都是唯一的。map中的所有的元素都是pair,即鍵(key)和值(value)組成的序列(pair中的第一個元素爲key,第二個元素爲value)。
map中的迭代器的種類爲雙向存取迭代器(bidirectional-access-iterator)。
請看下面的例子:
構建一個map
typedef std::map<int,string> EMPLOYEE_MAP;
EMPLOYEE_MAP employees;
往map中添加數據
employees.insert(EMPLOYEE_MAP::value_type(25301, “A”));
employees.insert(EMPLOYEE_MAP::value_type(25302, “B”));
employees.insert(EMPLOYEE_MAP::value_type(25303, “C”));
employees.insert(EMPLOYEE_MAP::value_type(25304, “D”));
employees.insert(EMPLOYEE_MAP::value_type(25305, “E”));
輸出map
EMPLOYEE_MAP::iterator itEnd = employees.end();
for (EMPLOYEE_MAP::iterator it = employees.begin(); it != itEnd; ++it)
{
std::cout << it->first << “-“;
        std::cout << it->second << endl;
}
輸出結果
         25301-A
25302-B
25303-C
25304-D
25305-E
multimap和map基本相同,所不同的是,一個multimap中的key可以重複。
multimap中的迭代器的種類爲雙向存取迭代器(bidirectional-access-iterator)。
multimap的操作同set。
這裏要提到的就是hashtable,即哈希表的抽象數據結構(ADT)。
因爲hashtable不是STL標準的一部分,部分編譯器(如Microsoft Visual C++)並沒有提供hashtable的實現。
hashtable中的迭代器的種類爲前向迭代器(forward-iterator)。
在SGI的STL中,有hashtable的實現,其中的hash_set,hash_map,hash_multiset,
hash_multimap都是基於hashtable而構建的,是hashtable的適配器(Adaptor)。而在
Microsoft Visual C++的STL中,並沒有提供STL的實現。其中的hash_set,hash_map,hash_multiset,hash_multimap都是基於STL中的hash算法而構建的。
hash_set的操作基本同set;
hash_map的操作基本同map;
hash_multiset的操作基本同multiset;
hash_multimap的操作基本同multimap。
STL中的算法以迭代器爲參數,通過作用於迭代器而達到處理容器數據的目的。所有的算法都前兩個參數都是以對迭代器(iterator),通常稱爲first和last。事實上,算法所處理的迭代器範圍爲[first,last),如果first==last則表明此範圍爲空。按照處理問題的不同,STL算法可分爲四組,分別是:
1.         不改變序列的算法(None-mutating sequence Algorithm)
2.         改變序列的算法(Mutating sequence Algorithm)
3.         排序以及相關算法(Sorting and related Algorithm)
4.         常用數字算法(Generalized numeric Algorithm)
很多算法都有if版本,如remove_if,這類算法會根據判斷條件進行相應的操作;有一些算法具有copy_if或copy版本,不同於非copy_if或copy版本的是,這些操作將獲取的值改寫另外的迭代器。
本文並不打算對這些算法作詳細的介紹,只是對這些算法做簡單性的描述。更多的內容,推薦參考《C++ Primer》或《STL和泛型程序設計》。
這類算法以輸入迭代器(input-iterator)爲輸入參數,但不改寫迭代器迭代器範圍內的數據。典型的算法有:find、count、equal、search、for_each等。
這類算法以輸入迭代器(input-iterator)爲輸入參數,往往還帶有一個輸出迭代器(output-iterator),對輸入迭代器(input-iterator)處理的結果寫入輸出迭代器。典型的算法有:copy、transform、remove、rotate、reverse、fill、replace等。
這類算法往往以雙向迭代器(bidirectional-iterator)爲輸入輸出參數,對迭代器範圍內的數據進行排序,最終序列裏面的值都會發生改變。之所以把排序以及相關算法和”不改變序列的算法”分離出來是因爲這類算法解決了是算法領域的一類問題,而且這類問題在算法領域的重要性異常突出。典型的算法有:sort、partial_sort、nth_element、qsort、stable_sort、partition、merge、inplace_merge等。
這類算法往往以雙向迭代器(bidirectional-iterator)爲輸入輸出參數,對迭代器範圍內的數據進行數字操作(如內積等),最終序列裏面的值都會發生改變。這類算法解決的是數學領域相關的問題,因此被劃爲一類。典型的算法有:next_permutation、pre_permutation、inner_product等。
適配器(Adaptor)是爲了增強功能或/和添加約束,對容器、迭代器、算法重新進行了包裝的模板類。設計模式中的Decorate模式的基本思想是實現適配器(Adaptor)的理論/實踐基礎。在STL中,適配器可分爲三類:容器適配器、迭代器適配器以及算法適配器。
容器適配器是對特定的容器進行Decorate而形成的新的模板類。常見的容器適配器有:
1.         vector適配器
stack:堆棧,是一種FILO的數據結構。
2.         deque適配器
priority_queue:優先級隊列,隊列裏面的所有數據都是按照優先級進行排序的。
queue:單向隊列。
3.         list適配器
slist:單向鏈表。
4.         hashtable的適配器
hash_map,hash_set,hash_multimap,hash_multiset
迭代器適配器是對特定的迭代器進行Decorate而形成的新的模板類。常見的迭代器適配器有:
1.         反向迭代器(reverse iterator)
相對迭代器(iterator),反向迭代器(reverse iterator)是按照相反的順序對容器的數據進行訪問的,如
vector<int>::reverse_iterator ritBegin = vec.rbegin();
vector<int>::reverse_iterator ritEnd = vec.rend();
2.         插入迭代器(insert iterator)
插入迭代器簡化了向容器中插入元素的工作,指定了向容器中插入元素的位置。STL中有三種插入迭代器:
前向插入迭代器
在容器的前面插入元素,如調用容器的push_front
後向插入迭代器
在容器的後面插入元素,如調用容器的push_back
任意插入迭代器
在容器的任意位置插入元素,如調用容器的insert
3.         原始存儲迭代器(raw storage itertor)
允許算法將其結果保存到沒有初始化的內存中。
函數對象適配器是對某類特定函數對象進行Decorate而形成的新的模板類。常見的迭代器適配器有:
1.         否定者 (negator)
STL中有兩個否定者not1和not2,分別對一元和二元謂詞(predicate)的執行結果取反。
2.         綁定者(Binder)
STL中有兩個綁定者binder1st和binder2nd(你可以通過調用函數bind1st和bind2nd分別構建這兩個綁定者),分別將特定值綁定到函數對象的第1個和第2個參數s。
3.         函數指針函數適配器(Adaptors for pointers to function)
因爲STL中的算法一般都使用函數對象作爲參數。如果需要使用原始的函數指針用於這些算法的輸入參數,那麼我們就需要將這些原始指針轉化成特定的函數對象。STL提供了這樣的轉換函數,包括:
轉化成員函數:mem_fun,mem_fun_ref,mem_fun1,mem_fun1_ref
轉化非成員函數:ptr_fun
1.      STL中文站:http://www.stlchina.org
2.      Boosthttp://www.boost.org/
3.      SGI STLhttp://www.sgi.com/tech/stl/
4.      STL porthttp://www.stlport.org/
        </div>
            </div>
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章