C++基础语法:STL之函数对象(一)

前言

      "打牢基础,万事不愁" .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)             //二元谓词的类型定义

         

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

jllws1

你的鼓励是我创作的动力,谢谢

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值