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就是函数指针),因为有重载的话就不知道调用的是哪个了。

全部评论

相关推荐

2025-12-17 12:08
门头沟学院 产品经理
牛客85811352...:1希音不知道算不算大厂 2完全符合,过得很舒服, 3确实只有杂活 领导找我续签到明年3、4月我要继续吗。主要是边实习边秋招这段时间还是有点累
什么是优秀的实习经历
点赞 评论 收藏
分享
评论
点赞
收藏
分享

创作者周榜

更多
牛客网
牛客网在线编程
牛客网题解
牛客企业服务