C++ day 39 函数对象(也叫函数符)

是什么:可以以函数方式和()结合使用的任意对象

包括了

  • 函数名:其实函数名就是指针,所以这一条应该包括在第二条里。
  • 函数指针:指向函数的指针
  • 类函数符:重载了()运算符(即定义了函数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
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值