C++基础语法:STL之函数对象(二)_预定义的函数符

前言

      "打牢基础,万事不愁" .C++的基础语法的学习."学以致用,边学边用",编程是实践性很强的技术,在运用中理解,总结.

引入

        STL(标准模板库)的学习.以<C++ Prime Plus> 6th Edition(以下称"本书")内容理解 .上一篇贴子C++基础语法:STL之函数对象(一)-CSDN博客接续

回顾函数对象

        1>从函数到函数指针

        函数的作用是表达逻辑,由返回值类型,函数名,形参列表和函数内部逻辑所组成. 

/*伪代码:普通函数声明*/
//标识返回值类型为ResultType,函数名为common,形参类型为ParaType的函数
ResultType common(ParaType pval);    

        函数指针是一种数据类型,指向符合函数指针描述的所有函数.以下是函数指针声明

/*伪代码:函数指针类型声明*/
/*fp是函数指针名*/
typedef ResultType (*fp)(ParaType pval)  //标识返回值类型为ResultType,形参类型为ParaType的函数

        以下的fun函数是被函数指针fp指向的函数

/*伪代码:函数声明*/
/*fun是被fp指向的函数*/
ResultType fun(ParaType pval);    

        函数指针可以作为函数形参使用 

        代码块1

/*伪代码:函数指针作形参*/
ResultType pfun(fp fpp,ParaType pval){   //fp:函数指针的类型,fpp是fp类型变量
    return (*fpp)(pval);                 //*fpp代表传入的函数
}    
======================
pfun(fun,pval);               //传入函数名fun,fun既表示函数常量,也表示函数指针变量,类似字符串

        比较函数common的调用和pfun的调用,形式上做了一点修改,本质上没有什么不同.

        下面红色部分说明了函数指针的优点:

        使用函数指针作参数的函数,灵活度高一点.例如common和fun都是被函数指针fp所指向的函数.

/*伪代码:pval是ParaType类型值或者变量*/
common(pval);         //调用common函数
pfun(common,pval);    //调用pfun函数,实际上调用common(pt),和上一个相同
pfun(fun,pval);       //调用pfun函数,实际上调用fun(pt);

        更多和函数指针有关的内容在 C++基础语法:函数指针-CSDN博客

        整理函数和函数指针的思路:

         函数是处理数据的工具.被处理的数据以形参列表出现,函数名是处理数据的方法;当函数的形参列表和返回值类型相同时,则他们属于同一函数类型,可以被同一函数指针所指.

        在函数指针作参数的函数里,传入函数名和函数形参列表,调用该函数(如代码块1),pval被函数指针fp处理.调用时传入fp所指向的函数.

        此外,函数指针还有另一个优点:把函数指针还原后的函数,产生的返回值,作为外层函数的输入值使用.也就是说:使用函数指针做参数的函数,可以完成两段逻辑(把一个函数看作一个逻辑).

        2>从函数指针到函数对象

        假如现在有个处理容器数据的需求,因此设计出一个函数调用,形式是:        

//伪代码
//在模板类里声明一个属性接收返回值,这里返回值用void
void 函数名(迭代器对象1,迭代器对象2,函数对象);        //函数指针替换成函数对象

         含义:把迭代器对象1到迭代器对象2之间的元素,交给函数指针处理.由于函数指针必须指明类型(返回值类型和形参类型),所以不能把函数指针用于容器,因此产生了函数对象的概念.这里有个问题:函数对象怎么可以作为class对象的?要看源码或者编译器是怎么定义的.但作为使用他的程序员,可以不关心,照着格式写能用就行.

        函数对象是用模板类来实现的.复习一下模板类的成员情况.当模板类传入class对象T时,属性可以有T类型的数组(数组个数一般用和class平级的int类型传入),T类型指针及双重指针(等);模板内部类可以有T类型对象(及指针);模板对象函数可以用T类型作返回值类型和形参类型.

        模板类行使函数对象(函数指针)的作用,需要放置容器元素T的位置.从声明可以看出T可以放到属性里,重载"()"的函数的形参里,或者内部类的属性里;可以以"值"(变量)的形式存在,也可以以"指针"(双重指针)的形式存在.

//伪代码:模板类定义
//模板中可以产生和T类型相关的数据
template<class T,int MAX=10>
Demo{
private:
    T t;
    T a[MAX];
    T* tp;
    class Node{
    public:
        T nt;
        ...;
    }
    Node * np;
public:
    构造函数略;
    T fun(T val);
}

         字面意思上再去理解什么是函数对象:模板类对象,调用重载的"()"运算符,做处理容器元素的函数指针.两个原因:一是用模板的对象属性来识别容器内的泛型元素(两边都是T),二是做函数指针的形参个数可以不确定,用对象属性来接收.一元函数,二元函数,谓词,二元谓词的概念是为了规范函数对象而设立的.

