C++STL之算法 | 函数对象

函数对象的概念

重载函数调用操作符的类,其对象常称为函数对象(function object),即它们是行为类似函数的对象,也叫仿函数(functor),其实就是重载“()”操作符,使得类对象可以像函数那样调用。

注意:
1.函数对象(仿函数)是一个类,不是一个函数。
2.函数对象(仿函数)重载了”() ”操作符使得它可以像函数一样调用。

假定某个类有一个重载的operator(),而且重载的operator()要求获取一个参数,我们就将这个类称为“一元仿函数”(unary functor);相反,如果重载的operator()要求获取两个参数,就将这个类称为“二元仿函数”(binary functor)。

函数对象基本概念
函数对象也可以有参数和返回值
函数对象超出函数概念,可以保存函数调用状态
函数对象做参数和返回值

#define _CRT_SECURE_NO_WARNINGS
#include<iostream>
#include<vector>
#include<algorithm>
#include<functional>
using namespace std;

class FuncObject01{
public:
    void operator()(){
        cout << "hello world" << endl;
    }
};

void FuncObject02(){
    cout << "hello world" << endl;
}

//函数对象概念
void test01(){
    
    FuncObject01 fobj;
    fobj();
    FuncObject02();

}

class FuncObject03{
public:
    int operator()(int a, int b){
        return a + b;
    }
};

int FuncObject04(int a,int b){
    return a + b;
}
//函数对象也可以像普通函数一样 具有返回值和参数
void test02(){
    
    FuncObject03 fobj;
    int ret = fobj(10,20);
    cout << "ret :" << ret << endl;

    ret = FuncObject04(10,20);
    cout << "ret :" << ret << endl;

}
//函数对象超出了普通函数的功能,可以具有保存函数调用状态
//例如 我们要统计函数调用次数

class FuncObject05{
public:
    FuncObject05() :count(0){}
    void operator()(){
        cout << "hello world" << endl;
        count++;
    }
    int count;
};

//普通函数要统计调用次数 需要一个全局变量
int g_count = 0;
void FuncObject06(){
    cout << "hello world" << endl;
    g_count++;
}
void test03(){

    FuncObject06();
    FuncObject06();
    cout << "函数调用次数:" << g_count << endl;

    //使用函数对象 不需要使用全局变量
    FuncObject05 fobj;
    fobj();
    fobj();
    fobj();

    cout << "函数调用次数:" << fobj.count << endl;

}

//函数对象做参数和返回值
class print{
public:
    print() :count(0){}
    void operator()(const int& val){
        cout << val << " ";
        count++;
    }
    int count;
};
void test04(){
    
    vector<int> v;
    v.push_back(1);
    v.push_back(3);
    v.push_back(5);
    v.push_back(2);

    //通过for_each算法 遍历容器元素
    print myprint;
    //函数对象做返回值和参数
    myprint = for_each(v.begin(), v.end(), myprint);
    cout << endl;

    cout << "函数对象调用次数:" << myprint.count << endl;
}

int main(){
    test01();
    test02();
    test03();
    test04();
    
    system("pause");
    return EXIT_SUCCESS;
}

谓词概念

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

struct myfuncobj01{
      bool operator()(int v)const{}  //接受一个参数,并且返回值为Bool 即一元谓词
}
bool compare01(int v); //同样是叫做一元谓词

struct myfuncobj02{
      bool operator()(int v1,int v2)const{}  //接受两个参数,返回值为Bool 即二元谓词
}
bool compare02(int v1,int v2); //同样是叫做二元谓词

上面想将struct改为class也行,不过要在重载函数前加public,因为class内默认是private

内建函数对象

STL内建了一些函数对象。分为:算数类函数对象,关系运算类函数对象,逻辑运算类仿函数。这些仿函数所产生的对象,用法和一般函数完全相同,当然我们还可以产生无名的临时对象来履行函数功能。
使用内建函数对象,需要引入头文件 #include<functional>。

6个算数类函数对象,除了negate是一元运算,其他都是二元运算。

template<class T> T plus<T>//加法仿函数
template<class T> T minute<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>//取反仿函数

6个关系运算类函数对象,每一种都是二元运算。

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>//小于等于

逻辑运算类运算函数,not为一元运算,其余为二元运算。

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

使用例子:

//使用内建函数对象声明一个对象
plus<int> myPlus;
cout << myPlus(5, 3) << endl;
//使用匿名临时对象
cout << plus<int>()(5, 6) << endl;
sort排序使用预定义函数对象进行排序。
count_if equal_to 参数绑定

函数对象适配器

函数对象适配器是完成一些配接工作,这些配接包括绑定(bind),否(negate),以及对一般函数或成员函数的修饰,使其成为函数对象,重点掌握函数对象适配器(红色字体):

bind1st :将参数绑定为函数对象的第一个参数
bind2nd : 将参数绑定为函数对象的第二个参数
not1 : 对一元函数对象取反
not2 : 对二元函数对象取反
ptr_fun : 将普通函数修饰成函数对象
mem_fun : 将成员函数修饰成函数对象,当容器内是对象指针时用这个,这是与mem_fun_ref的区别
mem_fun_ref : 将成员函数修饰成函数对象

预定义函数对象
仿函数适配器bind1st bind2nd
仿函数适配器not1 not2
仿函数适配器 ptr_fun
成员函数适配器 mem_fun mem_fun_ref

#define _CRT_SECURE_NO_WARNINGS

#include<iostream>
#include<functional>
#include<algorithm>
#include<vector>
#include<string>
using namespace std;

