文章目录
是什么:可以以函数方式和()结合使用的任意对象
包括了
- 函数名:其实函数名就是指针,所以这一条应该包括在第二条里。
- 函数指针:指向函数的指针
- 类函数符:重载了()运算符(即定义了函数
operator()()
)的类对象
啥类的都行,只要重载了()运算符,且重载内容也是随便写,没有限制。
还是第一次重载()运算符呢。。
感觉很不好理解,举个例子吧:
class Linear{
private:
double slope;
double y0;
public:
Linear(double s = 1, double b = 0):slope(s),y0(b){}
double operator()(double x){return x*slope+y0;}
};
Linear f1;//使用默认参数
Linear f2(10, 100);
double y1 = f1(2.5);//相当于f1.operator()(2.5),2.5*1+0
double y2 = f2(3);//3*10+100,很像函数的使用方式,但f2只是Linear类的对象而已
示例:for_each函数的第三个参数其实就是函数符
之前我们用的时候,都是传入的函数指针,一般都是传的函数名嘛,函数名就是指针,指向函数存储的内存位置。其实,底层实现上,for_each函数的接口里,第三个参数是一个类的对象:
template<class InputIterator, class Function>
for_each(InputIterator first, InputIterator second, Function f);
比如之前的使用:
for_each(a.begin(), a.end(), ShowReview);
void ShowReview(const Review &);
所以标识符ShowReview
的类型是void(*)(const Review &)
,传入for_each
函数后, Function
的类型就是void(*)(const Review &)
。在这个例子中,f是函数指针,f()
调用该函数。
而在刚才的Linear
类的示例中,传给Function
的类型是Linear
, f是Linear
类的对象,f()
调用的是Linear
类的重载的operator()
函数。
STL给函数符的定义
STL给容器下了定义,即容器到底是个什么概念;
给迭代器也做了概念的定义;
它给函数对象,也做了定义,描述了它的通用概念。
这里涉及大量新的术语,脑容量警告····新知识警告···
- 生成器:generator,不需要参数就可以调用函数的函数符
- 一元函数:unary function,只要一个参数就可以调用的函数符
for_each函数的第三个参数就是一元函数。因为他要把容器内的每一个元素作用于这个函数。
- 二元函数:binary function, 要俩参数
- 谓词:predicate, 即返回bool的一元函数
举例
bool tooBig(int n){return n>100;}//谓词
list<int> scores;
scores.remove_if(tooBig);//把谓词作为参数,remove_if是list模板的成员函数,它把谓词应用于区间里的每一个元素。这句代码的作用是:删除链表中所有大于100的元素。
- 二元谓词:binary predicate, 返回bool 的二元函数。
举例
//二元谓词
bool WorseThan(const Review & r1, const Review & r2);
sort(books.begin(), books.end(), WorseThan);//把二元谓词作为第三个参数
。。。。感觉学起了英语,,或者语文。。谓词都来了,果然我是在学语言呢
示例:类函数符,list模板类的remove_if方法
#include <iterator>
#include <algorithm>
#include <iostream>
#include <list>
void out_int(int n){std::cout << n << " ";}
template<class T>
class TooBig{
private:
T cutoff;
public:
TooBig(const T & t = 0):cutoff(t){}
bool operator()(const T & t){return t > cutoff;}
};
int main()
{
using std::list;
using std::cout;
using std::endl;
TooBig<int> f100(100);//limit = 100,f100是函数符,也是对象
//c++11新增的列表初始化方法
list<int> yada = {12, 3, 5, 8, 78, 95, 785, 351, 98, 102};
list<int> huha {45, 56, 89, 789, 456, 123, 258, 147, 369, 130};//可以省略赋值符号
cout << "Original lists:\n";
for_each(yada.begin(), yada.end(), out_int);
cout << endl;
for_each(huha.begin(), huha.end(), out_int);
cout << endl;
yada.remove_if(f100);
huha.remove_if(TooBig<int> (200));//匿名对象
cout << "New lists:\n";
for_each(yada.begin(), yada.end(), out_int);
cout << endl;
for_each(huha.begin(), huha.end(), out_int);
cout << endl;
return 0;
}
Original lists:
12 3 5 8 78 95 785 351 98 102
45 56 89 789 456 123 258 147 369 130
New lists:
12 3 5 8 78 95 98
45 56 89 123 147 130
C+11之前,不能用列表初始化list对象,但是可以用基于范围的构造函数,range constructor
int a[] = {12, 3, 5, 8, 78, 95, 785, 351, 98, 102};
list<int> yada(a, a+10);//range constructor
STL预定义的基本函数符:对所有内置的算术,关系,逻辑运算符都提供了等价函数符
示例(引入):transform()函数
- 第一个版本:4个参数,前两个是指定区间的迭代器,第三个是输出位置,第四个是函数对象,且是一元函数。
- 版本2:5个参数,前两个是指定区间的迭代器,第三个是另一个区间的起始位置(由于两个区间一一对应,所以不需要第二个区间的结束位置)。第四个是输出位置,第5个是函数对象,必须是二元函数。
gr8,m8都是vector<double>
类的对象,而mean函数是mean(double, double)
如果要相加,则自己定义一个add函数
可以看到,这里用了sqrt和mean,add函数符,但是都要自己定义,很麻烦,且不同类型又要重新定义。。。这违反了代码重用,所以,STL出招了。
更好的办法
STL提供这些函数符主要是为了支持STL的很多要把函数作为参数的函数。
STL的很多函数需要把函数作为参数,可见其高级。
这些需要被STL函数作为参数的函数,基本都是为了执行某种运算的,也就是基本的那些算术,关系,逻辑运算,比如把两个值相加,相减,乘除等。
所以,STL一不做二不休,就直接把所有的内置运算符全部都提供了等价的函数符。即给每一个运算符都专门又定义了一个模板类,然后让用户可以自行用任意数据类型去具体化这个类,创建对象,这个对象就是函数符,就可以用来作为STL函数的参数,并完成需要的运算。
他们都在头文件functional
中。他们类内部都是重载了()运算符以实现自己作为运算符的功能,所以对象都是函数对象。
示例
#include <functional>
plus<double> add;//add是一个plus<double>对象,可作为函数对象
double y = add(2.3, 4.5);//重载了()运算符为二元函数实现相加运算
transform(gr8.begin(), gr8.end(), m8.begin(), out, plus<double>());//默认构造函数创建的匿名对象
自适应函数符
看了很久,没有明白什么叫做自适应的函数符,真的没懂,但是也还是明白了一些东西:
首先, STL有5个和自适应有关的概念:
- 自适应生成器
- 自适应一元函数
- 自适应二元函数
- 自适应谓词
- 自适应二元谓词
之前说这几个概念都只是没加自适应三个字而已。
其次,自适应函数符和函数适配器有关系,后者必须基于前者。然而函数适配器是啥我也至今是一知半解。。
总之大概意思是:
函数适配器用于把一个自适应二元函数(必须是自适应的)转换为一个自适应一元函数,以被那些只接受一元函数作为参数的STL函数。
举例子:
multiplies()函数符,即刚才说的STL预定义的函数符之一,它当然需要俩参数啦,把俩参数相乘,但是,如果用在transform函数的第一个版本(4个参数)中,前2个迭代器参数就指定了multiplies()的一个参数,我们只需要传一个常量参数,比如让区间的每个元素都乘以20, 但是transform函数的第四个参数只接受一元函数呀,所以这时候就要用到函数适配器。
函数适配器(binder1st, binder2nd
类):把接受2个参数的函数符转换为接受1个参数的函数符
适配器,都是完成某种转换任务的,比如电源适配器,家里的220V交流电转换为电脑的12V直流电。这里函数适配器也是完成转换工作,所以要起名为适配器啊。
STL用两个类来自动完成函数适配的转换任务:binder1st, binder2nd
类。
这两个类很相似,只介绍第一个。
即调用f1(x)相当于调用f2(val, x),即这个适配器默默把参数val在其内部提供了,其实就相当于再给f2函数穿了一层衣服一样的。
示例
#include <iostream>
#include <vector>
#include <iterator>
#include <algorithm>
#include <functional>
void show(double x)
{
std::cout.width(6);
std::cout << x << ' ';
}
int main()
{
using namespace std;
const int N = 5;
vector<double> arr1 {1, 5, 8, 9 , 78};
double arr[N] = {45, 56, 12, 23, 46};
vector<double> arr2(arr, arr + N);
cout.setf(ios_base::fixed);//定点表示法
cout.precision(1);
cout << "arr1:\t";
for_each(arr1.begin(), arr1.end(), show);
cout << endl;
cout << "arr2:\t";
for_each(arr2.begin(), arr2.end(), show);
cout << endl;
//相加
vector<double> arr3(N);//必须分配空间
transform(arr1.begin(), arr1.end(), arr2.begin(), arr3.begin(), plus<double>());
cout << "arr3:\t";
for_each(arr3.begin(), arr3.end(), show);
cout << endl;
//相乘
vector<double> prod(N);
transform(arr1.begin(), arr1.end(), prod.begin(), bind1st(multiplies<double>(), 2.5));//arr1的每个元素乘以2.5
cout << "prod:\t";
for_each(prod.begin(), prod.end(), show);
cout << endl;
return 0;
}
arr1: 1.0 5.0 8.0 9.0 78.0
arr2: 45.0 56.0 12.0 23.0 46.0
arr3: 46.0 61.0 20.0 32.0 124.0
prod: 2.5 12.5 20.0 22.5 195.0