新C++(15):可调用对象及包装

新C++(15):可调用对象及包装_第1张图片

"彼岸岸上寻找,而天空爱上了飞鸟。"

void (*signal(int signum,void(*handler)(int))))(int);

上述给你一段这样的代码,你能知道这是什么嘛?唔...,似乎有点打脑壳。这是什么鬼??如果你把void后面的一些进行省略成这个的形式: void (*)(int),也许你会脱口而出,这是函数指针!

是的,这是一个经典的信号处理函数。那本章是为了来讲C语言那天马行空般恼人的函数指针??肯定不是,这个任务得去交给学习C语言指针时该去做的。函数指针的本质,就是函数入口的地址,但显然这对我们尤其是初学者特别不友好,这层层嵌套,何时才能嵌套得完?

因此,C++提供了其他的方式来替代传统式函数指针的应用。

---前言

一、什么是可调用对象

可调用对象除开传统的函数指针,C++还提供了其他两种调用对象。

①仿函数
②lambda表达式


二、仿函数

如果我们需要一个函数,实现两个数的加法。

//传统写法 无非就是定义函数
int AddFunc(int x, int y)
{
    return x + y;
}

但是呢,C++引入了仿函数的语法之后,与普通函数相比较,仿函数的使用更加便利。

所谓仿函数,就是让一个类,像函数一样去使用
struct AddObj
{
    int operator()(int x, int y)
    {
        return x + y;
    }    
};
新C++(15):可调用对象及包装_第2张图片

也许你会说,啊你这仿函数用起来,也不比普通函数调用简单多少,还不如原来普通函数调用呢。是的,对于上述这中情况,普通函数调用和仿函数似乎都很简单。

新C++(15):可调用对象及包装_第3张图片
struct Func2
{
    int operator()(int base, int n)
    {
        for (int i = 1;i <= n;++i)
        {
            base += i;
        }
        return base;
    }
};

template
class Num
{
public:
    int accmulate(int n)
    {
        return Func()(_base, n);
    }

private:
    int _base = 0;
};

显然,当面对这种情况,仿函数可以作为参数传递给一个类的模板作参数,仅凭这一点,仿函数的实用性其实就比普通函数广太多了。

新C++(15):可调用对象及包装_第4张图片


三、lambda表达式

C++11等于说"抄了别人语法的作业",但确实lambda表达式的出现是非常成功便捷的。

(1)lambda表达式语法

[capture-list](parameters)mutable->return-type{ statement };

[cpature-list] : 捕捉列表。编译器会根据[]来判断是否是lambda表达式,能够捕获该代码段内的上下文变量供lambda函数体使用。
(parameters): 参数列表如果不需要参数传递,则可以连同()一起省略。

->returntype: 返回值类型(可以省略不写,由编译器对返回类型进行推导)。

mutable :默认情况下,lambda函数总是一个const函数,mutable可以取消其常量性。使用该修饰符时,参数列表不可省略(即使参数为空)。

{statement}: 函数体。该函数体可以使用其参数、以及捕获列表中捕获的对象。

(2)lambda表达式的应用

lambda表达式的语法稍微过了一遍后,我们来看看它的应用吧。

    //1.最简单的lambda表达式
    //参数没有可以不用写 、 返回值也可以让编译器 推导
    [] {};

    //2.捕获上下文变量
    int a = 3, b = 4;
    auto f = [=] { return a + b;};
    cout << f() << endl;
新C++(15):可调用对象及包装_第5张图片

使用=、&捕捉:

我们现在想使用一个lambda表达式 交换两个数的值。

    auto f1 = [](int& a, int& b)
    {
        int tmp = a;
        a = b;
        b = tmp;
    };

    //引用或者叫取地址捕捉
    auto f2 = [&]
    {
        int tmp = a;
        a = b;
        b = tmp;
    };
新C++(15):可调用对象及包装_第6张图片

这样我们就能通过写lambda表达式,写一个简易的swap函数。

四、包装器与绑定

如果我们看到这个代码, ret = Func(x)。