/*
    template<class T> T plus<T>//加法仿函数
    template<class T> T minute<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>//取反仿函数
*/
//预定义函数对象
class print{
public:
    void operator()(int v){
        cout << v << "    ";
    }
};
void test01(){
    
    plus<int> myplus; //实例化一个对象
    int ret = myplus(10, 20);
    cout << "ret : " << ret << endl;

    cout << plus<int>()(30, 40) << endl;

    vector<int> v1, v2, v3;
    for (int i = 0; i < 10;i++){
        v1.push_back(i);
        v2.push_back(i + 1);
    }

    v3.resize(v1.size());
    transform(v1.begin(),v1.end(),v2.begin(),v3.begin(),plus<int>());

    for_each(v1.begin(), v1.end(), print());
    cout << endl;

    for_each(v2.begin(), v2.end(), print());
    cout << endl;

    for_each(v3.begin(), v3.end(), print());
    cout << endl;

}

//函数适配器bind1st bind2nd
//现在我有这个需求 在遍历容器的时候,我希望将容器中的值全部加上100之后显示出来,怎么做哇?
struct myprint : public binary_function<int,int,void>{   //二元函数对象 所以需要继承 binary_fucntion<参数类型,参数类型,返回值类型>
    void operator()(int v1 ,int v2) const{
        cout << v1 + v2 << " ";
    }
};
void test02(){
    
    vector<int> v;
    v.push_back(1);
    v.push_back(2);
    v.push_back(3);
    v.push_back(4);

    //我们直接给函数对象绑定参数 编译阶段就会报错
    //for_each(v.begin(), v.end(), bind2nd(myprint(),100));
    //如果我们想使用绑定适配器,需要我们自己的函数对象继承binary_function 或者 unary_function
    //根据我们函数对象是一元函数对象 还是二元函数对象
    for_each(v.begin(), v.end(), bind2nd(myprint(), 100));
    cout << endl;

    //总结:  bind1st和bind2nd区别?
    //bind1st : 将参数绑定为函数对象的第一个参数
    //bind2nd : 将参数绑定为函数对象的第二个参数
    //bind1st bind2nd将二元函数对象转为一元函数对象

}

//函数对象适配器 not1 not2
struct myprint02 {
    void operator()(int v1) const{
        cout << v1 << " ";
    }
};
void test03(){

    vector<int> v;
    v.push_back(2);
    v.push_back(1);
    v.push_back(5);
    v.push_back(4);
    
    vector<int>::iterator it =  find_if(v.begin(), v.end(), not1(bind2nd(less_equal<int>(), 2)));
    cout << "it:" << *it << endl;
    sort(v.begin(),v.end(),not2(greater<int>()));

    for_each(v.begin(), v.end(), myprint02());
    cout << endl;

    //not1 对一元函数对象取反
    //not2 对二元函数对象取反
}

//如何给一个普通函数使用绑定适配器(bind1st bind2nd)绑定一个参数?(拓展)
//ptr_fun
void myprint04(int v1,int v2){
    cout << v1 + v2 << " ";
}
void test04(){
    
    vector<int> v;
    v.push_back(2);
    v.push_back(1);
    v.push_back(5);
    v.push_back(4);


    //1 将普通函数适配成函数对象
    //2 然后通过绑定器绑定参数
    for_each(v.begin(), v.end(), bind2nd(ptr_fun(myprint04),100));
    cout << endl;

    //总结: ptr_fun 将普通函数转变为函数对象
}

//mem_fun mem_fun_ref
//如果我们容器中存储的是对象或者对象指针,如果能指定某个成员函数处理成员数据。
class student{
public:
    student(string name, int age) :name(name), age(age){}
    void print(){
        cout << "name:" << name << " age:" << age << endl;;
    }
    void print2(int a){
        cout << "name:" << name << " age:" << age << " a:" << a << endl;
    }
    int age;
    string name;
};
void test05(){
    

    //mem_fun : 如果存储的是对象指针,需要使用mem_fun
    vector<student*> v;
    student* s1 = new student("zhaosi",10);
    student* s2 = new student("liuneng", 20);
    student* s3 = new student("shenyang", 30);
    student* s4 = new student("xiaobao", 40);

    v.push_back(s1);
    v.push_back(s2);
    v.push_back(s3);
    v.push_back(s4);

    for_each(v.begin(), v.end(), mem_fun(&student::print));
    cout << "-----------------------------" << endl;

    //mem_fun_ref : 如果存储的是对象,需要使用mem_fun_ref

    vector<student> v2;
    v2.push_back(student("zhaosi",50));
    v2.push_back(student("liuneng", 60));
    v2.push_back(student("shenyang", 70));
    v2.push_back(student("xiaobao", 80));

    for_each(v2.begin(), v2.end(), mem_fun_ref(&student::print));

}


int main(){

    //test01();
    //test02();
    //test03();
    //test04();
    test05();

    system("pause");
    return EXIT_SUCCESS;
}

如果希望函数对象适配器能对我们自己编写的函数对象有效,我们需要根据我们的函数对象类型继承STL的父类对象。
如果你本身是二元函数对象 需要继承
二元函数继承:public binary_function<参数类型,参数类型,返回类型>
一元函数继承:public unary_function<参数类型,返回类型>

如果是普通函数、成员函数转化成的函数对象就不需要继承

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,125评论 6 498
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,293评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 162,054评论 0 351
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,077评论 1 291
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,096评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,062评论 1 295
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,988评论 3 417
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,817评论 0 273
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,266评论 1 310
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,486评论 2 331
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,646评论 1 347
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,375评论 5 342
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,974评论 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,621评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,796评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,642评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,538评论 2 352

推荐阅读更多精彩内容