C/C++ 专题知识知识图谱 移动 前端 Python Android Java

C++ 仿函数

2021-04-02  本文已影响0人  zcwfeng

仿函数

定义:仿函数(functor),就是使一个类的使用看上去像一个函数。其实现就是类中实现一个operator这个类就有了类似函数的行为,就是一个仿函数类了。

有时会发现有些功能实现的代码,会不断的在不同的成员函数中用到,但是又不好将这些代码独立出来成为一个类的一个成员函数。但是又很想复用这些代码。

写一个公共的函数,可以,这是一个解决方法,不过函数用到的一些变量,就可能成为公共的全局变量,再说为了复用这么一片代码,就要单立出一个函数,也不是很好维护。

这时就可以用仿函数了,写一个简单类,除了那些维护一个[类的成员函数]外,就只是实现一个operator(),在类实例化时,就将要用的,非参数的元素传入类中。这样就免去了对一些公共变量的全局化的维护了。又可以使那些代码独立出来,以便下次复用。

而且这些仿函数,还可以用关联,聚合,依赖的类之间的关系,与用到他们的类组合在一起,这样有利于资源的管理(这点可能是它相对于函数最显著的优点了)。如果在配合上模板技术和policy编程思想,那就更是威力无穷了

#include <iostream>
#include <set>
#include <algorithm>
using namespace std;

class CompareObject{
public:
    void operator()(){
        cout << "仿函数" << endl;
    }

    void operator()(int number,int number2){
        cout << "仿函数" << endl;
    }
};

// 查看c++ for_each源码自定义
class ShowActionObj{
public:
    void operator()(int content){
        cout << "custom 仿函数" << content << endl;
    }
};
// 回调方式
void showAction(int content){
    cout << "custom 一元谓词" << content << endl;

}

int main(){

    // 谓词 == 仿函数

    CompareObject fun1;

    fun1();


    set<int> setVar;
    setVar.insert(10);
    setVar.insert(20);
    setVar.insert(30);
    setVar.insert(40);
    setVar.insert(50);
    setVar.insert(60);

    for_each(setVar.begin(),setVar.end(),ShowActionObj());
    cout << "---" << endl;
    for_each(setVar.begin(),setVar.end(),showAction);

    return 0;
}

再写C++ STL 中总结了谓词,相当于仿函数
谓词 <-> 仿函数(空谓词 一元谓词 二元谓词 三元谓词)

C#是通过委托delegate来实现仿函数的。
Java中的仿函数是通过实现包含单个函数的接口实现的
C语言使用[函数指针]和[回调函数]来实现仿函数,例如一个用来排序的函数可以这样使用仿函数

List<String> list =Arrays.asList("10", "1", "20", "11", "21", "12");
Comparator<String> numStringComparator =new Comparator<String>(){
    publicint compare(String o1, String o2){
        returnInteger.valueOf(o1).compareTo(Integer.valueOf(o2));
    }
};
Collections.sort(list, numStringComparator);
#include <stdlib.h>
/* Callback function */
int compare_ints_function(void*A,void*B)
{
    return*((int*)(A))<*((int*)(B));
}
/* Declaration of C sorting function */
void sort(void*first_item,size_t item_size,void*last_item,int(*cmpfunc)(void*,void*));
int main(void)
{
    int items[]={4,3,1,2};
    sort((void*)(items),sizeof(int),(void*)(items +3), compare_ints_function);
    return 0;
}

回调函数,谓词,仿函数 分析

#include <iostream>
#include <set> // STL包
#include <algorithm> // 算法包

using namespace std;

// 我如何阅读C++源码,来写我们的仿函数
// 明明白白的仿函数(一元谓词==一元函数对象)
class showActionObj {
public:
    void operator()(int content) {
        cout << "自定义仿函数" << content << endl;
    }
};

// 回调函数 如果叫 仿函数 有点合理
// 简洁方式(回调函数、一元谓词      但是不能称为 仿函数)
void showAction(int content) {
    cout << "自定义 一元谓词" << content << endl;
}

using namespace std;

int main() {
    set<int> setVar;

    setVar.insert(10);
    setVar.insert(20);
    setVar.insert(30);
    setVar.insert(40);
    setVar.insert(50);
    setVar.insert(60);

    // for_each(setVar.begin(), setVar.end(), showActionObj());

    for_each(setVar.begin(), setVar.end(), showAction);

    return 0;
}

