龙盟编程博客 | 无障碍搜索 | 云盘搜索神器
快速搜索
主页 > 软件开发 > C/C++开发 >

STL组件之算法(1)

时间:2011-04-12 23:18来源:未知 作者:admin 点击:
分享到:
STL 提供了大量的模板类和函数,可以在OOP和常规编程中使用。所有的STL的大约50个算法都是完全通用的,而且不依赖于任何特定的数据类型。下面的小节说明了三个基本的STL组件: 1)

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算法能够回调自己的函数。例如

  1. #include <iostream.h>  
  2. #include <stdlib.h> // Need random(), srandom()  
  3. #include <time.h> // Need time()  
  4. #include <vector> // Need vector  
  5. #include <algorithm> // Need for_each()  
  6.  
  7. #define VSIZE 24 // Size of vector  
  8. vector<long> v(VSIZE); // Vector object  
  9.  
  10. // Function prototypes  
  11. void initialize(long &ri);  
  12. void show(const long &ri);  
  13. bool isMinus(const long &ri); // Predicate function  
  14.  
  15. int main()  
  16. {  
  17. srandom( time(NULL) ); // Seed random generator  
  18.  
  19. for_each(v.begin(), v.end(), initialize);//调用普通函数  
  20. cout << "Vector of signed long integers" << endl;  
  21. for_each(v.begin(), v.end(), show);  
  22. cout << endl;  
  23.  
  24. // Use predicate function to count negative values  
  25. //  
  26. int count = 0;  
  27. vector<long>::iterator p;  
  28. p = find_if(v.begin(), v.end(), isMinus);//调用断言函数  
  29. while (p != v.end()) {  
  30. count++;  
  31. p = find_if(p + 1, v.end(), isMinus);  
  32. }  
  33. cout << "Number of values: " << VSIZE << endl;  
  34. cout << "Negative values : " << count << endl;  
  35.  
  36. return 0;  
  37. }  
  38.  
  39. // Set ri to a signed integer value  
  40. void initialize(long &ri)  
  41. {  
  42. ri = ( random() - (RAND_MAX / 2) );  
  43. // ri = random();  
  44. }  
  45.  
  46. // Display value of ri  
  47. void show(const long &ri)  
  48. {  
  49. cout << ri << " ";  
  50. }  
  51.  
  52. // Returns true if ri is less than 0  
  53. bool isMinus(const long &ri)  
  54. {  
  55. return (ri < 0);  

所谓断言函数,就是返回bool值的函数。

函数对象

除了给STL算法传递一个回调函数,你还可能需要传递一个类对象以便执行更复杂的操作。这样的一个对象就叫做函数对象。实际上函数对象就是一个类,但它和回调函数一样可以被回调。例如,在函数对象每次被for_each()或find_if()函数调用时可以保留统计信息。函数对象是通过重载 operator()()实现的。如果TanyClass定义了opeator()(),那么就可以这么使用:

  1. TAnyClass object; // Construct object  
  2. object(); // Calls TAnyClass::operator()() function  
  3. 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

  1. #include <iostream.h>  
  2. #include <numeric> // Need accumulate()  
  3. #include <vector> // Need vector  
  4. #include <functional> // Need multiplies() (or times())  
  5. #define MAX 10  
  6. vector<long> v(MAX); // Vector object  
  7. int main()  
  8. {  
  9. // Fill vector using conventional loop  
  10. //  
  11. for (int i = 0; i < MAX; i++)  
  12. v[i] = i + 1;  
  13. // Accumulate the sum of contained values  
  14. //  
  15. long sum =  
  16. accumulate(v.begin(), v.end(), 0);  
  17. cout << "Sum of values == " << sum << endl;  
  18. // Accumulate the product of contained values  
  19. //  
  20. long product =  
  21. accumulate(v.begin(), v.end(), 1, multiplies<long>());//注意这行  
  22. cout << "Product of values == " << product << endl;  
  23. return 0;  

编译输出如下:

  1. $ g++ accum.cpp  
  2. $ ./a.out  
  3. Sum of values == 55  
  4. Product of values == 3628800 

『注意使用了函数对象的accumulate()的用法。accumulate() 在内部将每个容器中的对象和第三个参数作为multiplies函数对象的参数,multiplies(1,v)计算乘积。VC中的这些模板的源代码如下:

  1. // TEMPLATE FUNCTION accumulate  
  2. template<class _II, class _Ty> inline 
  3. _Ty accumulate(_II _F, _II _L, _Ty _V)  
  4. {for (; _F != _L; ++_F)  
  5. _V = _V + *_F;  
  6. return (_V); }  
  7. // TEMPLATE FUNCTION accumulate WITH BINOP  
  8. template<class _II, class _Ty, class _Bop> inline 
  9. _Ty accumulate(_II _F, _II _L, _Ty _V, _Bop _B)  
  10. {for (; _F != _L; ++_F)  
  11. _V = _B(_V, *_F);  
  12. return (_V); }  
  13. // TEMPLATE STRUCT binary_function  
  14. template<class _A1, class _A2, class _R>  
  15. struct binary_function {  
  16. typedef _A1 first_argument_type;  
  17. typedef _A2 second_argument_type;  
  18. typedef _R result_type;  
  19. };  
  20. // TEMPLATE STRUCT multiplies  
  21. template<class _Ty>  
  22. struct multiplies : binary_function<_Ty, _Ty, _Ty> {  
  23. _Ty operator()(const _Ty& _X, const _Ty& _Y) const 
  24. {return (_X * _Y); }  
  25. }; 

引言:如果你想深入了解STL到底是怎么实现的,最好的办法是写个简单的程序,将程序中涉及到的模板源码给copy下来,稍作整理,就能看懂了。所以没有必要去买什么《STL源码剖析》之类的书籍,那些书可能反而浪费时间。』

(1)发生器函数对象

有一类有用的函数对象是“发生器”(generator)。这类函数有自己的内存,也就是说它能够从先前的调用中记住一个值。例如随机数发生器函数。

普通的C程序员使用静态或全局变量 “记忆”上次调用的结果。但这样做的缺点是该函数无法和它的数据相分离『还有个缺点是要用TLS才能线程安全』。显然,使用类来封装一块:“内存”更安全可靠。先看一下例子:

Listing 9. randfunc.cpp

  1. #include <iostream.h>  
  2. #include <stdlib.h> // Need random(), srandom()  
  3. #include <time.h> // Need time()  
  4. #include <algorithm> // Need random_shuffle()  
  5. #include <vector> // Need vector  
  6. #include <functional> // Need ptr_fun()  
  7. using namespace std;  
  8. // Data to randomize  
  9. int iarray[10] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};  
  10. vector<int> v(iarray, iarray + 10);  
  11. // Function prototypes  
  12. void Display(vector<int>& vr, const char *s);  
  13. unsigned int RandInt(const unsigned int n);  
  14. int main()  
  15. {  
  16. srandom( time(NULL) ); // Seed random generator  
  17. Display(v, "Before shuffle:");  
  18. pinter_to_unary_function<unsigned int, unsigned int>  
  19. ptr_RandInt = ptr_fun(RandInt); // Pointer to RandInt()//注意这行  
  20. random_shuffle(v.begin(), v.end(), ptr_RandInt);  
  21. Display(v, "After shuffle:");  
  22. return 0;  
  23. }  
  24. // Display contents of vector vr  
  25. void Display(vector<int>& vr, const char *s)  
  26. {  
  27. cout << endl << s << endl;  
  28. copy(vr.begin(), vr.end(), ostream_iterator<int>(cout, " "));  
  29. cout << endl;  
  30. }  
  31. // Return next random value in sequence modulo n  
  32. unsigned int RandInt(const unsigned int n)  
  33. {  
  34. return random() % n;  

编译运行结果如下:

  1. $ g++ randfunc.cpp  
  2. $ ./a.out  
  3. Before shuffle:  
  4. 1 2 3 4 5 6 7 8 9 10  
  5. After shuffle:  
  6. 6 7 2 8 3 5 10 1 9 4 

首先用下面的语句申明一个对象:

  1. pointer_to_unary_function<unsigned int, unsigned int>  
  2. ptr_RandInt = ptr_fun(RandInt); 

这儿使用STL的单目函数模板定义了一个变量ptr_RandInt,并将地址初始化到我们的函数RandInt()。单目函数接受一个参数,并返回一个值。现在random_shuffle()可以如下调用:

  1. random_shuffle(v.begin(), v.end(), ptr_RandInt); 

在本例子中,发生器只是简单的调用rand()函数。

关于常量引用的一点小麻烦(不翻译了,VC下将例子中的const去掉)


精彩图集

赞助商链接