预定义的函数符

        本书P710中间16.5.2

        STL定义了多个基本函数符,它们执行诸如将两个值相加、比较两个值是否相等操作。提供这些函数对象是为了支持将函数作为参数的STL函数。例如,考虑函数transform( )。它有两个版本。第一个版本接受4个参数,前两个参数是指定容器区间的迭代器(现在您应该已熟悉了这种方法),第3个参数是指定将结果复制到哪里的迭代器,最后一 个参数是一个函数符,它被应用于区间中的每个元素,生成结果中的新元素。 (黑体字是原话)

        ----解读:函数符是定义好的模板类,模板类对象的重载"()"函数,处理容器类元素,得到一个返回值.返回值作为参数,实现外层的逻辑.像容器类一样,STL提供了"标准化"的函数符给程序员使用

         下面几行是书中代码:

const int LIM=5;
double arr1[LIM]={36,39,42,45,48};
vector<double> gr8{arr1,arr1+LIM};
ostream_iterator<double,char> out(cout," ");
transform(gr8.begin(),gr8.end(),out,sqrt);

         上述代码计算每个元素的平方根,并将结果发送到输出流。目标迭代器可以位于原始区间中。例如,将上述示例中的out替换为gr8.begin( ) 后,新值将覆盖原来的值。很明显,使用的函数符必须是接受单个参数的函数符

        ----解读:这几行代码做了几件事:生成了一个vector<double>对象gr8,调用transfor函数,生成了对象元素的平方根,把结果发给了输出流.

        使用的函数符必须是接受单个参数的函数符:这句话,因为transform函数指定了这里的函数符是个一元函数的函数指针,所以才说"必须接受单个参数的函数符".假设是模板对象的函数符,这里是不一定成立的,因为模板类对象的函数符可以不用参数. 下面将实现的模板类myplus就没有用参数

        第2种版本使用一个接受两个参数的函数,并将该函数用于两个区间中元素。它用另一个参数(即第3个)标识第二个区间的起始位置。 例如,如果m8是另一个vector对象,mean(double,double) 返回两个值的平均值,则下面的的代码将输出来自gr8和m8的值的平均值:

transform(gr8.begin(),gr8.end(),m8.begin(),out,mean);

        现在假设要将两个数组相加。不能将+作为参数,因为对于类型 double来说,+是内置的运算符,而不是函数。可以定义一个将两个数相加的函数,然后使用它:

//本书代码
double add(double x,double y){return x+y;}
...
transform(gr8.begin(),gr8.end(),m8.begin(),out,add);

        ----解读:讲了transform函数的两种重载形式.因为没有源码,只要学着用就行了.他们有个共同特点:函数符用的是函数指针. 为什么可以用函数指针,因为在transform函数调用之前已经确定了容器内元素类型(这里是double类型).所以本书下面讲了模板类对象函数符来解决泛型问题.

        然而,这样必须为每种类型单独定义一个函数。更好的办法是定义一个模板(除非STL已经有一个模板了,这样就不必定义)。头文件functional(以前为function.h)定义了多个模板类函数对象,其中包括 plus< >( )。可以用plus< >类来完成常规的相加运算:

#include<functional>
...
plus<double> add;
double y=add(2.2,3.4);        //using plus<double>::operator(){}

        这里有一个小问题:原书说的是"using plus<double>::operator(){}" ,笔者认为不是调用的重载的()---无参形式,而是调用了"T operator()(const T& t1, const T& t2);"---有参形式.有一种方法,默认参数可以把两种形式合起来,但试了下编译不能通过.

        ----解读: 模板类传入参数作属性,实现两个数组内元素的加法.

        注意:以下代码只为尝试还原plus的作用,不与源码一致

/*已测试,plus是STL已定义的模板,不能重名,用了myplus*/
/*模板类一样可以没有属性,用作模板函数*/
#include<iostream>
using namespace std;

template<class T>
class myplus {
	T para_one;										//参数1
	T para_two;										//参数2
public:
	myplus(T t3, T t4) :para_one(t3), para_two(t4) {};
	myplus() {};									//空构造函数
	T operator()(const T& t1, const T& t2);
	T operator()();
};

template<class T>
T myplus<T>::operator()(const T& t1, const T& t2) {	//重载函数对象()
	return t1 + t2;
}