C++ 中,STL + 算法包 + 迭代器 是分开的。所以我们需要手动组合。

image.png

for_each: 源码


for_each.png
#include <iostream>
#include <set> // STL包
#include <algorithm> // 算法包

using namespace std;

// 回调函数 (功能够简单)
void showAction(int __first) {
    cout << "一元谓词" << __first << endl;
}

// 仿函数(扩展性强) C++内置源码使用仿函数频率高,扩展性强
class showActionObj {
public:
    int count = 0;
    void _count() { cout << "本次输出次数是:" << this->count << endl; }

    void operator() (int __first) {
        cout << "仿函数" << __first << endl;
        count++;
    }
};

int main() {
    // 理解:类型传递
    // set<int, showActionObj> setVar; 这样写的语法是OK的,不能加括号
    set<int> setVar;

    setVar.insert(10);
    setVar.insert(20);
    setVar.insert(30);
    setVar.insert(40);
    setVar.insert(50);
    setVar.insert(60);

    // TODO 第一种方式
    for_each(setVar.begin(), setVar.end(), showAction);
    // 请你统计打印次数? 答:做不到

    // TODO 第二种方式
    showActionObj s; // 理解:值传递
    s = for_each(setVar.begin(), setVar.end(), s); // 传入进去的s是新的副本,我们外面的s是旧地址
    // 请你统计打印次数? 答:OK
    s._count();

    return 0;
}

看下_Function


image.png

可以解释这句话

s = for_each(setVar.begin(), setVar.end(), s); // 传入进去的s是新的副本,我们外面的s是旧地址

仿函数 能做到的封装,回调函数是做不到的。源码用到了大量仿函数,证明它的扩展性好。

解决 赋值问题说明

类型传递仿函数 怎么看源码得知写法

set<string> setVar; 

点击进入源码查看

看到这部分模板定义

template <class _Key, class _Compare = less<_Key>,
          class _Allocator = allocator<_Key> >

第一个参数就是我们<> 里面的类型,第二个参数,可以不写,默认参数less
less ,public继承了binary_function

struct _LIBCPP_TEMPLATE_VIS less : binary_function<_Tp, _Tp, bool>
{
    _LIBCPP_CONSTEXPR_AFTER_CXX11 _LIBCPP_INLINE_VISIBILITY
    bool operator()(const _Tp& __x, const _Tp& __y) const
        {return __x < __y;}
};

这样做,思想和RxJava一致,做到统一过滤用,有成百上千的操作符。
父类就可以得到我的子类仿函数,根据这个,我们就可以自定义仿函数。仿造源码,写一个二元谓词

#include <iostream>
#include <set>
using namespace std;

// C++源码:typename _Compare = std::less   less内置的仿函数,根据内置仿函数去写 自定义
//  bool operator()(const _Tp& __x, const _Tp& __y) const 二元谓词
class CompareObjectClass {
public:
    bool operator() (const string & __x, const string & __y) const { // const 指针 const  常量指针常量 = 只读
        return __x > __y;
    }
};

int main() {
    set<string, CompareObjectClass> setVar; // 第一版
    setVar.insert(setVar.begin(), "AAAAAAA");
    setVar.insert(setVar.begin(), "BBBBBBB");
    setVar.insert(setVar.begin(), "CCCCCCC");
    setVar.insert(setVar.begin(), "DDDDDDD");
    setVar.insert(setVar.begin(), "EEEEEEE");
    setVar.insert(setVar.begin(), "FFFFFFF");
    // 迭代器 循环
    for (set<string>::iterator iteratorVar = setVar.begin(); iteratorVar != setVar.end(); iteratorVar++) {
        cout << "循环item:" << *iteratorVar  << "\t";
        // 循环item:AAAAAAA   循环item:BBBBBBB  循环item:CCCCCCC  循环item:DDDDDDD  循环item:EEEEEEE  循环item:FFFFFFF

        // 循环item:FFFFFFF   循环item:EEEEEEE  循环item:DDDDDDD  循环item:CCCCCCC  循环item:BBBBBBB  循环item:AAAAAAA
    }
    return 0;
}

