接下来咱们来说这个STL里面一个很重要的概念函数对象,什么是函数对象呢, 如果一个类重载了圆括号运算符,那么这个类的对象就成为函数对象,然后下面这个CMyAverage 它重载了圆括号运算符,它的功能呢是求三个参数的平均值,然后返回 那如果我们定义了一个CMyAverage的对象,这个Average那我们就称这个Average是 这一个函数对象为什么呢,因为这个Average我们会按下面这个方式来使用, 啊就是后面跟个圆括号里头给参数,那我们以前会在什么东西后面跟着圆括号呢, 一般来说我们会在一个函数的名字后面,跟圆括号里面给参数或者是在一个函数指针的 后面跟圆括号里面给参数,可这块并不是函数也不是函数指针,是一个对象 这个表达式依然能够成立,那是为什么呢?当然是因为 这个表达式它就等价于Average.operator().(3,2,3) 也就说这个表达式,它看上去像个函数调用的形式,而且它实际上也执行了一个 函数调用,而这个Average又不是一个函数,它是一个对象,所以我们把这个Average又称为函数对象, 那刚才Average这个表达式它的返回值就是3,2,3的平均值就是2.6667 那函数对象是在STL里面就有很广泛的应用的, 呃,我们来看一个accumulate函数模板的例子, 呃,这是在Dev C++里面accumulate的源代码, 这个函数模板它有两个类型参数,一个是Inputlterator, 一个是Tp 这个Inputlterator就暗示你这个类型参数应该是一个迭代式类型的, 起码说可以是一个迭代式类型的,然后我们看这个accumulate的函数参数有三个,一个first,last,init 那这个first,last很像是这个accumulate所操作的容器上面一个区间的 起始位置和终止位置,这个区间都是左闭右开的,那这个init呢, 顾名思义,它应该像是一个起始值,初始值,那我们看到这个accumulate在里面做了什么事情, 它从一个循环,让first走到last,然后把这个 新first加到init上面 那么也就是说这个循环实际上在做一个累加的工作 怎么累加呢,就是把first,last这个区间里面,每一个元素 都通过一个加号加到初始值init上面,然后再返回到最终的这个init, 那这块这个typename就等价与class的啊,而且这个class也是一样的 那这个accumulate在STL里面它是有两个版本的,这边列出了第二个版本 比刚才第一个版本多出来一个类型参数, BinaryOperation,于是它的这个accumulate的函数参数也多了一个Binary_op 那我们看看accumulate这个版本源代码是这样的啊,就是 它对这个区间,first,last里面呢每一个元素 都拿它来跟这个初始值init 做一个运算,然后把运算的结果呢,又赋值给这个初始值,下次呢又用这个新的初始值和下一个元素做运算, 然后最终返回这个初始值, 那我们看accumulate是一个函数模板,那将来从这个模板实力化出来的 函数里面也会原模原样的出现这一条语句,那么为了使这个 表达式能够编译通过这个Binary_op本身是一个什么东西呢? 它可以是个函数指针,可以是一个函数的名字,也可以说一个函数对象, 如果这个Binary_op是一个函数对象的话, 那么这个表达式实际上就是在调用这个函数对象operator的圆括号成员函数,所以是说得通的, 那也就是说我们真正的调用accumulate模板的时候, 最后一个参数呢,可以是函数名字,函数指针,或者是函数对象。 那下面我们通过一个程序来看一下这个函数对象到底如何起作用, 呃,在一个程序里面呢,有个SumSquare这个函数,它的功能是把 第二个参数的iii加到第一个上面,然后返回,啊记住这个函数的功能, 呃,这个Printinterval这个模板它的功能就是把一个区间first,last里面的 每一个元素都给它打出来, 然后这里有一个类模板,SumPowers, 它的作用实际上是求 一个区间的,呃,power次方的和, 呃, 这个power要求多少次方的和,它的构造函数 就初始化这个power,然后它的圆括号成员函数是在求这个power次方的和, 把这个和加到total上面,我们看到这里面 要计算value的power次方,然后加到total上面, 一开始让这个一个iii v的值等于value,然后在下面这个循环里面 让这个v成了power减一次value, 那当然算下来这个v就是value的次方,然后我们把这个power次发 加到了这个total上面,返回。在这里面呢有一个数组a1, 里面有1,2,3,4,5,6,7,8,9,10,然后我们进行一个vector int这样的容器类, v是这样一个容器类的对象, 它初始化的方式就是用这个a1数组去初始化这个v,那经过这条语句以后,v里面所包含的 内容就是从a1这个数组里面拷贝过来的,啊,a 这个size一共是10所以就把十个元素都拷贝过来了, 呃,里面就是1,2,3,4,5,6,7,8,9,10 那你输出,啊,从v.begin到v.end当然输出值就是1,2,3,4,5,6,7,8,9,10 接下来呢我们调用的这个accumulate accumulate作用在整个v上面 然后给一个初始值0,然后呢要所做的这种累加的操作呢是求平方和, 所以这条语句实际上就会求出, v上面所有元素的平方和 那因此我们输出result的结果就是平方和385 具体为什么会是这样一个效果,等会再解释一下, 那么下面这条accumulate语句,它就要求这个 v上面所有元素的立方和,如何求立方和呢,在这里用到了这样一个函数对象, 呃,我们看到,SumPowers 是一个类模板的名字, 那么SumPowers int, 就是一个模板类的名字,啊,在类模板后面跟尖括号,尖括号里面给出了具体的类型, 那你得到的就是一个类的名字,然后在类的名字后面又跟了一个圆括号,再给了一些构造函数的参数, 那么这整个的东西都是一个对象的名字了, 只不过它是一个临时对象,那在,那这个对象实际上那就是一个函数对象,因为它所属的类是重展的圆括号, 然后在这个对象里面那个power的值就是3,也就是说这个对象它的圆括号工作的时候, 自然就会求3次方的这个和 呃,所以下面这个第三行输出输出的这个立方和:3025 那么至于下面这一行呢,那当然就是求4次方的和,所以就输出这个数字。 那么来看这个accumulate具体是怎么工作的啊, 那么第一条调用的accumulate语句是这样的, 呃,那么根据从这条调用语句就会 经由accumulate模板实例化出来一个accumulate函数, 那这个函数的各个参数是什么样的呢?啊,首先我们第一个实参是v.begin,那v.begin类型是什么就啊? 是vector int上面的迭代器,啊,所以accumulate上面的 实例化出来的函数的第一个参数就是,就是这样的,它叫first, 那v.end的类型当然也是vector int上面迭代器,所以accumulate的第二个参数是这样一个last。 第三个参数0就对应于整形参数, 那第四个参数呢是一个函数的名字, 那么,呃,什么样的形参才能够跟 函数的名字相匹配呢?当然函数指针式可以跟函数名字相匹配的, iii这块实例化出来的accumulate函数里面,最后一个参数就是这个函数指针op 这个op所指向的函数返回值是int,然后才有两个整形参数, 这正好跟SumSquares的特点是相同的, 那我们看到在这个被实例化出来的这个accumulate的代码里面, 当然就用到了这个op,那我们看到这个表达式是实际上是做什么啊, 就是调用了SumSquares, 那么说SumSquares它的功能是什么呢?就把第二个参数的 平方加到第一个参数上面去,所以我们看在这个循环里面做了什么事情, 就是init一开始一个初始值,然后对first,last区间里面的一个元素, 把它和init做一个运算,然后把算出来的结果呢放到init里面, 得到一个新的init值,然后下一次循环呢就把它新的init的值再跟这个first last里面区间呢下一个元素再做一个运算,又得到一个新的这个init的值, 那我们知道这个op做的实际上就是把第二个参数的平方值加到第一个参数上面, 然后这边init的初始值又是0,所以这整个循环所做的事情就是对 first,last区间里面的每一个元素都平方下,然后再把它们全加起来, 就是求平方和, 那我们再看这个第二条accumulate语句被调用的时候,会实例化出什么东西, 呃,前面呢参数都一样就没什么可说了,啊,那我们就看, 最后一个参数,它是一个函数对象,这个函数对象所属的类型本来就是SumPowers int 所以这块实例化出来的accumulate里面,最后一个参数就是这样的,SumPowers<int>op。 然后在这里所做的操作也是op(init,* first)。 那我们回忆一下这个 op现在就是一个函数对象,就是SumPowers<int>(3)。 也就是说,这个op 这个对象里面的Power成员变量它的值是3,那么在这里就调用了op的 圆括号成员函数,这个圆括号成员函数能做什么呢? 能做的事情就是把*first三次方加到初始值init上去。 所以经过这个循环以后啊,就会把这个first,last这个区间里面的 每一个元素的三次方都加到了这个init上面去。init的初始值 是0嘛。所以说,这整个循环所做的事情, 就是求出了first到last这个区间面的每一个元素的 立方的和。从这个例子呢,我们是能够看出来, 函数对象它的价值的。 同学们设想一下,如果我想用这个accumulate模板来求 v里面的元素的平方和,立方和,四次方和,五次方和, 如果你不用函数对象的话,该怎么解决?一种解决方案就是,我求平方和,我就写一个SumSquares函数用在这。 那我求立方和呢,我就写一个什么SumCurious函数用在这儿, 我要求四次方和呢,我还得另外再写一个Sum什么四次方的函数。 那你就要写一大堆的函数,那实在是不能忍,难受,对吧。 那如果我想只写一个函数,叫做SumPowers, 用在这儿,就能既求三次方和,四次方和,五次方和都可以。 那怎么办呢?关键是你,你只把函数名字写在 这的话,你没有地方把要求几次方和 这个几次方的信息传递给这个函数,除非你把这个几次方的信息用一个全局变量来记录。 然后你在调用这条语句的时候,事先把那个全局变量赋上一个值, 然后SumSquares这个函数内部呢,再去取得那个全局变量,然后才能知道要求多少次方和。 这当然是一种做法。但是这个做法呢? 非常不符合面向对象的思想,因为面向对象思想让我们要少用全局变量。 为了求一个几次方和,就专门开了一个全局变量在那里。 然后这个全局变量 在一个函数里面,又要访问,就显得这个变量也不独立,函数也不独立。 这个程序的所谓的局部性就不太好了,而我们用了这个函数对象呢, 我们在这里只用临时创建出来的函数对象,就能够来求三次方和,四次方和。 而且我们也不需要把求几次方和这个事情用一个全局变量去记录,我们 只需要把它设置成这个临时函数对象的一个成员变量就行了, 这样我们这个整个程序的这个局部性就很好,没有用到了全局变量。 在STL里面,实际上就有一些函数对象类模板,比如说有equal_to,greater,less等等。 这些东西首先它是模板,然后它们又都实现了圆括号这个成员函数。 所以我们就称之为函数对象类模板,那通过这些模板,我们可以生成函数对象。 这些模板呢,它们都在头文件functional里面定义的。 我们举一个例子,比如说有一个函数对象类模板,叫做greater,struct greater 这个greater呢,是从某个其它东西派生出来的,这个我们不管它,我们所关心的是 这个greater它重载了圆括号成员函数,这个成员函数呢,它 用来判断两个参数,谁大谁小,比大小的方式是使用这个大于号, 那x>y的话, operator圆括号返回值就是true,那我们要知道,如果用哪一个关联容器或者是哪一种算法 使用了这个greater模板作为比较器的话,那么这些容器或者是算法 比较两个元素x,y大小的时候,就会调用这里的operator圆括号成员函数,并且把x,y作为参数传进来。 那么这个函数的返回值如果为true的话,那么这些容器或者是算法就会认为x是 小于y的,但是我们看到这里面 实际上,当x>y这个表达式为true的时候, 整个函数的返回值才是true,也就是说这个greater 所定义出来的比大小的规则, 它是跟传统的方式是相反的,也就是说,传统意义上的这个大, 反而就意味着是小。所以greater有什么用呢,我们举一下list的这个例子, list这种容器里面,它有两个sort成员函数。 我们知道list上面的迭代器是双向迭代器,所以你没有办法用 STL的sort算法对list进行排序。但是如果你想对 list进行排序呢,你得调用list自己的sort成员函数。 list的sort成员函数两个版本。第一个版本是没有参数的。 没有参数的这个sort呢,它把list进行 排序的原则是按照小于号所规定的这个比较方法 顺序排列,就是说比大小是用小于号来定义的。那还有 另外一个sort的版本,是一个函数模板, 在这个版本的sort里面,有一个参数op, 这个op实际上就定义了比大小的这个规则。 也就是说,这个版本的sort在执行的过程中,在需要比较两个元素x,y大小的时候, 它会去看op(x,y)这个表达式的返回值。 如果这个表达式的返回值为true,就认为x<y,也就是说x需要排在y的前面。 我们看一个具体的例子。 在这里,我们写了一个MyLess,这个是一个 函数对象类,在这里面呢,我们重载了 圆括号,这个圆括号所规定的比大小的规则 是两个整数,谁的个位数小,谁就算小。 然后这下面有一个模板, 这个模板是用来输出first到last这个区间里面的所有元素的。 记住这个圆括号的规则是,谁的个位数小,谁就小。 然后我们去看看main里面,main里面有一个整型数组a, 里面有这么多个元素,然后我们定义了一个list容器, lst,它里面放的都是整形变量,然后用这种方式初始化的话,lst里面的内容就 是把a的内容给复制过来了,于是lst里面也就是5,21,14,2,3。 那下面我们就调用 lst的sort成员函数,对这个lst这个容器进行排序。 在这里,我们给出的参数是一个行助对象。 MyLess是一个类的名字,那这个类的名字后面跟圆括号, 实际上就是一个临时对象了,这个临时对象是用无参构的函数初始化的。 那也就是说,我们再要用这个sort的时候,我们就指明了, 这时候排序,比大小的规则是由MyLess这个函数对象决定的。 更准确地说,就是由MyLess里面的,这个类里面的圆括号来 决定比大小的规则。那也就是说,这个比大小的规则就是 哪个元素的个位数小,哪个元素就算小。 因此,经过这个排序以后, lst里面的元素实际上是按照个位数从小到大排序的。 所以我们这个lst的内容给它输出,就变成了21,2,3,14,5。 好,下面我们再次调用 lst的sort成员函数。但这次调用的时候呢,比大小的规则我们变了, 我们用一个greater<int>这个函数对象来作为比大小的规则。 greater是个类模板,那greater<int>就是一个 类,greater<int>再加个圆括号呢, 这就生成了一个临时对象, 它是一个函数对象,因为greater<int>这个类里面重载了圆括号。 而且那个圆括号,它所规定的比大小的规则是什么呢? 我们回忆一下,等于说就是 x>y要成立的话,实际上就认为x算是小的, 实际上它说的是这个意思。那因此说 lst.sort这条语句,实际上 对lst进行排序的时候,遵循的规则是, 哪个整数,它的这个大, 哪个整数大,这个大是数学上的大的意思啊, 实际上它就反而算小。因此这条语句会导致lst被 降序排列,所以输出的结果呢,是这个21,14,5, 3,2。 那现在我们引入了函数对 象这个概念以后,我们就有必要再重申一下在STL中使用自定义的“大”,“小” 关系这件事情了。 那有了函数对象,我们就发现,关联容器和STL中的许多算法, 都是可以用函数或者函数对象来自定义比较器的,就是定义比大小的规则。 如果在自己定义了比较器op的情况下,那下面三种说法就是等价的。 op(x,y)的返回指数以及y大于x的三个做法就是等价的了。 那我们看一个例题。 这个例题要求你写出my max模板 在这个例题里面有一个my less的函数对象类 它的圆括号规定了一个比大小的规则。这个规则是什么呢, 就是谁的个位数小谁就算小。 然后这个程序里面还有另外一个函数叫做mycompare 他也规定了两个常数比大小的规则。这规则是什么呢. 当a1的个位数小于a2的个位数他返回false 否则就返回true。所以这个mycompare规定的比大小规则是 谁的个位数大谁就算小。 下面我们看main里面这里有一个数组。 然后我们要用一个mymax模板求出 这个a里面的 这个所谓的最大值从名字上看最大值 但什么叫最大。有不同的定义。 那如果我们采用myless这个函数对象 所决定的定义,最大的意思就是个位数最大。 那如果我们采用mycompare规定的 比大小的规则。那最大地定义就变成 个位数最小了。 然后这个mymax它返回值是一个 迭代系或者指针类的东西。然后通过星号可以把指向可以取出来。 如果我们要求这个程序输出结果是19和12,也就是说按照myless所定义的这个 我们求出a数组里面的最大值。这个时候意味着个位数最大。 那我们按照mycompare定义的规则我们也可以求出a数组里面的最大值。 这个时候呢最大意味着个位数最小。谁的个位数最小谁就算最大。 所以输出就是19,12.那我们的问题是你如何去写这样一个mymax模板。 大家可以不妨先思考一下。先写一写然后再看答案。 这个mymax是一个函数模板。 这里面有个类型参数T,还有一个类型参数Pred 这个T呢可以是一个迭代系,first,last。 我们可以看到mymax的前脸个参数first,last是T类型的。 他用来指明mymax想要操作的区间的 起始位值和终止位值。 然后mymax的返回值当然也是一个迭代器。T的类型也是一个迭代器。那mymax的返回值 是一个迭代器,指向找到的那个最大元素。 然后mymax的第二个参数函数myless实际上 指明了这个比大小的规则。 那mymax的代码是怎么样的呢。 首先我们要假设一个最大值。 那我们就不妨加设最大值是区间开始的那个元素。现在我们用一个tempmax他是一个迭代器。 让他等于first,就是说我们假设tempmax总是 指向最大的元素。那一开始我们让他等于first。 然后我们就便利这个区间,从first走到last。 然后再便利的过程中我们有可能分析这个tempmax。 tempmax指向当前我们找到的最大的元素的那一个 迭代器。那我们怎么更新呢,我们就要拿*tempmax,就是当前的最大值 和*first就是我们新考察的那个元素去进行比较。怎么比较呢,就是用 myless这个东西去进行比较。那mymax这个模板被实例化以后,这个myless它很有可能是一个 函数指针,也有可能是一个函数对象。那不管是函数指针或函数对象后面都可以跟圆括号。 如果myless这个函数对象的话实际上在这里可以调用myless圆括号的成员函数 然后这个成员函数就会有一个返回值。如果这个返回值是true的话就意味着 这个tempmax所指向的函数是比first指向的元素小。 那这种情况下呢我们就要更新tempmax了。 那我们每发现一个比*tempmax更大的 元素,我们就更新tempmax让他指向更大的元素。那么我们最后 这个循环进行完以后tempmax就应该指向最大的元素。然后我们返回tempmax就行了。