STL組件之算法
STL提供了大量的模板類(lèi)和函數(shù),可以在OOP和常規(guī)編程中使用。所有的STL的大約50個(gè)算法都是完全通用的,而且不依賴(lài)于任何特定的數(shù)據(jù)類(lèi)型。下面的小節(jié)說(shuō)明了三個(gè)基本的STL組件:
1)迭代器提供了訪問(wèn)容器中對(duì)象的方法。例如,可以使用一對(duì)迭代器指定list或vector中的一定范圍的對(duì)象。迭代器就如同一個(gè)指針。事實(shí)上,C++的指針也是一種迭代器。但是,迭代器也可以是那些定義了operator*()以及其他類(lèi)似于指針的操作符地方法的類(lèi)對(duì)象。
2)容器是一種數(shù)據(jù)結(jié)構(gòu),如list,vector,和deques ,以模板類(lèi)的方法提供。為了訪問(wèn)容器中的數(shù)據(jù),可以使用由容器類(lèi)輸出的迭代器。
3)算法是用來(lái)操作容器中的數(shù)據(jù)的模板函數(shù)。例如,STL用sort()來(lái)對(duì)一個(gè)vector中的數(shù)據(jù)進(jìn)行排序,用find()來(lái)搜索一個(gè)list中的對(duì)象。函數(shù)本身與他們操作的數(shù)據(jù)的結(jié)構(gòu)和類(lèi)型無(wú)關(guān),因此他們可以在從簡(jiǎn)單數(shù)組到高度復(fù)雜容器的任何數(shù)據(jù)結(jié)構(gòu)上使用。
函數(shù)和函數(shù)對(duì)象
STL中,函數(shù)被稱(chēng)為算法,也就是說(shuō)它們和標(biāo)準(zhǔn)C庫(kù)函數(shù)相比,它們更為通用。STL算法通過(guò)重載operator()函數(shù)實(shí)現(xiàn)為模板類(lèi)或模板函數(shù)。這些類(lèi)用于創(chuàng)建函數(shù)對(duì)象,對(duì)容器中的數(shù)據(jù)進(jìn)行各種各樣的操作。下面的幾節(jié)解釋如何使用函數(shù)和函數(shù)對(duì)象。
函數(shù)和斷言
經(jīng)常需要對(duì)容器中的數(shù)據(jù)進(jìn)行用戶(hù)自定義的操作。例如,你可能希望遍歷一個(gè)容器中所有對(duì)象的STL算法能夠回調(diào)自己的函數(shù)。例如
- #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);//調(diào)用普通函數(shù)
- 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);//調(diào)用斷言函數(shù)
- 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);
- }
所謂斷言函數(shù),就是返回bool值的函數(shù)。
函數(shù)對(duì)象
除了給STL算法傳遞一個(gè)回調(diào)函數(shù),你還可能需要傳遞一個(gè)類(lèi)對(duì)象以便執(zhí)行更復(fù)雜的操作。這樣的一個(gè)對(duì)象就叫做函數(shù)對(duì)象。實(shí)際上函數(shù)對(duì)象就是一個(gè)類(lèi),但它和回調(diào)函數(shù)一樣可以被回調(diào)。例如,在函數(shù)對(duì)象每次被for_each()或find_if()函數(shù)調(diào)用時(shí)可以保留統(tǒng)計(jì)信息。函數(shù)對(duì)象是通過(guò)重載 operator()()實(shí)現(xiàn)的。如果TanyClass定義了opeator()(),那么就可以這么使用:
- TAnyClass object; // Construct object
- object(); // Calls TAnyClass::operator()() function
- for_each(v.begin(), v.end(), object);
STL定義了幾個(gè)函數(shù)對(duì)象。由于它們是模板,所以能夠用于任何類(lèi)型,包括C/C++固有的數(shù)據(jù)類(lèi)型,如long。有些函數(shù)對(duì)象從名字中就可以看出它的用途,如plus()和multiplies()。類(lèi)似的greater()和less-equal()用于比較兩個(gè)值。
注意
有些版本的ANSI C++定義了times()函數(shù)對(duì)象,而GNU C++把它命名為multiplies()。使用時(shí)必須包含頭文件<functional>。
一個(gè)有用的函數(shù)對(duì)象的應(yīng)用是accumulate() 算法。該函數(shù)計(jì)算容器中所有值的總和。記住這樣的值不一定是簡(jiǎn)單的類(lèi)型,通過(guò)重載operator+(),也可以是類(lèi)對(duì)象。
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
『注意使用了函數(shù)對(duì)象的accumulate()的用法。accumulate() 在內(nèi)部將每個(gè)容器中的對(duì)象和第三個(gè)參數(shù)作為multiplies函數(shù)對(duì)象的參數(shù),multiplies(1,v)計(jì)算乘積。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到底是怎么實(shí)現(xiàn)的,***的辦法是寫(xiě)個(gè)簡(jiǎn)單的程序,將程序中涉及到的模板源碼給copy下來(lái),稍作整理,就能看懂了。所以沒(méi)有必要去買(mǎi)什么《STL源碼剖析》之類(lèi)的書(shū)籍,那些書(shū)可能反而浪費(fèi)時(shí)間。』
(1)發(fā)生器函數(shù)對(duì)象
有一類(lèi)有用的函數(shù)對(duì)象是“發(fā)生器”(generator)。這類(lèi)函數(shù)有自己的內(nèi)存,也就是說(shuō)它能夠從先前的調(diào)用中記住一個(gè)值。例如隨機(jī)數(shù)發(fā)生器函數(shù)。
普通的C程序員使用靜態(tài)或全局變量 “記憶”上次調(diào)用的結(jié)果。但這樣做的缺點(diǎn)是該函數(shù)無(wú)法和它的數(shù)據(jù)相分離『還有個(gè)缺點(diǎn)是要用TLS才能線程安全』。顯然,使用類(lèi)來(lái)封裝一塊:“內(nèi)存”更安全可靠。先看一下例子:
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;
- }
編譯運(yùn)行結(jié)果如下:
- $ 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
首先用下面的語(yǔ)句申明一個(gè)對(duì)象:
- pointer_to_unary_function<unsigned int, unsigned int>
- ptr_RandInt = ptr_fun(RandInt);
這兒使用STL的單目函數(shù)模板定義了一個(gè)變量ptr_RandInt,并將地址初始化到我們的函數(shù)RandInt()。單目函數(shù)接受一個(gè)參數(shù),并返回一個(gè)值?,F(xiàn)在random_shuffle()可以如下調(diào)用:
- random_shuffle(v.begin(), v.end(), ptr_RandInt);
在本例子中,發(fā)生器只是簡(jiǎn)單的調(diào)用rand()函數(shù)。
關(guān)于常量引用的一點(diǎn)小麻煩(不翻譯了,VC下將例子中的const去掉)
#p#
(2)發(fā)生器函數(shù)類(lèi)對(duì)象
下面的例子說(shuō)明發(fā)生器函數(shù)類(lèi)對(duì)象的使用。
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;
- }
編譯運(yùn)行輸出如下:
- $ 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 發(fā)生器封裝在一個(gè)類(lèi)中,該類(lèi)能從先前的“使用”中記憶運(yùn)行結(jié)果。在本例中,類(lèi)FiboRand 維護(hù)了一個(gè)數(shù)組和兩個(gè)索引變量I和j。
FiboRand類(lèi)繼承自u(píng)nary_function() 模板:
- template <class Arg>
- class FiboRand : public unary_function<Arg, Arg> {...
Arg是用戶(hù)自定義數(shù)據(jù)類(lèi)型。該類(lèi)還定以了兩個(gè)成員函數(shù),一個(gè)是構(gòu)造函數(shù),另一個(gè)是operator()()函數(shù),該操作符允許random_shuffle()算法象一個(gè)函數(shù)一樣“調(diào)用”一個(gè)FiboRand對(duì)象。
#p#
(3)綁定器函數(shù)對(duì)象
一個(gè)綁定器使用另一個(gè)函數(shù)對(duì)象f()和參數(shù)值V創(chuàng)建一個(gè)函數(shù)對(duì)象。被綁定函數(shù)對(duì)象必須為雙目函數(shù),也就是說(shuō)有兩個(gè)參數(shù),A和B。STL 中的幫定器有:
- bind1st() 創(chuàng)建一個(gè)函數(shù)對(duì)象,該函數(shù)對(duì)象將值V作為***個(gè)參數(shù)A。
- bind2nd()創(chuàng)建一個(gè)函數(shù)對(duì)象,該函數(shù)對(duì)象將值V作為第二個(gè)參數(shù)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()計(jì)算滿(mǎn)足特定條件的元素的數(shù)目。 這是通過(guò)將一個(gè)函數(shù)對(duì)象和一個(gè)參數(shù)捆綁到為一個(gè)對(duì)象,并將該對(duì)象作為算法的第三個(gè)參數(shù)實(shí)現(xiàn)的。 注意這個(gè)表達(dá)式:
- bind1st(greater<int>(), 8)
該表達(dá)式將greater<int>()和一個(gè)參數(shù)值8捆綁為一個(gè)函數(shù)對(duì)象。由于使用了bind1st(),所以該函數(shù)相當(dāng)于計(jì)算下述表達(dá)式:
8 > q
表達(dá)式中的q是容器中的對(duì)象。因此,完整的表達(dá)式
- count_if(aList.begin(), aList.end(),
- bind1st(greater<int>(), 8), k);
計(jì)算所有小于或等于8的對(duì)象的數(shù)目。
(4)否定函數(shù)對(duì)象
所謂否定(negator)函數(shù)對(duì)象,就是它從另一個(gè)函數(shù)對(duì)象創(chuàng)建而來(lái),如果原先的函數(shù)返回真,則否定函數(shù)對(duì)象返回假。有兩個(gè)否定函數(shù)對(duì)象:not1()和 not2()。not1()接受單目函數(shù)對(duì)象,not2()接受雙目函數(shù)對(duì)象。否定函數(shù)對(duì)象通常和幫定器一起使用。例如,上節(jié)中用bind1nd來(lái)搜索 q<=8的值:
- count_if(aList.begin(), aList.end(),
- bind1st(greater<int>(), 8), k);
如果要搜索q>8的對(duì)象,則用bind2st。而現(xiàn)在可以這樣寫(xiě):
- start = find_if(aList.begin(), aList.end(),
- not1(bind1nd(greater<int>(), 6)));
你必須使用not1,因?yàn)閎ind1nd返回單目函數(shù)。
總結(jié):使用標(biāo)準(zhǔn)模板庫(kù) (STL)
盡管很多程序員仍然在使用標(biāo)準(zhǔn)C函數(shù),但是這就好像騎著毛驢尋找Mercedes一樣。你當(dāng)然最終也會(huì)到達(dá)目標(biāo),但是你浪費(fèi)了很多時(shí)間。
盡管有時(shí)候使用標(biāo)準(zhǔn)C函數(shù)確實(shí)方便(如使用sprintf()進(jìn)行格式化輸出)。但是C函數(shù)不使用異常機(jī)制來(lái)報(bào)告錯(cuò)誤,也不適合處理新的數(shù)據(jù)類(lèi)型。而且標(biāo)準(zhǔn)C函數(shù)經(jīng)常使用內(nèi)存分配技術(shù),沒(méi)有經(jīng)驗(yàn)的程序員很容易寫(xiě)出bug來(lái)。.
C++標(biāo)準(zhǔn)庫(kù)則提供了更為安全,更為靈活的數(shù)據(jù)集處理方式。STL最初由HP實(shí)驗(yàn)室的Alexander Stepanov和Meng Lee開(kāi)發(fā)。最近,C++標(biāo)準(zhǔn)委員會(huì)采納了STL,盡管在不同的實(shí)現(xiàn)之間仍有細(xì)節(jié)差別。
STL的最主要的兩個(gè)特點(diǎn):數(shù)據(jù)結(jié)構(gòu)和算法的分離,非面向?qū)ο蟊举|(zhì)。訪問(wèn)對(duì)象是通過(guò)象指針一樣的迭代器實(shí)現(xiàn)的;容器是象鏈表,矢量之類(lèi)的數(shù)據(jù)結(jié)構(gòu),并按模板方式提供;算法是函數(shù)模板,用于操作容器中的數(shù)據(jù)。由于STL以模板為基礎(chǔ),所以能用于任何數(shù)據(jù)類(lèi)型和結(jié)構(gòu)。
【編輯推薦】
- 檢測(cè)C++中的內(nèi)存泄漏
- c/c++基礎(chǔ) 預(yù)處理指令總結(jié)
- 詳細(xì)介紹c++中的類(lèi)對(duì)象內(nèi)存模型
- C++基礎(chǔ) 詳細(xì)介紹const的用法
- C++初學(xué)者 const使用詳解