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


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

    仿函数(函数对象)

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

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

    一元、二元仿函数

    所以一元二元,就是指重载函数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区别仅仅在于,前者是将绑定的参数作为二元仿函数的第一个参数,后者是将绑定的参数作为二元仿函数的第二个参数。

    如何使用?

    三步骤:

    1. 绑定一个参数。
    2. 仿函数(函数对象)要继承binary_function<参数1类型,参数2类型,仿函数返回类型>
    3. 仿函数要加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的数,这个时候取反适配器就发挥了用途。
    还是老规则,三步骤,一步不能少:

    // 继承和const
    class 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
    

    以上。请指教。

  • 相关阅读:
    eclipse异常关闭,无法启动tomcat解决办法
    VM下,装centos7系统,配置nginx的问题
    用php代码统计数据库中符合条件的行数
    css中的相对定位与绝对定位的区别
    URL地址理解
    开机直接显示用户被锁定点一下就黑屏
    css3 三个属性:-webkit-tap-highlight-color,-webkit-touch-callout,-webkit-user-select
    jquery.flipster.js 3D旋转木马效果
    css3 position:sticky 固定导航平滑过渡
    iframe跨域获取父页面url
  • 原文地址:https://www.cnblogs.com/love-jelly-pig/p/10006219.html
Copyright © 2020-2023  润新知