如果类重载了函数调用运算符,则可以像使用函数一样使用该类的对象,因为这样的类同时也能存储状态,所以与普通函数相比它们更加灵活。
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, '\n');
errors(s); // cerr 中打印 s,后面跟一个换行符
函数对象通常是作为泛型算法的实参:
for_each(vs.begin(), vs.end(), PrintString(cerr, "\n"));
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
头文件中的函数对象:
function 的操作 | 说明 |
---|---|
function<T> f; | f 是一个用来存储可调用对象的空 function,这些可调用对象的调用形式应该与函数类型 T 相同(即 T 是 retType(args)) |
function<T> f(nullptr); | 显式地构造一个空 function |
function<T> f(obj); | 在 f 中存储可调用对象 obj 的副本 |
f | 将 f 作为条件:当 f 含有一个可调用对象时为真;否则为假 |
f(args) | 调用 f 中的对象,参数是 args |
定义为 function<T> 的成员的类型 | 说明 |
---|---|
result_type | 该 function 类型的可调用对象返回的类型 |
argument_type first_argument_type second_argument_type |
当 T 有一个或两个实参时定义的类型。如果 T 只有一个实参,则 argument_type 是该类型的同义词;如果 T 有两个实参,则 first_argument_type 和 second_argument_type 分别代表两个实参的类型 |
在算法中使用标准库函数对象
// 传入一个临时函数对象用于执行两个 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)
构建实现不同运算的函数表:
divide div;
map<string,int(*)(int,int)> binops;
binops.insert({"+",add});
binops.insert({"/", div}); //错误,div 不是函数指针
标准库 function 类型
function
定义在 functional
头文件中。 function
是一个模板,创建具体的 function
类型时需要提供额外的信息。
function<int(int,int)>
divide div;
std::function<int(int,int)> f1 = add; // 函数指针
std::function<int(int,int)> f2 = div; // 函数对象类的对象
std::function<int(int,int)> f3 = [](int i,int j){return i * j;}; // lambda
std::function<int(int,int)> f4 = mod; // lambda
//调用
std::cout<<f1(7,3)<< std::endl;
std::cout<<f2(7,3)<< std::endl;
std::cout<<f3(7,3)<< std::endl;
std::cout<<f4(7,3)<< std::endl;
使用 function
重新定义上面的函数表:
divide div;
std::map<std::string, std::function<int(int,int)>> binops =
{
{"+", add}, //函数指针
{"-", std::minus<int>()}, // 标准库函数对象
{"/", div}, // 自定义函数对象
{"*", [](int i,int j){return i * j;}}, // 未命名 lambda 表达式
{"%", mod}, // 命名 lambda 表达式
};
调用:
std::cout << binops["+"](19,5) << std::endl;
std::cout << binops["-"](19,5) << std::endl;
std::cout << binops["/"](19,5) << std::endl;
std::cout << binops["*"](19,5) << std::endl;
std::cout << binops["%"](19,5) << std::endl;
重载的函数与 function
不能直接将重载函数的名字存入 function
类型的对象中。
struct Sales_data;
int add(int i,int j){return i + j;}
Sales_data add(const Sales_data& lhd, const Sales_data& rhd);
int main() {
std::map<std::string, std::function<int(int,int)>> binops;
binops.insert({"+", add}); // 错误,不能区分是哪个 add
return 0;
}
解决上面问题的有效途径是存储函数指针而非函数名字:
int (*fp) (int, int) = add;
binops.insert({"+", fp}); // 正确,fp 指向正确的 add 版本
同样,也可以使用 lambda
来指定希望使用的 add
版本:
binops.insert({"+", [](int a, int b){ return add(a,b);} });
注意:本文非原创,内容摘录自《函数调用运算符》,感谢原作者的付出和无私分享。
网友评论