通过Set 的内置反函数less, 内置反函数,定义我们自己的函数

容器存储对象,生命周期

set 存入对象 奔溃(set会自动排序,对象没法排序,所以奔溃) 解决方案:自定义仿函数解决

为了方便避免重复,我们用vector存储对象,说明生命周期

#include <iostream>
#include <set> // set 存入对象 奔溃(set会自动排序,对象没法排序,所以奔溃)  解决方案:自定义仿函数解决
#include <vector>  // 存入对象

using namespace std;

class Person {
private:
    string name;
public:
    Person(string name) : name(name) {}

    void setName(string name) {
        this->name = name;
    }

    string getName() {
        return this->name;
    }

    Person(const Person &person) {
        this->name = person.name; // 浅拷贝

        cout << "Person拷贝构造函数执行了..." << endl;
    }

    ~Person() {
        cout << "Person析构函数执行了" << endl;
    }
};

int main() {
    // Java:把对象存入 添加到 集合
    // C++: 调用拷贝构造函数,存进去的是另一个新的对象

    vector<Person> vectorVar;

    // person 被main函数弹栈 析构一次
    Person person("David"); // 2  David

    // 里面的insert函数弹栈 析构一次
    vectorVar.insert(vectorVar.begin(), person); // 外面的person是旧地址,到insert函数里面的person就是新地址(拷贝构造函数 一次)

    person.setName("Kevin"); // 1

    // newPerson 被main函数弹栈 析构一次
    Person newPerson =
            vectorVar.front(); // front里面的person是旧地址, 外面的newPerson就是新地址(拷贝构造函数 一次)

    cout << "newPerson:" << newPerson.getName().c_str() << endl;

    // 3次析构函数   两次拷贝构造

    return 0;
} // main弹栈

代码执行了,3次析构函数 两次拷贝构造

Person person("David")

Person newPerson =vectorVar.front()

执行拷贝构造函数 front里面的是旧的地址,外面的newPerson 就是新的地址

Person person("David")

main函数执行完会析构一次

vectorVar.insert(vectorVar.begin(), person) i

nsert 内部结束也会析构一次

Person newPerson =vectorVar.front()

newPerson 被main函数弹栈 析构一次

预定义函数

C++ 内置函数

#include <iostream>
#include <set> // STL包
#include <algorithm> // 算法包
using namespace std;

int main() {
    // "David" + "AAAA" // 运算符重载

    // C++已经提供了 预定义函数  plus,minus,multiplies,divides,modulus ...
    plus<int> add_func;

    int r = add_func(1, 1);
    cout << r << endl;

    plus<string> add_func2;
    string r2 = add_func2("AAAA", "BBB");
    cout << r2 << endl;

    plus<float> add_func3;
    float r3 = add_func3(4354.45f, 34.3f);
    cout << r3 << endl;

    return 0;
}

简单的例子,plus 。 我们拼接字符串可能想到运算符重载,但是c++内置plus解决了这个问题。

有些比如,对象可能就不适用,我们可以手写预定义函数
: public binary_function_t<T, T, T>
可以要可以不要

#include <iostream>
#include <set> // STL包
#include <algorithm> // 算法包
using namespace std;

template<typename Arg1, typename Arg2, typename Result>
struct binary_function_t
{
    /// 第一个参数类型 是底一个参数的类型
    typedef Arg1    first_argument_type;

    //econd_argument_type是第二个参数的类型
    typedef Arg2    second_argument_type;

    /// @c result_type是返回类型
    typedef Result  result_type;
};

// TODO 对象 + 对象
// 1.运算符重载
// 2.对象+对象 自己去写仿函数

template<typename T>
struct plus_d : public binary_function_t<T, T, T>
{
    T operator() (const T & x, const T & y) {
        return x + y;
    }
};

int main() {

    plus_d<int> add_func;
    int r = add_func(1, 1);
    cout << r << endl;

    plus_d<string> add_func2;
    string r2 = add_func2("AAAA", "BBB");
    cout << r2 << endl;

    plus_d<float> add_func3;
    float r3 = add_func3(4354.45f, 34.3f);
    cout << r3 << endl;

    return 0;
}
上一篇下一篇

猜你喜欢

热点阅读