14.8 函数调用运算符
函数调用运算符必须是成员函数
struct absInt {
int operator()(int val) const {
return val < 0 ? -val : val;
}
};
int i = -42;
absInt absObj;
int ui = absObj(i); // 42
absInt
对象作用于一个实参列表,看起来像调用函数的过程
调用对象实际上是在运行重载的调用运算符
如果类定义了调用运算符,则该类的对象称作函数对象
含有状态的函数对象类
class PrintString
{
private:
ostream &os;
char sep;
public:
PrintString(ostream& o = cout, char c = ' '): os(o), sep(c) {}
void operator()(const string& s) const { os << s << sep; }
};
PrintString printer;
string s = "hello";
printer(s); // 调用重载函数运算符
函数对象常常作为泛型算法的实参
vector<string> vs = {"kavin", "jack", "lisa"};
for_each(vs.begin(), vs.end(), PrintString(cout, '#'));
// kavin#jack#lisa#
14.8.1 lambda是函数对象
编译器会将lambda
表达式翻译成一个未命名类的未命名对象
例如字符串排序的lambda
表达式
vector<string> words = {"kavin", "jack", "lisa"};
stable_sort(words.begin(), words.end(),
[](const string& a, const string& b)
{ return a.size() < b.size(); });
// jack
// lisa
// kavin
其行为类似于下面这个类的一个未命名对象
class ShorterString {
public:
bool operator()(const string& s1, const string& s2) const {
return s1.size() < s2.size();
}
};
默认情况下,lambda
不能改变它捕获的对象,因此函数调用符是一个const
成员函数,如果lambda
被声明为可变的,则调用运算符就不是const
的了
然后就可以用这个类替代lambda
表达式,效果是一样的
stable_sort(words.begin(), words.end(), ShorterString());
如果是通过值捕获对象,那产生的类必须为每个值捕获的变量建立对应的数据成员,同时创建构造函数,令其使用捕获的变量的值来初始化数据成员
vector<string> words = {"joe", "jack", "lisa", "kavin"};
int sz = 5;
auto wc = find_if(words.begin(), words.end(),
[sz](const string& a)
{ return a.size() >= sz; });
cout << *wc << endl;
// kavin
生成的类将类似于
class SizeComp {
public:
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));
// kavin 效果是一样的
14.8.2 函数调用运算符
如果要让字符串从大到小排序
vector<string> words = {"jack", "lisa", "kavin"};
sort(svec.begin(), svec.end(), greater<string>());
// lisa
// kavin
// jack
指针也可以用
string s1 = "kavin";
string s2 = "jack";
string s3 = "lisa";
vector<string*> nameTable = {&s1, &s2, &s3};
sort(nameTable.begin(), nameTable.end(), greater<string*>());
for (auto& n : nameTable) cout << *n << endl;
// lisa
// jack
// kavin
14.8.3 可调用对象与function
有几种可调用的对象
- 函数
- 函数指针
- lambda表达式
- bind创建的对象
- 重载了函数调用运算符的类
两种不同类型的可调用对象却可以共享同一种调用形式(call signature)
例如int(int, int)
是一个函数类型,接受两个int
,返回一个int
对于几个可调用对象共享同一种调用形式的情况,会希望把他们看成具有相同的类型
// 普通函数
int add(int i, int j) { return i + j;}
// lambda,其产生一个未命名的函数对象类
auto mod = [](int i, int j) { return i % j; };
// 函数对象类
struct divide {
int operator()(int denominator, int devisor) {
return denominator / devisor;
}
};
利用这些可调用对象构建一个简单的桌面计算器,需要定义一个函数表用于存储指向这些可调用对象的“指针”,但是直接添加会有问题
map<string, int(*)(int, int)> binops;
binops.insert({"+", add}); // yes, add是一个指向函数的指针
binops.insert({"%", mod}); // err, mod不是一个函数指针,是一个对象
需要考虑如何消除这种错误
标准库function类型
function<T> f; // f是一个用来存储可调用对象的空function
声明一个function
类型,可以表示接受两个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; };
cout << f1(4, 2) << endl;
cout << f2(4, 2) << endl;
cout << f3(4, 2) << endl;
// 6
// 2
// 0
这样就可以直接调用,不用管是函数指针,还是函数对象
map<string, function<int(int, int)>> binops = {
{"+", add}, // 函数指针
{"-", minus<int>()}, // 标准库函数对象
{"/", divide()}, // 用户定义的函数对象
{"*", [](int i, int j){return i * j;}}, // 未命名的lambda
{"%", mod} // 命名的lambda
};
cout << binops["+"](10, 5) << endl; // 15
cout << binops["-"](10, 5) << endl; // 5
cout << binops["/"](10, 5) << endl; // 2
cout << binops["*"](10, 5) << endl; // 50
cout << binops["%"](10, 5) << endl; // 0
重载的函数与function
不能(直接)将重载函数的名字存入function
类型的对象中
int add(int i, int j) { return i + j; }
Sales_data add(const Sales_data&, const Sales_data&);
binops.insert({"+", add}); // err, 哪个add
解决二义性问题的一个途径是存储函数指针,换一个不重复的名字
int (*fp)(int, int) = add;
binops.insert({"+", fp});
也可以使用lambda
来消除二义性
binops.insert({"+", [](int a, int b) {return add(a, b);}});