C++ Primer第十四章②
C++ Primer
重载运算与类类型转换
函数调用运算符
我们来做个很有意思的事情:
struct absInt
{
int operator()(int val) const
{
return val < 0 ? -val : val;
}
};
我们定义了一个类,里面重载了一个运算符(),用来返回绝对值,我们来调用试试:
int i = -24;
absInt a;
int ui = a(i); //调用了operator()
看着像不像我们调用了一个函数
如果类定义了调用运算符(),那么该类的对象叫作函数对象,因为可以调用这种对象,所以我们说这些对象的行为像函数一样。
我们来个复杂点的:
带状态的函数对象类
我们要写个打印string的类,默认情况下,我们的类会把内容写入cout中,每个string用空格隔开,也允许用户提供其他可写入的流及分隔符:
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; //分隔字符
};
上面这个类不难看懂吧,虽然涉及蛮多知识的,但是我们都介绍过了,下面我们来调用试试:
string s = "hehe";
PrintString printer;
printer(s); //打印hehe,后面跟一空格
PrinterString errors(cerr, '\n');
errors(s); //在cerr中打印s,后面跟换行
函数对象还可以作为泛型算法的实参:
for_each(vs.begin(), vs.end(), PrintString(cerr, '\n'));
第三个参数是PrintString的一个临时对象(看着是不是很像lambda啊,不过它没有捕获什么)
lambda与函数对象的关系
我们来回忆下之前的lambda表达式,根据单词的长度进行排序,同样长度的根据字典序排列:
stable_sort(words.begin(), words.end(),
[](const string &a, const string &b {return a.size() < b.size();})
那么归根结底,编译器是怎么处理神奇的lambda的呢?其实直接把lambda当成一个未命名类的未命名对象(这个类的对象还是个函数对象)
所以,我们可以用一个函数对象去替代上面的lambda(可以回过头想想lambda关于返回类型的规定,与这无关,就是插一句,因为我忘了。。。回过头去看的):
class ShorterString
{
public:
bool operator()(const string &s1, const string &s2) const
{
return s1.size()<s2.size();
}
};
有了这个类后,我们就可以这样写:
stable_sort(words.begin(), words.end(), ShorterString())
第三个参数是新构建的ShorterString临时对象。
刚刚我们是用函数对象去替代了一个没有捕获的lambda,接下来我们要来个有捕获列表的lambda,用类去替换:
//获得第一个size大于给定sz的元素的迭代器
auto wc = find_if(words.begin(), words.end(),
[sz](const string &a){return a.size() >= sz;}
那我们相应的类是这样:
class SizeComp
{
public:
SizeComp(size_t n) : sz(n){} //要写个构造函数,用来“捕获变量”
bool operator()(const string &s) const {return s.size()>=sz;}
private:
size_t sz;
};
标准库定义的一些函数对象
我在这就用代码举些例子,要用的时候再查好了,基本都定义在头文件functional中:
plus<int> intAdd;
int sum = intAdd(10, 20); //sum = 30
vector<string> svec = {"f", "z"};
sort(svec.begin(), svec.end(), greater<string>()); //降序排列第三个参数是未命名的函数对象
可调用对象与function
我们来搞点事情:
//四则运算
int add(int i, int j){return i + j;} //普通函数
auto mod = [](int i, int j){return i % j;} //lambda
struct divide //函数对象类
{
int operator()(int de, int di){return de / di;}
};
有没有发现,调用以上这些东西,都是同一种形式:
int(int, int)
所以啊,我们想定义一个函数表,从表中查找要调用的函数,在C++里面,我们可以用map,但是啊,又不行,为啥呢:
map<string, int(*)(int, int)> binops; //一个string一个函数指针
binops.insert({"+", add});
//然而其他的不是函数类型,放不进map里
于是我们要有新东西来解决这个问题了
标准库function类型
直接抛代码把,简单来说就是把function作为一个模板,这定义在functional头文件中:
function<int(int, int)> //我们声明了一个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;}; // lambda
cout << f1(4, 2); //打印6
cout << f2(4, 2); //2
cout << f3(4, 2); //8
有了这个神器,我们就可以建立函数表了:
map<string, function<int(int, int)>> binops;
//可以理解为后一个参数指定了函数类型(函数名正好不在函数类型里面)
我们来重新初始化一下,给它点内容:
map<string, function<int(int, int)>> binops =
{
//四则运算
{“+”, add},
{"-", atd::minus<int>()}, //标准库函数对象
{"*", [](int i, int j){return i*j;}}, //未命名的lambda
{"/", divide()},
{"%", mod}
};
有了这个函数表,我们就可以很方便很直观地调用了:
binops["+"](10, 5); //15
binops["-"](10, 5); //5
binops["*"](10, 5); //50
binops["/"](10, 5); //2
binops["%"](10, 5); //0
我们最好不要把函数名直接放到函数表中(你看我们的add就是函数指针),因为有重载的话就不知道调用的是哪个了。
查看11道真题和解析