如果类重载了函数调用运算符,则可以像使用函数一样使用该类的对象,因为这样的类同时也能存储状态,所以与普通函数相比它们更加灵活。
struct absInt{
int operator()(int val) const{
return val < 0 ? -val : val;
}
};
上面的类只定义了一种操作:函数调用运算符,它负责接受一个int类型的形参,然后返回该实参的绝对值。
int i = -42;
absInt absObj;
int ui = absObj(i); //i被传递给absObj.operator()
即使 absObj
是一个对象而非函数,也能调用该对象,调用对象实际上是在运行重载的调用运算符。
函数调用运算符必须是成员函数,一个类可以定义多个不同版本的调用运算符,相互之间在参数数量和参数类型上应该有所区别。
如果类定义了调用运算符,则该类的对象称作函数对象,因为可以调用这种对象,所以这些对象的行为像函数一样。
含有状态的函数对象类
函数对象除了 operator()
之外也可以包含其他成员。
class PrintString{
public:
PrintString(ostream &o = cout,char c = ' '):os(o),sep(c){ }
void operator()(const string &s)const{os<<s<<sep;}
private:
ostream &os;
char sep;
};
使用:
PrintString printer;
printer(s); //cout中打印s,后面跟一个空格
PrintString errors(cerr,'
');
errors(s); //cerr中打印s,后面跟一个换行符
函数对象通常是作为泛型算法的实参:
for_each(vs.begin(),vs.end(),PrintString(cerr,"
"));
lambda 是函数对象
编写了一个 lambda
后,编译器将该表达式翻译成一个未命名类的未命名对象。
stable_sort(words.begin(),words.end(),[](const string &a,const string &b)
{return a.size() < b.size();});
其行为类似下面这个类的一个未命名对象:
class ShorterString{
public:
bool operator()(const string &a,const string &b)
{return a.size() < b.size();}
};
使用上面的类重写 stable_sort
:
stable_sort(words.begin(),words.end(),ShorterString());
表示 lambda 及相应捕获行为的类
auto wc = find_if(words.begin(),words.end(),[sz](const string &a)
{return a.size() > = sz;})
该 lambda 表达式产生的类将形如:
class SizeComp
{
SizeComp(size_t n):sz(n) { }
bool operator()(const string &s)const {return s.size() >= sz;}
private:
size_t sz;
};
auto wc = find_if(words.begin(),words.end(),SizeComp(sz))
标准库定义的函数对象
标准库定义了一组表示算术运算符、关系运算符和逻辑运算符的类,每个类分别定义了一个执行命名操作的调用运算符。
plus<int> intAdd; //可执行int加法的函数对
negate<int> intNegate; //可对int值取反的函数对象
int sum = intAdd(10,20); //sum=30
sum = intAdd(10,intNegate(10)); //sum=0
定义在 functional
头文件中的函数对象:
在算法中使用标准库函数对象
// 传入一个临时函数对象用于执行两个 string 对象的比较运算
sort(svec.begin(),svec.end(),greater<string>());
标准库规定其函数对象对于指针同样适用:
vector<string *> nameTable; //指针的vector
//错误:nameTable 中的指针彼此之间没有任何关系,所以 < 将产生未定义的行为
sort(nameTable.begin(),nameTable.end(),[](string *a,string *b){return a < b;}) ;
//正确,标准库规定指针的 less 定义是良好的
sort(nameTable.begin(),nameTable.end(),less<string*>());
可调用对象与 function
C++ 中的可调用对象包括:函数、函数指针、lambda
表达式、bind
创建的对象以及重载了函数调用运算符的类。
可调用对象也有类型,labmda 有自己唯一的未命名类型,函数及函数指针的类型由其返回值类型和实参类型决定。两个不同类型的可调用对象却可能共享同一种调用形式,调用形式指明了返回类型以及传递给调用的实参类型,一种调用形式对应一个函数类型:
int (int,int) //是一个函数类型,它接受两个int,返回一个int
不同的类型可能具有相同类型的调用方式
int add(int i,int j){return i + j;}
auto mod = [](int i,int j){return i % j;}
struct divide{
int operator()(int denminator,int divisor){
return denminator / divisor;
}
};
尽管这些可调用对象对其参数执行了不同的算术运算,尽管它们的类型各不相同,但是共享一种调用形式:
int (int,int)
构建实现不同运算的函数表:
map<string,int(*)(int,int)> binops;
binops.insert({"+",add});
binops.insert({"%",mod}); //错误,mod不是函数指针
标准库 function 类型
function
定义在 functional
头文件中:
function
是一个模板,创建具体的 function
类型时需要提供额外的信息。
function<int(int,int)>
function<int(int,int)> f1 = add; //函数指针
function<int(int,int)> f2 = divide; //函数对象类的对象
function<int(int,int)> f3 = [](int i,int j){return i * j;} //lambda
//调用
cout<<f1(4,2)<<endl;
cout<<f2(4,2)<<endl;
cout<<f3(4,2)<<endl;
使用 function
重新定义上面的函数表:
map<string,function<int(int,int)>> binops =
{
{"+",add}, //函数指针
{"-",std::minus<int>()}, //标准库函数对象
{"/",divide}, //自定义函数对象
{"*",[](int i,int j){return i * j;}}, //未命名lambda表达式
{"%",mod}, //命名lambda表达式
};
调用:
binops["+"](10,5);
binops["-"](10,5);
binops["/"](10,5);
binops["*"](10,5);
binops["%"](10,5);
重载的函数与 function
不能直接将重载函数的名字存入 function 类型的对象中。
int add(int i,int j){return i + j;}
Sales_data add(const Sales_data&,const Sales_data&);
map<string,funciton<int(int,int)>> binops;
binops.insert({"+",add}); //错误,不能区分是哪个add
解决上面问题的有效途径是存储函数指针而非函数名字:
int (*fp) (int,int) = add;
binops.insert("add",fp); //正确,fp指向正确的add版本
同样,也可以使用 lambda 来指定希望使用的 add 版本:
binops.insert({"+"},[](int a,in b){return add(a,b);});