笔记会持续更新,有错误的地方欢迎指正,谢谢!
类包含状态所以比普通函数更灵活,如果类定义了调用运算符(),那么该类的对象叫作函数对象,因为可以调用这种对象,所以,我们说这些对象的行为像函数一样。
带状态的函数对象类
我们要写个打印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; //分隔字符
};
上面这个类不难看懂吧,虽然涉及蛮多知识的,但是我们都介绍过了,下面我们来调用试试:
当定义PrintString对象时,对于分隔符及输出流,既可使用默认值也可提供自己的值。
string s = "hehe";
PrintString printer;//使用默认值,打印到cout
printer(s); //在cout中打印s,后面跟一空格
PrintString errors(cerr, '\n');//使用自己提供的值,打印到cerr
errors(s); //在cerr中打印s,并且后面默认跟的空格,变成了换行
函数对象PrintString(cerr, '\n')
还可以作为泛型算法的实参:
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:
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;
};
替换后:
auto wc = find_if(words.begin(), words.end(),SizeComp(sz));
标准库定义的函数对象
标准库函数对象:一组表示算术运算符、关系运算符、逻辑运算符的类。每个类分别定义了执行命名操作的调用运算符;都是模版类。要用的时候再查好了,基本都定义在头文件functional中。
举个例子:
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,第二个参数是函数指针,而不是(int, int)
binops.insert({"+", add}); //然而其他的不是函数类型,放不进map里
于是我们要有新东西来解决这个问题了
标准库function类型
直接抛代码,简单来说就是把function作为一个模板,这定义在functional头文件中:
unction<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