前言
"打牢基础,万事不愁" .C++的基础语法的学习."学以致用,边学边用",编程是实践性很强的技术,在运用中理解,总结.
引入
STL(标准模板库)的学习.以<C++ Prime Plus> 6th Edition(以下称"本书")内容理解 .函数对象是STL中的一个重要内容
函数对象定义
本书P707很多STL算法都使用函数对象——也叫函数符(functor)。函数符是可以以函数方式与( )结合使用的任意对象。这包括函数名、指向函数的指针和重载了( )运算符的类对象(即定义了函数operator( )( )的类)。(黑体字是本书原话)
----解读:红色部分是函数对象的定义,简而言之可以使用()运算符的对象,包括函数,函数指针,重载了()运算符的类对象.前面帖子C++基础语法:函数指针-CSDN博客 讲过函数指针,看这里能不能用得上.下面是一个重载了()运算符的类对象函数,可自行阅读
接着看:还记得函数for_each吗?它将指定的函数用于区间中的每个成员:
for_each(books.begin(),books.end(),ShowReview); //for_each函数调用
原文:通常,第3个参数可以是常规函数,也可以是函数符。实际上,这提出了一个问题:如何声明第3个参数呢?不能把它声明为函数指针, 因为函数指针指定了参数类型。由于容器可以包含任意类型,所以预先无法知道应使用哪种参数类型。STL通过使用模板解决了这个问题。 for_each的原型看上去就像这样:
//for_each函数原型,Function可以传入函数对象
template<class InputIterator,class Function>
Function for_each(InputIterator first,InputIterator last,Function f);
ShowReview( )的原型如下:
void ShowReview(const Review &);
原文:这样,标识符ShowReview的类型将为void(*)(const Review &),这也是赋给模板参数Function的类型。对于不同的函数调用,Function参数可以表示具有重载的( )运算符的类类型。最终,for_each( )代码将具有 一个使用f( )的表达式。在ShowReview( )示例中,f是指向函数的指针, 而f( )调用该函数。如果最后的for_each( )参数是一个对象,则f( )将是调用其重载的( )运算符的对象。
----解读:红色部分说明了在模板函数for_each()定义中,函数对象被当成类对象传给形参f.在之前的内容中,函数指针是一种数据类型,函数是指针类型的常量.这里一番操作,函数对象被当成class对象进入了函数模板是个新的内容(具体看不见源码不知道怎么来的,记住)
函数符定义
原文:正如STL定义了容器和迭代器的概念一样,它也定义了函数符概 念。 生成器(generator)是不用参数就可以调用的函数符。 一元函数(unary function)是用一个参数可以调用的函数符。 二元函数(binary function)是用两个参数可以调用的函数符。
----解读:很容易理解,函数符--函数f()或者长得像函数的对象调用f(),现在有了分类:
/*伪代码:函数原型*/
ResultType fun(); //没形参的叫生成器
ResultType fun(ParaType pt); //1个形参叫一元函数
ResultType fun(ParaType pt,ParaType1 pt1); //2个形参叫二元函数
原文:例如,提供给for_each( )的函数符应当是一元函数,因为它每次用于一个容器元素。
----解读:根据函数原型(Function f),函数符f是一元函数,大概长这样
ResultType f(ParaType pt); //函数符f是一元函数
例子中传入的函数ShowReview,原型是void ShowReview(const Review &),就是一元函数.
他每次用于一个容器元素,每次用一个迭代器对象访问到一个元素,再传给函数对象--假设
原文:当然,这些概念都有相应的改进版: 返回bool值的一元函数是谓词(predicate); 返回bool值的二元函数是二元谓词(binary predicate)。
----解读:谓词是返回bool类型的一元函数,二元谓词是返回bool类型的二元函数
/*伪代码:函数原型*/
bool fun(ParaType pt); //返回bool类型的一元函数,谓词
bool fun(ParaType pt,ParaType1 pt1); //返回bool类型的二元函数,二元谓词
原文:一些STL函数需要谓词参数或二元谓词参数。例如,程序清单16.9 使用了sort( )的这样一个版本,即将二元谓词作为其第3个参数:
bool WorseThan(const Review& r1,const Review& r2) //WorseThan函数原型
sort(books.begin(),books.end(),WorseThan); //sort使用了二元谓词WorseThan
----解读:能用于STL的函数,是不是格式都一致?三个参数,前两个用迭代器对象表示容器区间,后一个用谓词处理被选中的容器元素.像这里的books.begin()和books.end()表示容器对象books里的所有元素被选中(因为没有函数原型,暂时先这样理解)
谓词
原文:list模板有一个将谓词作为参数的remove_if( )成员,该函数将谓词应用于区间中的每个元素,如果谓词返回true,则删除这些元素。例如,下面的代码删除链表three中所有大于100的元素
bool tooBig(int n){return n>100;}
list<int> scores;
scores.remove_if(tooBig);
----分析:谓词起到了接转的作用.谓词返回值类型设定为bool型,当bool为真时,n值被传递给list容器对象scores,容器对象拿到n后和每个元素比较,比n高的被删除.
来回溯实现过程:
1>遍历容器内数据,将其代入tooBig()函数
2>如果tooBig()返回值为true,则执行删除动作.
注意:以下代码只为尝试还原逻辑,不保证和源代码一致
代码块1
/*伪代码,list对象调用*/
void remove_if(predicate pr){ //predicata:谓词类型
for(auto pt=scores.begin();pt!=scores.end();pt++){
if((*pr)(*pt)) //调用谓词函数,如果容器值大于100
pt=scores.erase(pt); //删除结点
}
}
===================
scores.remove_if(tooBig); //传入tooBig,谓词类型常量
问题来了.tooBig函数带了常量100,属于硬编码,应该修改成把限定值limit传入
//满足要求的tooBig函数定义
bool tooBig(int n,int limit){return n>limit;} //limit设为删除条件
但这里明确了remove_if()函数的参数是谓词,后面定义的tooBig是二元谓词,两者类型不一样.也因此引出了本书后面内容,使用类函数符传入限定值.
原文:最后这个例子演示了类函数符适用的地方。假设要删除另一个链表中所有大于200的值。如果能将取舍值作为第二个参数传递给tooBig( ), 则可以使用不同的值调用该函数,但谓词只能有一个参数。然而,如果设计一个TooBig类,则可以使用类成员而不是函数参数来传递额外的信息:
----原书代码贴图
这里,一个值(V)作为函数参数传递,而第二个参数(cutoff)是由类构造函数设置的。有了该定义后,就可以将不同的TooBig对象初始化为不同的取舍值,供调用remove_if( )时使用。
----分析:为了实现谓词函数,本例中需要两个参数,而tooBig函数作为谓词,被限定了只能传入一个参数.解决办法是声明一个模板类TooBig,用类函数符替代了函数tooBig,用对象属性传入了第二个参数.
引申:谓词函数里需要的参数,都可以传入模板类对象,在谓词函数中使用.传入数量等于总共需要参数数量减1(谓词占1个);在模板参数声明泛型,在构造函数中传入(模板基础)
/*伪代码*/
template<class T,class TN> //两个class类型被使用
class Demo{
private:
T val; //模板声明类型T,才可以使用该变量
TN tn; //模板声明类型TN,才可以使用该变量
double d; //operator()中使用的第三个参数
public:
Demo(const T& t,const TN& n,double do):val(t),tn(n),d(do){}
bool operator()(const T& t){statement;} //语句
}
注意:上述模板出现了两个class类型参数(T和TN),实际上没有可操作性(他只能保证语法不报错),实际使用中,用两个泛型参数产生逻辑的,笔者暂时还没见过.
接下来是模板函数转换成单个参数的函数对象的方法.原文:因此,调用tB100(x)相当于调用tooBig(x, 100),但两个参数的函数被转换为单参数的函数对象,其中第二个参数被用于构建函数对象。简 而言之,类函数符TooBig2是一个函数适配器,使函数能够满足不同的接口。
----分析:函数适配器,为了满足谓词里只有一个参数的格式.如何操作如引申内容所示,把谓词函数形参以外的参数作为模板类对象属性传入.
小结
函数符是创造的一个新概念.将函数,函数指针,类对象函数符作为class对象使用
对比如下:
用普通函数来提供逻辑的实现,考虑的是形参和返回值----当形参值等于什么值,得到什么结果,返回值是什么 .形参和返回值应该用什么数据类型等等.
函数符的设计者设计了一套规则,函数符引入了谓词,二元谓词等概念,使代码的编写围绕着如何设计谓词,谓词如何与容器对象交互上(这个似乎有一定限制:自己设计个三元谓词不知道可不可行).如 代码块1 所示,他们起到作用是一样的.函数符的特点是把函数当成参数,使代码的形式做了改变.
typedef bool (*pf)(const T& t); //谓词的类型定义
typedef bool (*f)(const T& t1,const T& t2) //二元谓词的类型定义