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的定位符共用序列。