您现在的位置是:网站首页> 内容页

仿函数、谓词、适配器、内建对象配合常见算法的使用

  • www.818448.com
  • 2019-07-08
  • 425人已阅读
简介在前面几篇中,已经有过好几次的仿函数结合遍历、查找等算法的使用了,这边再进行归纳一下。仿函数(函数对象)前面已经说过了,仿函数的特点有:是个类,不是个函数,可以说成是函数对象。重载()

在前面几篇中,已经有过好几次的仿函数结合遍历、查找等算法的使用了,这边再进行归纳一下。

仿函数(函数对象)

前面已经说过了,仿函数的特点有:

是个类,不是个函数,可以说成是函数对象。重载()。内部可以声明一些变量,保存状态,如声明一个整型变量记录调用次数。仿函数通常不声明构造函数和析构函数,因此构造和析构时不会发生问题,避免了函数调用时的运行问题。可内联编译。 使用函数指针就不大可能了,因为不知道函数指针的具体内容到底适不适合内联。模板函数使得通用性更强。

一元、二元仿函数

所以一元二元,就是指重载函数operator()的参数有几个,一个称为一元,两个称为二元。

仿函数作为一种 策略 使用,配合算法能够让我们获得想要的结果。

可能 策略 这个词初看不能理解,下面就看看例子吧。

在看例子之前先看看for_each的原型:

// 返回类型为仿函数类型,接受两个指针和一个仿函数Function for_each (InputIterator first, InputIterator last, Function fn);

现在我要使用for_each算法,遍历vector容器,且还要输出它。

//仿函数class MyPrint{public: MyPrint() {m_Num = 0;} int m_Num;public: //重载,策略为输出,然后次数+1 void operator() (int num) { cout << num << " "; m_Num++; }};int main(){ vector<int> v; for (int i = 0; i < 10; ++i) { v.push_back(i); } //调用 MyPrint my = for_each(v.begin(), v.end(), MyPrint()); cout << endl << my.m_Num << endl; return 0;}

看看输出结果:

可以看到我们已经正确输出了容器中的元素,且内部保存的状态也得以正确保存。看我们的仿函数,策略就是输出元素。

谓词

谓词是指普通函数或重载的operator()返回值是bool类型的函数对象(仿函数)。如果operator接受一个参数,那么叫做一元谓词,如果接受两个参数,那么叫做二元谓词,谓词可作为一个判断式。这个判断式,也可看做策略。

谓词和仿函数有几分相似,只是返回值确定为bool型的。用法也几乎一样。

谓词可分一元谓词和二元谓词。 根据各自策略不同,比如二元谓词可以用来做降序排列,这在前几篇都有讲过。只要规定一个排序规则即可。如:

class MyCmp{ public: bool operator()(int a,int b) return a > b;};

那一元排序也有自己的用途,比如我想在容器中找到大于5的数字:

class MyCmp{ public: bool operator()(int v) return v > 5;};int main(){ vector<int> v; for (int i = 0; i < 10;i ++) { v.push_back(i); } vector<int>::iterator it = find_if(v.begin(), v.end(), GreaterThenFive()); if (it == v.end()) cout << "没有找到" << endl; else cout << "找到了: " << *it << endl; return 0;}

这里我们使用了find_if这个标准库算法,原型为:

InputIterator find_if (InputIterator first, InputIterator last, UnaryPredicate pred);

返回值为迭代器,前两个参数为起始和终止迭代器,最后一个参数即我们的策略:一元谓词。这边指定只能用一元谓词。

内建函数对象

你可能会想,那我们每次要改变策略,是不是每次都要写一个仿函数(函数对象),来得到我们想要的结果?那会不会太麻烦了点。其实STL内建了一些函数对象。分为:算数类函数对象,关系运算类函数对象,逻辑运算类仿函数。我们要用这些函数要引入头文件#include

下面介绍常用的一些内建函数对象,取几个来讲,用法都一样,根据自己想要的策略选择即可。

算术类函数对象

template<class T> T plus<T> //加法仿函数template<class T> T minus<T> //减法仿函数template<class T> T multiplies<T>//乘法仿函数template<class T> T divides<T> //除法仿函数template<class T> T modulus<T> //取模仿函数template<class T> T negate<T> //取反仿函数

以上除了negate是一元运算,其它都是二元的。举个例子:

#include <functional>...int main(){ negate<int> n; cout << n(10) << endl; // 结果为-10 plus<int> p; cout << p(10,36) << endl; // 结果为46 divides<int> d; cout << d(36,10) << endl; // 结果为3 return 0;}

其它不再概述,一样。

关系类函数对象

template<class T> bool equal_to<T> //等于template<class T> bool not_equal_to<T> //不等于template<class T> bool greater<T> //大于template<class T> bool greater_equal<T>//大于等于template<class T> bool less<T> //小于template<class T> bool less_equal<T> //小于等于

这里的每一个函数对象都是一个二元仿函数。

我们之前用的升序排序,现在完全可以用内置函数对象替换:

int main(){ vector<int> v; for (int i = 0; i < 10; ++i) v.push_back(i); sort(v.begin(), v.end(), greater<int>()); auto it = v.begin(); for (; it != v.end(); ++it) cout << *it << " "; return 0;}