template<class T>
T myplus<T>::operator()() {							//无参数的函数对象()
	return para_one + para_two;
}

        测试代码

int main(void) {
	myplus<double> add;                        //和原书代码意思一致
	double y = add(2.2, 3.4);                  //和原书代码意思一致
	cout << "加法结果是:" << y << endl;

	myplus<double> add1(2.2, 3.4);
	double z = add1();                      
	cout << "第二个加法结果是:" << z << endl;
}

======================内容分割线========================================== 

以下内容只为推导,不保证准确 

        它使得将函数对象作为参数很方便:       

transform(gr8.begin(),gr8.end(),m8.begin(),out,plus<double>());

          这里,代码没有创建命名的对象,而是用plus构造函数构造了一个函数符,以完成相加运算(括号表示调用默认的构造函数,传递给transform( )的是构造出来的函数对象)

        ----解读:这里笔者开始没有看懂,参数是怎么传给函数符的?

        推导:重载()运算符,没有参数是可以的,只要在构造方法中把数据传给属性.默认构造方法传数据也是可以的,只要用到默认参数,即:        

myplus(T t3=n1, T t4=n2) :para_one(t3), para_two(t4) {}; //构造函数

        但是模板类是单独定义的,不会识别n1和n2.除非模板类在transform内部定义,这也是不可能的.最后还有一个,就是参数由模板传入,所以plus(STL定义的模板类)的抬头定义应该是:

template<class T,T n1=val1,T n2=val2>
class plus{}

        配合有默认参数的构造函数,默认传入val1和val2.那么在transform函数编写时,可以将迭代器里的值,传给val1和val2.这样才是可行的.

        重写myplus的定义


template<class T,T n1=val1,T n2=val2>
class myplus {
	T para_one;										//参数1
	T para_two;										//参数2
public:
	myplus(T t3=n1, T t4=n2) :para_one(t3), para_two(t4) {};
//	myplus() {};									//空构造函数不要用了,上面一行已实现
	T operator()(const T& t1, const T& t2);
	T operator()();
};

template<class T>
T myplus<T>::operator()(const T& t1, const T& t2) {	//重载函数对象()
	return t1 + t2;
}

template<class T>
T myplus<T>::operator()() {							//无参数的函数对象()
	return para_one + para_two;
}

        尝试transform函数的伪代码(只表示过程)

//函数调用
transform(gr8.begin(),gr8.end(),m8.begin(),out,plus<double>()){}

/*伪代码:两个容器元素相加*/
//generator生成器类型,对应调用的是plus<double>()
transform(InputIterator it1,InputIterator it2,InputIterator it3,ostream_iterator oi,generator gr)
{
    auto pr2=it3;                    //迭代器指向被加的数组元素
    for(auto pr=it1;pr!=it2;pr++){   //遍历加的数组
        gr.val1=*pr2++;              //传递给plus的第二个模板参数,指针+1
        gr.val2=*pr;                 //传递给plus的第三个模板参数
        T t=gr();                    //调用默认构造函数的()无参运算符,计算结果交给t
        oi(t);                       //输出
    }
}

        上述代码只看意思还有问题,至少被加的数组元素个数不能少于做加法的数组,否则出bug,但只说明个意思,不做多余分析. 

======================内容分割线========================================== 

        后面有一张表, 对于所有内置的算术运算符、关系运算符和逻辑运算符,STL都提供了等价的函数符。表16.12列出了这些函数符的名称。它们可以用于处理C++内置类型或任何用户定义类型(如果重载了相应的运算符)

        ----解读:内置类型数据集合可以用表中函数符进行计算,自定义类型重载相应运算符,也可以实现运算.例如:自定义的类型,重载了operator+(),那么两个对象也可以相加.具体内容见本书第11章"使用类"

   小结

        模板类做函数对象是除了做容器后的一大应用方向,作为处理容器内元素的函数指针.   

        模板类是非常灵活的,一传值很灵活,可以从模板参数传,或者默认参数传; 二对象函数设计,可以用属性也可以不用;

        函数指针做参数的函数,与非函数指针做参数的函数,优势体现在可以表现多段逻辑.     

        默认参数的使用和定义在C++中,写是很好写,读是好难读.站在使用者,每次看见一个函数调用,还得想想是不是有隐藏的"因"在,着实不太舒服.白话理解一下默认参数:传进来就用你的,不传就用自己的.

   更新

        预定义函数的解读和实现在另一篇帖子(自用)仿写程序-CSDN博客里,这篇后面推导部分可以不看

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

jllws1

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

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

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

打赏作者

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

抵扣说明:

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

余额充值