这个Func是什么??emm它可能是一个函数调用,可能是一个仿函数,也可能是一个lambda表达式。因为这些都是可调用对象。

我们再来看看下面的一份代码:

template
T useF(F f, T x)
{
    static int count = 0;
    cout << "count:" << ++count << endl;
    cout << "count:" << &count << endl;
    return f(x);
}

double f(double i)
{
    return i / 2;
}

struct Functor
{
    double operator()(double d)
    {
        return d / 3;
    }
};
新C++(15):可调用对象及包装_第7张图片

我们用三个不同的可调用对象,函数名、仿函数、lambda仿函数作为函数参数模板传入类useF中。

新C++(15):可调用对象及包装_第8张图片

他们的功能如出一辙,但是唯一不同的是,我们在useF类中定义的静态成员变量Count是存在多份的。也就意味着,在useF实例化对象时,实际上是实例化出3份功能相同的函数的。

但是如果我们只想让这个类实例化出一份函数呢?这时候就需要用到我们的包装器。

(1)function

function是一个适配器,其底层就是一个类模板。

新C++(15):可调用对象及包装_第9张图片

其类原型如下:

#inclued 

template 
class function
{}
Ret:可调用对象的返回值类型
Args:这里是一个可变参数 可调用对象参数列表

function的应用:

int f(int a, int b)
{
    return a + b;
}

struct Functor
{
public:
    int operator() (int a, int b)
    {
        return a + b;
    }
};

我们完成对这些函数的包装器。

新C++(15):可调用对象及包装_第10张图片

对类函数包装:

包装器对类成员的包装也常用。不过相对于可调用对象而言,它传入的参数会多一个。

class Plus
{
public:
    static int plusi(int a, int b)
    {
        return a + b;
    }
    double plusd(double a, double b)
    {
        return a + b;
    }
}
新C++(15):可调用对象及包装_第11张图片
值得注意的是,若要访问类成员函数。
需要在function参数加 该类类型。 并且在使用时需要进行传参。

对于静态函数而言,可以不需要"&",但是对于成员函数必须要"&"。因此,最好的写法是,都取"地址"。

因此,有了function包装器,我们可以尝试着对那个我们之前面对的问题(解决模板的效率低下,实例化多份)做出一定的回应。

新C++(15):可调用对象及包装_第12张图片

此时,当函数实例化时,函数内的count只有一个。也就是说只实例化出了一个函数。

包装器的其他应用场景

包装器还有其他应用的场景,并使用起来后,更加简洁、便于维护。下面是一个leetcode题。

新C++(15):可调用对象及包装_第13张图片

进行更改后,可以变成这样。

新C++(15):可调用对象及包装_第14张图片

(2)bind

std::bind函数定义在头文件中, 是一个函数模板,它就像一个函数包装器(适配器)接受一个可
调用对象(callable object), 生成一个新的可调用对象来“适应”原对象的参数列表
新C++(15):可调用对象及包装_第15张图片

其原型如下:

template 
/* unspecified */ bind (Fn&& fn, Args&&... args);
// with return type (2)
template 
/* unspecified */ bind (Fn&& fn, Args&&... args);

这个函数模板,时常用来固定下来一些参数,也可以实现传参参数的顺序调整。

placeholders:

新C++(15):可调用对象及包装_第16张图片

这是bind的参数,是一个命名空间。

bind函数应用:

如果我们需要调用一个普通函数,需要这样的用法。

新C++(15):可调用对象及包装_第17张图片

如果我们需要调用一个成员函数,需要这样的用法。

新C++(15):可调用对象及包装_第18张图片


总结

那么以上也就是本篇的所有内容,"C++11的语法让C++不再是C++",这句话现如今让我深有体会。当然,这些内容有一些是十分好用的,比如说lambda表示,function在一些场景时使用,也是十分"香的"。然而,深入学习一门语言本身就是这样得繁琐,乏味。也望诸君的努力不会白费。

本篇到此结束,感谢你的阅读。

祝你好运,向阳而生~

你可能感兴趣的:(新C++,c++,开发语言)