我们把原本的仿函数改为了内建函数对象:

greater<int>()

这样就能实现从大到小排序了。

逻辑类函数对象

template<class T> bool logical_and<T> //逻辑与template<class T> bool logical_or<T> //逻辑或template<class T> bool logical_not<T> //逻辑非

除了logical_not是一元以外,均是二元。使用方式与上一致,不再累述。

适配器

现在着重介绍最难的适配器。

函数适配器bind1st和bind2nd

是什么?

VS的文档里面是这样描述bind2nd的:

一个辅助模板函数,它创建一个适配器,通过将二元函数的第二个参数绑定为指定值,将二元函数对象转换为一元函数对象。

也就是说我们使用bind2nd的目的在于:对于只能使用一元仿函数的地方,我们可以用bind2nd绑定第二个参数,这样我们将二元仿函数就转换成一元仿函数,不过是绑定了个参数进去而已。

bind1st和bind2nd区别仅仅在于,前者是将绑定的参数作为二元仿函数的第一个参数,后者是将绑定的参数作为二元仿函数的第二个参数。

如何使用?

三步骤:

    绑定一个参数。仿函数(函数对象)要继承binary_function<参数1类型,参数2类型,仿函数返回类型>仿函数要加const修饰,防止改变任何东西。

下面举个例子。我们要用for_each遍历一个容器,每次遍历都输出结果为对应元素值加上一个我指定的数的和。for_each只能传递一元仿函数,所以,我要用bind2nd将我指定的数传递进去:

// 三步骤之继承,全看operator参数类型和返回值class MyPrint2 :public binary_function<int, int, void>{public: // 三步骤之const void operator()(int v1, int v2) const { cout << "v1 = " << v1 << " v2 = " << v2 << " v1+v2 = " << (v1 + v2) << endl; }};void test4(){ vector<int>v; for (int i = 0; i < 5; i++) { v.push_back(i); } cout << "请输入起始值:" << endl; int x; cin >> x; // 三步骤之绑定 for_each(v.begin(), v.end(), bind1st(MyPrint2(), x)); cout << endl; // 三步骤之绑定 for_each(v.begin(), v.end(), bind2nd( MyPrint2(),x ));}

上面我用了两个for_each,一个使用bind1st绑定,另一个是用bind2nd绑定。结果如下:

可以看到,bind1st是把绑定值作为第一个参数,元素值作为第二个参数,而bind2nd正好相反。

取反适配器not1和not2

取反适配器not1就是一元仿函数取反,not2就是二元仿函数取反。

倘若有个仿函数是这样的,它想找到大于5的数:

class GreaterThen5{public: bool operator()(int a) { return a > 5; }};int main(){ vector<int>v; for (int i = 0; i < 10; i++) v.push_back(i); auto it = find_if(v.begin(), v.end(), GreaterThen5()); if(it!=v.end()) cout << "值为:" << *it << endl; return 0;}

这样我们能正确找到,但是现在我们突然改了方案,不能修改仿函数的基础上要改成找到小于5的数,这个时候取反适配器就发挥了用途。还是老规则,三步骤,一步不能少:

// 继承和constclass GreaterThen5 : public unary_function<int,bool>{public: bool operator()(int a) const { return a > 5; } };int main(){ vector<int>v; for (int i = 0; i < 10; i++) v.push_back(i); // 使用not1 auto it = find_if(v.begin(), v.end(), not1(GreaterThen5())); if(it!=v.end()) cout << "值为:" << *it << endl; return 0;}

其实还可以写成:

not1(bind2nd(Great<int>,5));

其中缘由就自己领悟吧哈哈。

函数指针适配器ptr_fun

上面介绍了仿函数的适配使用,现在就介绍普通函数的适配使用方法。

void MyPrint3(int a, int b){ cout << "和为:" << a + b << endl;}void test7(){ vector<int>v; for (int i = 0; i < 6; i++) v.push_back(i); for_each(v.begin(),v.end(),bind2nd(ptr_fun(MyPrint3),100));}

注意点:

使用了bind2nd却不用遵循三步骤。ptr_fun(MyPrint3),直接把函数名称传进去就是一个函数对象了。

成员函数适配器

上面讲的是普通的野生函数,那如果是在类里面的成员函数呢?关键字:mem_fun_ref

class Person{public: string _strName; int _iAge; Person(string strName, int iAge){ _strName = strName; _iAge = iAge; } void ShowInfo(){ cout << "姓名:" << _strName << " 年龄:" << _iAge << endl; //_strName = "hello"; }};int main(){ vector <Person>v; v.push_back(Person("aaa", 10)); v.push_back(Person("ccc", 30)); v.push_back(Person("bbb", 20)); v.push_back(Person("ddd", 40)); for_each(v.begin(), v.end(), mem_fun_ref(&Person::ShowInfo)); return 0;}

记得要在mem_fun_ref里面取地址哦。倘若我们现在vector容器中存放的是指针类型:

vector<Person *> v;

那我们只需要将mem_fun_ref改为:

mem_fun

以上。请指教。

文章评论

Top