《C++ Primer》读书笔记第十四章-2-函数调用运算符

来源:互联网 发布:淘宝vip专享 编辑:程序博客网 时间:2024/06/10 13:58

笔记会持续更新,有错误的地方欢迎指正,谢谢!

类包含状态所以比普通函数更灵活,如果类定义了调用运算符(),那么该类的对象叫作函数对象,因为可以调用这种对象,所以,我们说这些对象的行为像函数一样。

带状态的函数对象类

我们要写个打印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;//使用默认值,打印到coutprinter(s); //在cout中打印s,后面跟一空格PrintString errors(cerr, '\n');//使用自己提供的值,打印到cerrerrors(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;} //lambdastruct 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;}; // lambdacout << f1(4, 2); //打印6cout << f2(4, 2); //2cout << 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); //15binops["-"](10, 5); //5binops["*"](10, 5); //50binops["/"](10, 5); //2binops["%"](10, 5); //0
阅读全文
0 0
原创粉丝点击