仿函数——思维导图
仿函数是什么
- 使用对象名调用operator()函数看起来像是在使用函数一样,因此便有了仿函数的称呼;
- 仿函数存在的意义是:让我们方便的调用类的成员函数;
- 它在功能上替代了C语言的函数指针
- 目前我们学的写的仿函数的存在意义就是为了:使用类的重载operator()
- 什么情况下我们会使用仿函数:我们在需要实现一个我们的独特意图时,我们会写仿函数定制一个功能;
仿函数的优势
-
使用成本低
它允许我们在传参的时候顺手设定逻辑方向,即让我们可以通过改变输入类型来间接改变目标函数内部的一些逻辑方向; -
信息密度低
仿函数体现了封装思维,把大量的修改逻辑的冗杂操作封装成一次修改类型; -
低耦合
将函数内的一些过程摘出来用一个函数实现它,将函数的部分功能模块化
理解仿函数
仿函数调用类的成员函数和我们之前学的回调操作类似;
- 回调——以对象名调函数:传对象,凭借这个对象调用成员函数;
- 仿函数——以类型名调函数:传类型、传模板参数的类型,凭借这个类型的成员函数
仿函数的底层逻辑依然是靠对象(通过传来的类型和模板参数生成一个对象)使用成员函数,仿函数的优点在于省去了我们生成对象的操作,使我们的操作更加灵活。
仿函数使我们可以自定义进入类的函数内容
仿函数不仅可以对我们传过去的参数间的逻辑运算做出改变,还可以对目标函数识别参数这一过程也做出改变;
例如:我们可以决定它的参数到底是直接参与逻辑运算还是解引用后再参与(可能传来一个地址);
仿函数的原理
当我们使用仿函数时我们会传递一些信息,我们传递的是一个类型,该类型作为模板参数给到目标函数时,它的类型信息被抽取给了目标函数,目标函数根据这个类型信息进而实例化一个对象,后续使用该对象的对象名调用operator()等函数。
举例
A<int> a;
为什么实例化对象不能我们不止写了对象名A还要加一个类型int进去,因为我们传递的对象本身并没有把它的模板参数的类型传递过去,而目标函数本身是个类需要一个类型去定义它的模板参数类型,只有这样才能在后面实例化出一个确定的对象,所以我们要手动补充;
逻辑上简而言之,一个类型传给另一个类型,附加个模板参数类型;
实践上简而言之,实例化需要类型名、类型的模板参数类型;
关于仿函数的思考
仿函数满足的需求——易构思
我们在学习stack时会遇到一些新的问题,这些问题需要我们使用非类型模板参数去解决,即我们需要在设计类时需要有一个途径去快捷的修改类内部的逻辑,而此时会出现我们用原有的方法无法修改类内部的逻辑;因此,这里我们需要涉及到仿函数(类型函数)的概念;
仿函数使用设计的巧妙——易使用
由模板的出现,结合封装的思想发展而生的一个新的逻辑构建途径,将一个类在实例化后的对象名作为一个函数名看待;在对象名后面使用operator()并加入参数,这种方法可以允许我们对参数数量确定的情况下通过选择传递不同的类型,来实现对参数彼此之间的逻辑关系的控制修改;
仿函数语法设计的巧妙——易勘误
观察传参,我们可以看数量、类型、种类等信息哪些被传过去了,通过这些关键信息的传递,我们可以去猜测编译过程发生了什么,接收参数的类进行自身内部的所有行为都是基于它接收到的信息,当我们在设计传参时发现错错误,一般都是类里面需要某种信息,而我们没有传递给它;
仿函数的本质——封装实例化的过程
仿函数其本质还是回调——凭借一个对象去使用它的函数;不过不同点在于,我们这里传输的是函数的类型,另外加一个模板参数类型,将实例化一个对象的步骤交给了仿函数去做;
仿函数的优势——易修改
这种方法的优势在于我们可以直接修改传参时传入的类型和模板参数就修改了整个类里面某一逻辑,且这种逻辑是可以被我们以自定义的方法去定义;
仿函数的语法逻辑
追根溯源,是编译过程决定哪些语法可以,哪些不可以。
从仿函数的与法规则我们也可以从中看出C++的一些语法和编译过程的联系。
例如:非类型模板参数必须要传递常量,以便其编译;
在实践中,我们可以发现必须要常量才能使仿函数实例化,因为实例化一个对象我们需要类的类型和模板参数类型,我们在传递类型时没有把模板参数类型传过去,这意味着当编译开始时,编译没有办法完成自己的任务,编译的任务是提前把需要使用的逻辑通路准备号好,而当我们没有传递模板参数类型时,一些类型本身就需要模板参数类型才完整,不给就无法去确定类型的一些变量的数据类型,所以就无法编译这些变量、进而无法编译一些函数,最后编译的内容由有一堆未知的东西;