C++语言导学(7): 函数对象(Function Object)¶
函数对象(Function Object)¶
定义了operator()操作的对象就称为Function Object:
相比较一般的函数,函数对象是带有类型,进而可以有各自的状态,而且通常比函数指针更快。
这是一个通过函数对象对自定义类型在容器中如何排序的例子:
class Person{
public:
string firstname() const;
string lastname() const;
//...
};
class PersonSortCriterion{
public:
bool operator(cosnt Persion& p1, const Persion& p2) const{
return p1.lastname() < p2.lastname() ||
(p1.lastname() == p2.lastname() &&
p1.firstname() < p2.firstname());
}
};
auto func = []sortPerson(const Person& p1, const Person& p2{
return p1.lastname() < p2.lastname() ||
(p1.lastname() == p2.lastname() &&
p1.firstname() < p2.firstname());
}
set<Person, PersonSortCriterion> coll; // function obj
set<Person, decltype(func)> coll; // lambda形式
for(auto pos = coll.begin(); pos != coll.end(); ++pos){
//...
}
这里因为函数对象带有类型,可以作为set的模板参数传入; STL中有预定义的一系列函数对象,比如Greater升序排序。
函数适配器(Function Adapter): bind¶
函数适配器是指能够将不同的函数对象结合起来的东西,最重要的适配器就是 bind()
。
bind()
一般用来将参数绑定到可调用对象,还可以通过预定义占位符_1
,_2
,...等代指实参,通过bind()
定义的函数对象可以称为binder
。
比如:
#include <functional>
#include <iostream>
using namespace std;
using namespace std::placeholders;
int main() {
auto plus10 = bind(plus<int>(), _1,10);
cout << "+10 by 7: " << plus10(7) << "\n";
auto plus10time2 = bind(multiplies<int>(),plus10,2);
cout << "+10 * 2 by 7: " << plus10time2(7) << "\n";
auto pow3 = bind(multiplies<int>(),bind(multiplies<int>(),_1,_1),_1);
cout << "x*x*x by 7: " << pow3(7) << "\n";
auto inverDivede = bind(divides<double>(),_2,_1);
cout << "invdiv by 7/49: " << inverDivede(49,7) << "\n";
}
/*
output:
+10 by 7: 17
+10 * 2 by 7: 34
x*x*x by 7: 343
invdiv by 7/49: 0.142857
*/
}
binder
常用在算法函数中作为函数对象参数:
std::transform(coll.begin(), coll.end(), coll.begin(),bind(plus<int>(),_1,10)); //必须指明binder类型
auto pos = std::find_if(coll.begin(), coll.end(),bind(greater<int>(),_1,42));
binder参数传递方式¶
有传值和传引用两种方式:
void incr(int& i)
{
++i;
}
int incr2(const int&i)
{
return i+1;
}
int i = 0;
bind(incr, i)(); // 改变的是i的拷贝,不影响外面的i
bind(incr, ref(i))(); // 会改变变量i的值, reference
auto j = bind(incr2, cref(i))(); // 不会改变变量i的值, const reference
注意这里的incr无法重载,因为绑定的只是一个函数名字。
举例: binder调用全局函数¶
char myToupper(char c){
std::locale loc;
return std::use_facet<std::ctypes<char>>(loc).toupper(c);
}
string s("Internationlization"): //i18n
string sub("Nation");
auto pos = search(s.begin(),s.end(), // binder形式
sub.begin(),sub.end(),
bind(equal_to<char>(),
bind(myToupper,_1),
bind(myToupper,_2)));
auto pos2 = search(s.begin(), s.end(), // lambda形式
sub.begin(), sub.end(),
[](char c1, char c2)
{return mToupper(c1) == myToupper(c2);});
if(pos != s.end()){
cout << "\"" << sub << "\" is part of \"" << s << "\"\n";
}
举例: binder调用成员函数¶
class Person{
private:
string name;
public:
Person(cosnt string& n): name(n){}
void print()const {
cout << name << endl;
}
void print2(const string& prefix) const{
cout << prefix << name << endl;
}
};
vector<Person> coll = {Person("Tick"),Person("Trick"),Person("Track")};
// binder
for_each(coll.begin(),coll.end(),bind(&Person::print, _1));
for_each(coll.begin(),coll.end(),bind(&Person::print2, -1, "Person:"));
bind(&Person::print2,_1,"This is: ")(Person("nico"));
for_each(coll.begin(),coll.end(),std::mem_fn(&Person::print)); // std::mem_fn省去占位符
for_each(coll.begin(),coll.end(),bind(std::mem_fn(&Person::print2),_1,"Person:"));
std::mem_fn(&Person::print)(Person("nico"));
std::mem_fn(&Person::print2)(Person("nico"), "This is: ");
// lambda
for_each(coll.begin(),coll.end(),[](const Person& p){ p.print();});
for_each(coll.begin(),coll.end(),[](const Person& p){ p.print(2,"Person: ");});
举例: binder绑定至数据成员¶
// 累加value值
map<string, int> coll{{"a",1},{"b",3},{"c",5}};
int sum = accumulate(coll.begin(), coll.end(), 0,
bind(plus<int>(),_1,bind(&map<string,int>::value_type::second, _2))); // binder
int sum = accumulate(coll.begin(), coll.end(), 0,
[](const int&a, const auto &x) -> int { return a + x.second; }); // lambda
Binder对比Lambda¶
简单的函数绑定时,lambda会更加直观:
auto plus10 = bind(plus<int>(), _1,10);
auto plus10 = [](int i){ return i+10;};
auto plus10time2 = bind(multiplies<int>(),plus10,2);
auto plus10time2 = [](int i){ return (i+10)*2;};
auto pow3 = bind(multiplies<int>(),bind(multiplies<int>(),_1,_1),_1);
auto pow3 = [](int i){ return i*i*i; };
auto inverDivede = bind(divides<double>(),_2,_1);
auto inverDivede = [](double d1, double d2){ return d2/d1;};
但是带有状态的情况下时,binder更不易出差:
class MeanValue{
private:
long num;
long sum;
public:
MeanValue():num(0),sum(0){}
void operator()(int elem){
++num;
sum += elem;
}
double value(){
return static_cast<double>(sum) / static_cast<double>(num);
}
};
vector<int> coll = {1,2,3,4,5,6,7,8};
MeanValue mv = for_each(coll.begin(), coll.end(), MeanValue());
cout << "Mean Value: " << mv.vlaue() << endl;
long sum = 0;
for_each(coll.begin(), coll.end(), [&sum](int elem){ sum += elem; });
double mv = static_cast<double>(sum) / static_cast<double>(coll.size());
cout << "Mean Value: " << mv << endl;
总结¶
一般情况下,首选lambda,选择lambda会更加直观,需要维护状态时可选择binder。
- 微信搜索: 「 MinYiLife 」, 关注公众号!
- 本文链接: https://www.lesliezhu.com/blog/2022/10/31/cpp_7/
- 版权声明: 原创文章,如需转载请注明文章作者和出处。谢谢!