函数对象和stl算法应用实例
2014-01-11 11:45
591 查看
Stl中不仅封装了常见的数据结构,也用模板实现了常用的算法,如查找、排序等。其中的算法也非常多,不可能都记全也没必要记,只要知道如何应用如何查找msdn帮助(可以下载也可以在VS中,选中关键词如sort按住F1进入网页版本帮助)即可。
这里的beg和end都必须是迭代器,parms常常是自定义或是系统定义的函数对象(相当于函数调用)。
下面以整数排序为例,sort(vec.begin(),vec.end()),按照正常从大到小排序
可以看到在第二次排序过程中,调用了我们自定义的cmp方法进行排序。这一点在排序对象为自定义时特别有用,比如我们需要排序的是结构体,但是系统不知道如何根据结构体内部进行排序,这就需要我们自己提供排序方法。也就是fun (beg, end, beg2, other parms)模式中的参数。
还有一种方法自定义排序,就是在需要排序的对象或者结构体当中进行运算符重载”<”,运算符重载相当于一个比较器,排序的时候以此作为比较条件。
现在考虑实现一个拷贝的功能,也就是把一个vector中的所有数据拷贝到另一个vector中间去,但是我们加一些限制条件,需要拷贝的整型数据必须大于15。
但是如果下一次我们又改变条件了,条件改成数据必须大于16,那我们必须重新写一个函数gt16;那假设下次又要改成17.....那这样要写的函数岂不是有无穷多个。问题就在于这个比较的值能不能动态的改变,这时就需要我们的function object。
所谓函数对象就是能以函数的形式调用的对象,比如对象class gt_N,需要能够以 gt_N() 这种形式调用。我们可以通过重载运算符()来实现。那我们就来实现下这个gt_N的函数对象吧。
根据函数对象中的运算符operator()使用参数的个数进行分
4000
类:(1)一元判定函数:返回bool型值的一元函数,如判断逻辑是否为真true/false (2)二元判定函数:返回bool型值的二元函数,如判断两个值的大小。
(a)下面以greater为例,将q1中大于2的数字拷贝到q2:
Copy_if(q1.begin(),q1.end(),q2.begin(), bind2nd(greater<int>(),2) );
(b)复制序列中不等于20的数字:
Copy_if(q1.begin(),q1.end(),q2.begin(), not1(bind2nd(equal_to<int>(),20) );
下面对上述两个例子进行解释,在例(a)中使用了函数对象greater,greater是个二元的,也就是比较两个数的大小,其中一个数是vector中遍历的每个数字。那么另外一个需要比较的数字,我们可以让它固定下来(可以绑定第一个或者第二个数值)。绑定bind, 第一个1st,第二个2nd。所以bind2nd(greater<int>(),2)就是将vector中的每个数据和2进行比较,只有大于2的才会拷贝到q2中。
not1(bind2nd(equal_to<int>(),20)) :首先bind2nd(equal_to<int>(),20)表示vector遍历中的每个数字等于2的才进行拷贝到q2,然后not1表示每个数字不等于2的才进行拷贝到q2。
Not1是一元的,not2是二元的,比如下面例子中的less就是两个数字比较。那上面的例子(b)中的equal_to也是两个数字比较啊,但是由于绑定了第二个元素(bind2nd)为20,所以也就变成一元了,即采用not1. sort( v1.begin( ), v1.end( ), not2(less<int>( ) ) );
总结:1.约束器bind1st和bind2nd可以绑定二元函数对象的某一个值。2.谓词否定迭代器,由not1和not2组成并对原来的条件取反,它们分别有一个或两个模板参数。
有时候需要我们自己去写一些函数对象或者函数来处理一些逻辑,但是我们又无法使用已有的bind1st,bind2nd,not1,not2. 这些函数对象适配器都无法应用在我们自己写的函数上,那么如何才能使用呢?
系统提供了普通函数适配器适配器ptr_fun(),它可以将一个我们自己的普通函数变成函数对象并和已有的bind1st,bind2nd,not1,not2.配合使用。下面以判定某个数是n的倍数:
2.2.2成员函数适配器
(1).mem_fun()接受一个对象指针传递过来的成员函数
(2).mem_fun_ref()接受一个对象引用传递过来的成员函数,它们都返回一个函数对象。
1.STL算法的结构形式和排序样例
STL提供的算法库很庞大,要记下这么多的算法是很困难的,所幸它几乎所有的算法都遵循如下的结构形式:fun (beg, end, other parms); fun (beg, end, dest, other parms); fun (beg, end, beg2, other parms); fun (beg, end, beg2, end2, other parms);
这里的beg和end都必须是迭代器,parms常常是自定义或是系统定义的函数对象(相当于函数调用)。
下面以整数排序为例,sort(vec.begin(),vec.end()),按照正常从大到小排序
#include <iostream> #include <vector> #include <algorithm> #include <iterator> using namespace std; bool cmp(int a,int b) { return a > b; } int main3( ) { vector<int> q1; for(int i=0; i<10; i++) { q1.push_back(rand()%100+1);//对q1随机赋值,1-100 } //将q1中的值全部输出到控制台 copy(q1.begin(),q1.end(),ostream_iterator<int>(cout," ")); cout << endl; //正常从小到大排序 sort(q1.begin(),q1.end()); cout << "after sort:" << endl; copy(q1.begin(),q1.end(),ostream_iterator<int>(cout," ")); cout << endl; //调用我们自己提供的排序算法,从大到小排序 sort(q1.begin(),q1.end(),cmp); cout << "after sort by our algorithm:" << endl; copy(q1.begin(),q1.end(),ostream_iterator<int>(cout," ")); cout << endl; return 0; }
可以看到在第二次排序过程中,调用了我们自定义的cmp方法进行排序。这一点在排序对象为自定义时特别有用,比如我们需要排序的是结构体,但是系统不知道如何根据结构体内部进行排序,这就需要我们自己提供排序方法。也就是fun (beg, end, beg2, other parms)模式中的参数。
还有一种方法自定义排序,就是在需要排序的对象或者结构体当中进行运算符重载”<”,运算符重载相当于一个比较器,排序的时候以此作为比较条件。
2.函数对象
既然我们在调用stl算法的时候,已经可以调用自定义函数来实现功能了,为什么还需要这个所谓的“函数对象”呢?调用已有的函数有什么不好吗?什么是函数对象呢?它能给我们带来什么好处呢?现在考虑实现一个拷贝的功能,也就是把一个vector中的所有数据拷贝到另一个vector中间去,但是我们加一些限制条件,需要拷贝的整型数据必须大于15。
但是如果下一次我们又改变条件了,条件改成数据必须大于16,那我们必须重新写一个函数gt16;那假设下次又要改成17.....那这样要写的函数岂不是有无穷多个。问题就在于这个比较的值能不能动态的改变,这时就需要我们的function object。
所谓函数对象就是能以函数的形式调用的对象,比如对象class gt_N,需要能够以 gt_N() 这种形式调用。我们可以通过重载运算符()来实现。那我们就来实现下这个gt_N的函数对象吧。
#include <iostream> #include <vector> #include <algorithm> #include <iterator> using namespace std; bool gt_15(int a) { return a > 15; } class gt_N { int n; public: gt_N(int data):n(data){} bool operator()(int t) { return n > t; } }; int main4( ) { vector<int> q1; vector<int> q2(10); for(int i=0; i<10; i++) { q1.push_back(rand()%100+1);//对q1随机赋值,1-100 } cout << "q1=" ; copy(q1.begin(),q1.end(),ostream_iterator<int>(cout," ")); cout << endl; //将q1中的数据拷贝到q2 copy_if(q1.begin(),q1.end(),q2.begin(),gt_N(50)); cout << endl; //输出q2的值 cout << "copy data > 50 from q1" << endl << "q2 = " << endl; copy(q2.begin(),q2.end(),ostream_iterator<int>(cout," ")); cout << endl; return 0; }
2.1函数对象的分类
一般来说,算法的流程都是将迭代器中的每个元素都执行一遍我们所提供的函数对象或者函数,然后做出相应选择。头文件<functional>定义了大量的通用函数对象,如plus,greater,less,并且可以通过函数对象适配器(bind1st,bind2nd,not1,not2)增强功能。根据函数对象中的运算符operator()使用参数的个数进行分
4000
类:(1)一元判定函数:返回bool型值的一元函数,如判断逻辑是否为真true/false (2)二元判定函数:返回bool型值的二元函数,如判断两个值的大小。
(a)下面以greater为例,将q1中大于2的数字拷贝到q2:
Copy_if(q1.begin(),q1.end(),q2.begin(), bind2nd(greater<int>(),2) );
(b)复制序列中不等于20的数字:
Copy_if(q1.begin(),q1.end(),q2.begin(), not1(bind2nd(equal_to<int>(),20) );
下面对上述两个例子进行解释,在例(a)中使用了函数对象greater,greater是个二元的,也就是比较两个数的大小,其中一个数是vector中遍历的每个数字。那么另外一个需要比较的数字,我们可以让它固定下来(可以绑定第一个或者第二个数值)。绑定bind, 第一个1st,第二个2nd。所以bind2nd(greater<int>(),2)就是将vector中的每个数据和2进行比较,只有大于2的才会拷贝到q2中。
not1(bind2nd(equal_to<int>(),20)) :首先bind2nd(equal_to<int>(),20)表示vector遍历中的每个数字等于2的才进行拷贝到q2,然后not1表示每个数字不等于2的才进行拷贝到q2。
Not1是一元的,not2是二元的,比如下面例子中的less就是两个数字比较。那上面的例子(b)中的equal_to也是两个数字比较啊,但是由于绑定了第二个元素(bind2nd)为20,所以也就变成一元了,即采用not1. sort( v1.begin( ), v1.end( ), not2(less<int>( ) ) );
#include <iostream> #include <vector> #include <algorithm> #include <iterator> #include <map> using namespace std; int main5( ) { vector<int> q1; vector<int> q2(10); vector<int> q3(10); for(int i=0; i<10; i++) { q1.push_back(rand()%100+1);//对q1随机赋值,1-100 } cout << "q1=" ; copy(q1.begin(),q1.end(),ostream_iterator<int>(cout," ")); cout << endl; //将q1中大于50的数据拷贝到q2,采用了自带的函数对象greater和函数对象适配器bind2nd copy_if(q1.begin(),q1.end(),q2.begin(),bind2nd(greater<int>(),50)); cout << endl; //输出q2的值 cout << "copy data > 50 from q1" << endl << "q2 = " << endl; copy(q2.begin(),q2.end(),ostream_iterator<int>(cout," ")); cout << endl; //将q1中不大于50的数据拷贝到q3,采用了自带的函数对象greater和函数对象适配器bind2nd以及not1 copy_if(q1.begin(),q1.end(),q3.begin(),not1(bind2nd(greater<int>(),50))); cout << endl; //输出q3的值 cout << "copy data <= 50 from q1" << endl << "q3 = " << endl; copy(q3.begin(),q3.end(),ostream_iterator<int>(cout," ")); cout << endl; return 0; }
总结:1.约束器bind1st和bind2nd可以绑定二元函数对象的某一个值。2.谓词否定迭代器,由not1和not2组成并对原来的条件取反,它们分别有一个或两个模板参数。
2.2 函数对象的适配器
2.2.1普通函数适配器适配器ptr_fun()有时候需要我们自己去写一些函数对象或者函数来处理一些逻辑,但是我们又无法使用已有的bind1st,bind2nd,not1,not2. 这些函数对象适配器都无法应用在我们自己写的函数上,那么如何才能使用呢?
系统提供了普通函数适配器适配器ptr_fun(),它可以将一个我们自己的普通函数变成函数对象并和已有的bind1st,bind2nd,not1,not2.配合使用。下面以判定某个数是n的倍数:
#include <iostream> #include <vector> #include <algorithm> #include <iterator> #include <map> using namespace std; //判定x是否是n的倍数 bool isNtimes(int x,int n) { return (x % n == 0) ? 1 : 0; } int main6( ) { vector<int> q1; vector<int> q2(10); for(int i=0; i<10; i++) { q1.push_back(rand()%100+1);//对q1随机赋值,1-100 } cout << "q1=" ; copy(q1.begin(),q1.end(),ostream_iterator<int>(cout," ")); cout << endl; //将q1中为2的倍数的数据拷贝到q2,采用了ptr_fun函数对象适配器和bind2nd copy_if(q1.begin(),q1.end(),q2.begin(),bind2nd(ptr_fun(isNtimes),2)); cout << endl; //输出q2的值 cout << "copy data > 50 from q1" << endl << "q2 = " << endl; copy(q2.begin(),q2.end(),ostream_iterator<int>(cout," ")); cout << endl; return 0; }
2.2.2成员函数适配器
(1).mem_fun()接受一个对象指针传递过来的成员函数
#include <iostream> #include <vector> #include <algorithm> #include <iterator> #include <map> using namespace std; class Shape{ public: virtual void draw() { } }; class Circle: public Shape{ public: void draw() { cout << "Circle ...." << endl; } }; class Rect: public Shape{ public: void draw() { cout << "Rect ...." << endl; } }; int main7( ) { vector<Shape*> q1; q1.push_back(new Circle); q1.push_back(new Rect); //对q1中的每个数据,也就是Shape*,执行其成员函数,由于其是虚函数所以执行动态连编 //mem_fun()接受一个对象指针传递过来的成员函数 for_each(q1.begin(),q1.end(),mem_fun(&Shape::draw)); return 0; }
(2).mem_fun_ref()接受一个对象引用传递过来的成员函数,它们都返回一个函数对象。
#include <iostream> #include <vector> #include <algorithm> #include <iterator> #include <map> using namespace std; class Angle { int degree; public: Angle(int d):degree(d){} int multiply(int ntimes) { return degree * ntimes; } }; int main( ) { vector<Angle> q1; for (int i=0; i<50; i+=10) { q1.push_back(Angle(i)); } int x[] = {1,2,3,4,5}; //transform(src1.begin,src1.end,src2.bgin,dest1.begin,FunObj) //将q1和x中的数据相乘,然后输出到控制台 //其中q1和x中对应数据操作时,都调用了方法Angle::multiply实现了相乘的功能 transform(q1.begin(),q1.end(),x,ostream_iterator<int>(cout," "), mem_fun_ref(&Angle::multiply)); return 0; }
3.常见stl算法
一般常用的比如查找find、排序sort,堆运算make_heap, push_heap, pop_heap, sort_heap; 计数count_if; 拷贝copy_if ; 数据处理transform; 删除元素remove_if4.总结
Stl算法中的要点:(1)确定操作源数据的迭代器begin、end和目标存放数据的迭代器begin (2)确定函数对象,也就是对遍历中每个数据需要进行的操作。(3)函数对象约束器,bind1st,bind2nd和谓词否定迭代器not1,not2,可以固定某个操作数的大小(4)函数对象适配器,ptr_fun(),mem_fun(),mem_fun_ref() 将普通函数、成员函数转换为函数对象并且可以和函数对象约束器组合使用。分清楚mem_fun(针对对象指针的成员函数)和mem_fun_ref(对象本身传递过来的成员函数)的区别。
相关文章推荐
- 18函数对象&19command模式20函数对象在STL中的应用
- STL算法设计理念 - 预定义函数对象
- STL 算法中函数对象和谓词
- STL的6大组件:容器、类属算法、迭代器、函数对象、适配器、分配器。
- 模板函数与模板类的区别。模板函数允许隐式调用,所以STL算法允许传入函数指针,也允许传入函数对象
- 18函数对象&19command模式20函数对象在STL中的应用
- 函数对象的意义:泛型算法应用以及避免重载带来的全局影响
- STL算法和函数对象
- STL算法之回调函数和函数对象的理解及设计
- C++ STL 基础及应用(7) 函数对象(仿函数)
- HDU 5727 2016多校Contest 1 E题【暴力,STL应用,匈牙利算法,小心函数返回值别忘记写初始化!】
- STL算法和函数对象
- 从零开始学C++之STL(八):函数对象、 函数对象与容器、函数对象与算法
- STL算法设计理念 - 函数对象和函数对象当参数和返回值
- C++之STL(八):函数对象、 函数对象与容器、函数对象与算法
- 从零开始学C++之STL(八):函数对象、 函数对象与容器、函数对象与算法
- STL函数对象:定义、及其在STL中的应用
- stl算法设计理念_预定义函数对象和函数适配器1
- 从零开始学C++之STL(八):函数对象、 函数对象与容器、函数对象与算法
- STL知识点(常用算法函数介绍 、容器、类属算法、迭代器、函数对象、适配器、分配器