您好,登錄后才能下訂單哦!
這篇文章主要介紹了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源碼剖析》之類的書籍,那些書可能反而浪費時間。』
(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()。單目函數接受一個參數,并返回一個值。現在random_shuffle()可以如下調用:
random_shuffle(v.begin(), v.end(), ptr_RandInt);
在本例子中,發生器只是簡單的調用rand()函數。
關于常量引用的一點小麻煩(不翻譯了,VC下將例子中的const去掉)
(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對象。
(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以模板為基礎,所以能用于任何數據類型和結構。
感謝你能夠認真閱讀完這篇文章,希望小編分享的“STL組件之算法的示例分析”這篇文章對大家有幫助,同時也希望大家多多支持億速云,關注億速云行業資訊頻道,更多相關知識等著你來學習!
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。