函数对象

STL中,很多算法提供通过回调函数的方式来定义内部的具体实现细节。说回调函数其实不准确,因为模板实参不一定是函数指针,模板函数的内部只是通过模板实参调用了operator(),模板实参只需要支持operator()就可以。所以,除了函数外,函数对象、Lambda表达式等可调用对象也可以用于算法,定义其内部实现细节

1、函数对象
函数对象是指内部实现了operator()的类对象,可以提供类似函数的行为:

class Print
{
public:
	void operator() (string s)
	{
		cout << s << endl;
	}
};

Print p;
p("hello world");

结合算法使用:

Print p;
vector<string> vec = {"something ", "i ", "want ", "just ", "like ", "this\n"};
for_each(vec.cbegin(), vec.cend(), p);

函数对象相比普通函数的一个优势是可以通过成员变量拥有状态:

class Print
{
public:
	Print() : cnt(0)
	{}
	
	void operator() (string s)
	{
		cnt++;
		cout << s;
	}

private:
	int cnt;
};

Print p;
vector<string> vec = {"something ", "i ", "want ", "just ", "like ", "this\n"};
for_each<vector<string>::const_iterator, Print&>(vec.cbegin(), vec.cend(), p);

上面代码统计对象p输出的字符串数目。但是,算法中传递的函数对象默认情况下按值传递,如果要按引用传递,必须显示指明模板实参为引用类型:for_each<vector::const_iterator, Print&>

2、预定义函数对象
C++标准库提供的预定义函数对象定义在 <functional>中:

函数对象作用
negate<>()-param
plus<>()param1 + param2
minus<>()param1 - param2
multiplies<>()param1 * param2
divides<>()param1 / param2
modulus<>()param1 % param2
equal_to<>()param1 == param2
not_equal_to<>()param1 != param2
less<>()param1 < param2
greater>()param1 > param2
less_equal<>()param1 <= param2
greater_equal<>()param1 >= param2
logical_not<>()!param
logical_and<>()param1 && param2
logical_or<>()param1
bit_and<>()param1 & param2
bit_or<>()param1
bit_xor<>()param1 ^ param2

3、函数适配器bind
函数适配器,也叫改造器,是指改造现有函数,以适用于当前环境的一种机制,改造的结果仍表现为函数,所以bind也可以搭配算法使用。bind不仅适用于函数,可以用来将参数绑定于可调用对象,并可利用预定义占位符指定可调用对象真正使用的实参。

使用bind调用全局函数
假设有个计算矩形面积的接口square,传参是长和宽,计算单位为cm,但实参单位不一定是cm,于是:

float square(float l, float w)
{
	return l * w;
}

auto squareAdapter = bind(square, bind(divides<float>(), _1, _3), bind(divides<float>(), _2, _3));
squareAdapter(12.0, 5.0, 10);		// 传参单位为mm
squareAdapter(12.0, 5.0, 1);		// 传参单位为cm

使用bind调用成员函数和成员变量

class Rect
{
public:
	float square(float l, float w)
	{
		s = l * w;
		return s;
	}
	
	float s;
};

Rect rect;
// 调用成员函数
auto squareAdapter = bind(&Rect::square, rect, bind(divides<float>(), _1, _3), bind(divides<float>(), _2, _3));
squareAdapter(12, 5, 10);	// 展开为squareAdapter(rect, 12, 5, 10),相当于rect.square(divides(12,10), divides(5, 10))

// 绑定成员变量
auto squareReader = bind(&Rect::s, rect);
squareReader();		// 展开为squareReader(rect),相当于rect.s

使用square绑定类成员时,实际调用的第一实参需要是对应类对象:squareAdapter(rect, 12, 5, 10)squareReader(rect)

预定义定位符
上面代码中的_1_2等属于定位符,定义于命名空间std::placeholder。定位符指定了实际函数调用时使用的调用实参。

利用bind调用函数时,实际函数的传参由bind的表达式定义了基本框架,再用实参根据定位符进行替换。例如:

auto squareAdapter = bind(square, bind(divides<float>(), _1, _3), bind(divides<float>(), _2, _3));
squareAdapter(12.0, 5.0, 10);

实际调用square函数的框架为:

square(divides(_1, _3), divides(_2, _3));

再用对应位置的实参替换定位符:

//squareAdapter(12.0, 5.0, 10);
square(divides(12.0, 10), divides(5.0, 10));

例子中还使用了嵌套bind。内层bind在外层bind对应的位置进行函数调用,函数返回值作为外层bind当前位置的函数实参。内层和外层bind的定位符共用序列。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值