STL組件之算法
STL提供了大量的模板類和函數,可以在OOP和常規編程中使用。所有的STL的大約50個算法都是完全通用的,而且不依賴于任何特定的數據類型。下面的小節說明了三個基本的STL組件:
1)迭代器提供了訪問容器中對象的方法。例如,可以使用一對迭代器指定list或vector中的一定范圍的對象。迭代器就如同一個指針。事實上,C++的指針也是一種迭代器。但是,迭代器也可以是那些定義了operator*()以及其他類似于指針的操作符地方法的類對象。
2)容器是一種數據結構,如list,vector,和deques ,以模板類的方法提供。為了訪問容器中的數據,可以使用由容器類輸出的迭代器。
3)算法是用來操作容器中的數據的模板函數。例如,STL用sort()來對一個vector中的數據進行排序,用find()來搜索一個list中的對象。函數本身與他們操作的數據的結構和類型無關,因此他們可以在從簡單數組到高度復雜容器的任何數據結構上使用。
函數和函數對象
STL中,函數被稱為算法,也就是說它們和標準C庫函數相比,它們更為通用。STL算法通過重載operator()函數實現為模板類或模板函數。這些類用于創建函數對象,對容器中的數據進行各種各樣的操作。下面的幾節解釋如何使用函數和函數對象。
函數和斷言
經常需要對容器中的數據進行用戶自定義的操作。例如,你可能希望遍歷一個容器中所有對象的STL算法能夠回調自己的函數。例如
- #include <iostream.h>
- #include <stdlib.h> // Need random(), srandom()
- #include <time.h> // Need time()
- #include <vector> // Need vector
- #include <algorithm> // Need for_each()
- #define VSIZE 24 // Size of vector
- vector<long> v(VSIZE); // Vector object
- // Function prototypes
- void initialize(long &ri);
- void show(const long &ri);
- bool isMinus(const long &ri); // Predicate function
- int main()
- {
- srandom( time(NULL) ); // Seed random generator
- for_each(v.begin(), v.end(), initialize);//調用普通函數
- cout << "Vector of signed long integers" << endl;
- for_each(v.begin(), v.end(), show);
- cout << endl;
- // Use predicate function to count negative values
- //
- int count = 0;
- vector<long>::iterator p;
- p = find_if(v.begin(), v.end(), isMinus);//調用斷言函數
- while (p != v.end()) {
- count++;
- p = find_if(p + 1, v.end(), isMinus);
- }
- cout << "Number of values: " << VSIZE << endl;
- cout << "Negative values : " << count << endl;
- return 0;
- }
- // Set ri to a signed integer value
- void initialize(long &ri)
- {
- ri = ( random() - (RAND_MAX / 2) );
- // ri = random();
- }
- // Display value of ri
- void show(const long &ri)
- {
- cout << ri << " ";
- }
- // Returns true if ri is less than 0
- bool isMinus(const long &ri)
- {
- return (ri < 0);
- }
所謂斷言函數,就是返回bool值的函數。
函數對象
除了給STL算法傳遞一個回調函數,你還可能需要傳遞一個類對象以便執行更復雜的操作。這樣的一個對象就叫做函數對象。實際上函數對象就是一個類,但它和回調函數一樣可以被回調。例如,在函數對象每次被for_each()或find_if()函數調用時可以保留統計信息。函數對象是通過重載 operator()()實現的。如果TanyClass定義了opeator()(),那么就可以這么使用:
- TAnyClass object; // Construct object
- object(); // Calls TAnyClass::operator()() function
- for_each(v.begin(), v.end(), object);
STL定義了幾個函數對象。由于它們是模板,所以能夠用于任何類型,包括C/C++固有的數據類型,如long。有些函數對象從名字中就可以看出它的用途,如plus()和multiplies()。類似的greater()和less-equal()用于比較兩個值。
注意
有些版本的ANSI C++定義了times()函數對象,而GNU C++把它命名為multiplies()。使用時必須包含頭文件<functional>。
一個有用的函數對象的應用是accumulate() 算法。該函數計算容器中所有值的總和。記住這樣的值不一定是簡單的類型,通過重載operator+(),也可以是類對象。
Listing 8. accum.cpp
- #include <iostream.h>
- #include <numeric> // Need accumulate()
- #include <vector> // Need vector
- #include <functional> // Need multiplies() (or times())
- #define MAX 10
- vector<long> v(MAX); // Vector object
- int main()
- {
- // Fill vector using conventional loop
- //
- for (int i = 0; i < MAX; i++)
- v[i] = i + 1;
- // Accumulate the sum of contained values
- //
- long sum =
- accumulate(v.begin(), v.end(), 0);
- cout << "Sum of values == " << sum << endl;
- // Accumulate the product of contained values
- //
- long product =
- accumulate(v.begin(), v.end(), 1, multiplies<long>());//注意這行
- cout << "Product of values == " << product << endl;
- return 0;
- }
編譯輸出如下:
- $ g++ accum.cpp
- $ ./a.out
- Sum of values == 55
- Product of values == 3628800
『注意使用了函數對象的accumulate()的用法。accumulate() 在內部將每個容器中的對象和第三個參數作為multiplies函數對象的參數,multiplies(1,v)計算乘積。VC中的這些模板的源代碼如下:
- // TEMPLATE FUNCTION accumulate
- template<class _II, class _Ty> inline
- _Ty accumulate(_II _F, _II _L, _Ty _V)
- {for (; _F != _L; ++_F)
- _V = _V + *_F;
- return (_V); }
- // TEMPLATE FUNCTION accumulate WITH BINOP
- template<class _II, class _Ty, class _Bop> inline
- _Ty accumulate(_II _F, _II _L, _Ty _V, _Bop _B)
- {for (; _F != _L; ++_F)
- _V = _B(_V, *_F);
- return (_V); }
- // TEMPLATE STRUCT binary_function
- template<class _A1, class _A2, class _R>
- struct binary_function {
- typedef _A1 first_argument_type;
- typedef _A2 second_argument_type;
- typedef _R result_type;
- };
- // TEMPLATE STRUCT multiplies
- template<class _Ty>
- struct multiplies : binary_function<_Ty, _Ty, _Ty> {
- _Ty operator()(const _Ty& _X, const _Ty& _Y) const
- {return (_X * _Y); }
- };
引言:如果你想深入了解STL到底是怎么實現的,***的辦法是寫個簡單的程序,將程序中涉及到的模板源碼給copy下來,稍作整理,就能看懂了。所以沒有必要去買什么《STL源碼剖析》之類的書籍,那些書可能反而浪費時間?!?/p>
(1)發生器函數對象
有一類有用的函數對象是“發生器”(generator)。這類函數有自己的內存,也就是說它能夠從先前的調用中記住一個值。例如隨機數發生器函數。
普通的C程序員使用靜態或全局變量 “記憶”上次調用的結果。但這樣做的缺點是該函數無法和它的數據相分離『還有個缺點是要用TLS才能線程安全』。顯然,使用類來封裝一塊:“內存”更安全可靠。先看一下例子:
Listing 9. randfunc.cpp
- #include <iostream.h>
- #include <stdlib.h> // Need random(), srandom()
- #include <time.h> // Need time()
- #include <algorithm> // Need random_shuffle()
- #include <vector> // Need vector
- #include <functional> // Need ptr_fun()
- using namespace std;
- // Data to randomize
- int iarray[10] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
- vector<int> v(iarray, iarray + 10);
- // Function prototypes
- void Display(vector<int>& vr, const char *s);
- unsigned int RandInt(const unsigned int n);
- int main()
- {
- srandom( time(NULL) ); // Seed random generator
- Display(v, "Before shuffle:");
- pinter_to_unary_function<unsigned int, unsigned int>
- ptr_RandInt = ptr_fun(RandInt); // Pointer to RandInt()//注意這行
- random_shuffle(v.begin(), v.end(), ptr_RandInt);
- Display(v, "After shuffle:");
- return 0;
- }
- // Display contents of vector vr
- void Display(vector<int>& vr, const char *s)
- {
- cout << endl << s << endl;
- copy(vr.begin(), vr.end(), ostream_iterator<int>(cout, " "));
- cout << endl;
- }
- // Return next random value in sequence modulo n
- unsigned int RandInt(const unsigned int n)
- {
- return random() % n;
- }
編譯運行結果如下:
- $ g++ randfunc.cpp
- $ ./a.out
- Before shuffle:
- 1 2 3 4 5 6 7 8 9 10
- After shuffle:
- 6 7 2 8 3 5 10 1 9 4
首先用下面的語句申明一個對象:
- pointer_to_unary_function<unsigned int, unsigned int>
- ptr_RandInt = ptr_fun(RandInt);
這兒使用STL的單目函數模板定義了一個變量ptr_RandInt,并將地址初始化到我們的函數RandInt()。單目函數接受一個參數,并返回一個值?,F在random_shuffle()可以如下調用:
- random_shuffle(v.begin(), v.end(), ptr_RandInt);
在本例子中,發生器只是簡單的調用rand()函數。
關于常量引用的一點小麻煩(不翻譯了,VC下將例子中的const去掉)
#p#
(2)發生器函數類對象
下面的例子說明發生器函數類對象的使用。
Listing 10. fiborand.cpp
- #include <iostream.h>
- #include <algorithm> // Need random_shuffle()
- #include <vector> // Need vector
- #include <functional> // Need unary_function
- using namespace std;
- // Data to randomize
- int iarray[10] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
- vector<int> v(iarray, iarray + 10);
- // Function prototype
- void Display(vector<int>& vr, const char *s);
- // The FiboRand template function-object class
- template <class Arg>
- class FiboRand : public unary_function<Arg, Arg> {
- int i, j;
- Arg sequence[18];
- public:
- FiboRand();
- Arg operator()(const Arg& arg);
- };
- void main()
- {
- FiboRand<int> fibogen; // Construct generator object
- cout << "Fibonacci random number generator" << endl;
- cout << "using random_shuffle and a function object" << endl;
- Display(v, "Before shuffle:");
- random_shuffle(v.begin(), v.end(), fibogen);
- Display(v, "After shuffle:");
- }
- // Display contents of vector vr
- void Display(vector<int>& vr, const char *s)
- {
- cout << endl << s << endl;
- copy(vr.begin(), vr.end(),
- ostream_iterator<int>(cout, " "));
- cout << endl;
- }
- // FiboRand class constructor
- template<class Arg>
- FiboRand<Arg>::FiboRand()
- {
- sequence[17] = 1;
- sequence[16] = 2;
- for (int n = 15; n > 0; n—)
- sequence[n] = sequence[n + 1] + sequence[n + 2];
- i = 17;
- j = 5;
- }
- // FiboRand class function operator
- template<class Arg>
- Arg FiboRand<Arg>::operator()(const Arg& arg)
- {
- Arg k = sequence[i] + sequence[j];
- sequence[i] = k;
- i--;
- j--;
- if (i == 0) i = 17;
- if (j == 0) j = 17;
- return k % arg;
- }
編譯運行輸出如下:
- $ g++ fiborand.cpp
- $ ./a.out
- Fibonacci random number generator
- using random_shuffle and a function object
- Before shuffle:
- 1 2 3 4 5 6 7 8 9 10
- After shuffle:
- 6 8 5 4 3 7 10 1 9
該程序用完全不通的方法使用使用rand_shuffle。Fibonacci 發生器封裝在一個類中,該類能從先前的“使用”中記憶運行結果。在本例中,類FiboRand 維護了一個數組和兩個索引變量I和j。
FiboRand類繼承自unary_function() 模板:
- template <class Arg>
- class FiboRand : public unary_function<Arg, Arg> {...
Arg是用戶自定義數據類型。該類還定以了兩個成員函數,一個是構造函數,另一個是operator()()函數,該操作符允許random_shuffle()算法象一個函數一樣“調用”一個FiboRand對象。
#p#
(3)綁定器函數對象
一個綁定器使用另一個函數對象f()和參數值V創建一個函數對象。被綁定函數對象必須為雙目函數,也就是說有兩個參數,A和B。STL 中的幫定器有:
- bind1st() 創建一個函數對象,該函數對象將值V作為***個參數A。
- bind2nd()創建一個函數對象,該函數對象將值V作為第二個參數B。
舉例如下:
Listing 11. binder.cpp
- #include <iostream.h>
- #include <algorithm>
- #include <functional>
- #include <list>
- using namespace std;
- // Data
- int iarray[10] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
- list<int> aList(iarray, iarray + 10);
- int main()
- {
- int k = 0;
- count_if(aList.begin(), aList.end(),
- bind1st(greater<int>(), 8), k);
- cout << "Number elements < 8 == " << k << endl;
- return 0;
- }
Algorithm count_if()計算滿足特定條件的元素的數目。 這是通過將一個函數對象和一個參數捆綁到為一個對象,并將該對象作為算法的第三個參數實現的。 注意這個表達式:
- bind1st(greater<int>(), 8)
該表達式將greater<int>()和一個參數值8捆綁為一個函數對象。由于使用了bind1st(),所以該函數相當于計算下述表達式:
8 > q
表達式中的q是容器中的對象。因此,完整的表達式
- count_if(aList.begin(), aList.end(),
- bind1st(greater<int>(), 8), k);
計算所有小于或等于8的對象的數目。
(4)否定函數對象
所謂否定(negator)函數對象,就是它從另一個函數對象創建而來,如果原先的函數返回真,則否定函數對象返回假。有兩個否定函數對象:not1()和 not2()。not1()接受單目函數對象,not2()接受雙目函數對象。否定函數對象通常和幫定器一起使用。例如,上節中用bind1nd來搜索 q<=8的值:
- count_if(aList.begin(), aList.end(),
- bind1st(greater<int>(), 8), k);
如果要搜索q>8的對象,則用bind2st。而現在可以這樣寫:
- start = find_if(aList.begin(), aList.end(),
- not1(bind1nd(greater<int>(), 6)));
你必須使用not1,因為bind1nd返回單目函數。
總結:使用標準模板庫 (STL)
盡管很多程序員仍然在使用標準C函數,但是這就好像騎著毛驢尋找Mercedes一樣。你當然最終也會到達目標,但是你浪費了很多時間。
盡管有時候使用標準C函數確實方便(如使用sprintf()進行格式化輸出)。但是C函數不使用異常機制來報告錯誤,也不適合處理新的數據類型。而且標準C函數經常使用內存分配技術,沒有經驗的程序員很容易寫出bug來。.
C++標準庫則提供了更為安全,更為靈活的數據集處理方式。STL最初由HP實驗室的Alexander Stepanov和Meng Lee開發。最近,C++標準委員會采納了STL,盡管在不同的實現之間仍有細節差別。
STL的最主要的兩個特點:數據結構和算法的分離,非面向對象本質。訪問對象是通過象指針一樣的迭代器實現的;容器是象鏈表,矢量之類的數據結構,并按模板方式提供;算法是函數模板,用于操作容器中的數據。由于STL以模板為基礎,所以能用于任何數據類型和結構。
【編輯推薦】