c++—函数式编程(lambda、函数对象、函数包装器、函数适配器)

1. lambda表达式

    (1)本质:实际是匿名函数,能够捕获一定范围的变量,与普通函数不同,可以在函数内部定义;

    (2)作用:①简化程序结构,因为优化了函数命名与函数传参;②提高程序运行效率,因为优化了函数调用、函数返回等消耗; 适用于简单功能的函数优化;

    (3)捕获总结与示例:

        ① = :按值捕获,只可用不可改

        ② & :按地址捕获,即可用又可改

        ③ 变量名:按值捕获,可用不可改

        ④ &变量名:引用捕获,可用可改

        ⑤ 副本捕获:c++14后可以自定义变量名 = 捕获变量,但是无法通过副本名改变变量名,示例如下:

#include <iostream>

using namespace std;

int main(int argc, char **argv)
{
    int num1 = 5;
    int num2 = 6;

    auto func_add = [&num1,num2]()  //num1可修改,num2不可修改
    {
        num1 = 7;
        return num1 + num2;
    };

    auto func_add1 = [=](int a, int b)
    {
        a = 1;  //这里修改的只是形参a/b的值,不会改变num1与num2的值
        b = 1;
        return a + b;
    };

    auto func_add2 = [&]
    {
        // num1 = 1;  //按引用传递,可用可修改num1与num2的值
        return num1 + num2;
    };

    cout<<func_add()<<endl;
    cout<<"num1 = "<<num1<<" num2 = "<<num2<<endl;
    cout<<func_add1(num1,num2)<<endl;
    cout<<"num1 = "<<num1<<" num2 = "<<num2<<endl;
    cout<<func_add2()<<endl;
    cout<<"num1 = "<<num1<<" num2 = "<<num2<<endl;

    return 0;
}

    (4)注意:lambda不可以包含static修饰的变量及全局变量;且避免复杂的lambda表达式;

    (5)内部机理:c++14编译器优化处理,为每个lambda表达式生成对应的类,且将捕获成员私有化;

    (6)引用悬挂是延迟调用造成的,相关变量已经提前释放,解决的办法是使用副本捕获;

2. 函数对象

    (1)概述:凡是一个类重载了函数运算符(),那么该类的对象就是函数对象,以对象处理函数调用,符合c++面向对象的思维:一切皆为对象;就是将函数对象化;

        既可以自定义函数对象模板,也可以利用系统内置的函数对象,如下例的plus();

#include <iostream>
#include <string>
#include <functional>

using namespace std;

template <typename T>
class Add
{
public:
    Add() = default;
    void operator()(T &&a, T &&b)  //重载函数运算符,采用的是万能引用
    {
        cout<<a+b<<endl;
    }
};

int main(int argc ,char **argv)
{
    Add<int> c_add;
    c_add.operator()(5,6);  //利用成员函数的形式调用
    c_add(5,6);    //采用函数成员方式

    plus<int> p1;
    cout<<p1(5,6)<<endl;  //使用系统函数对象库

    return 0;
}

    (2)函数对象(function object)与普通函数的区别

        ①函数对象比一般函数更灵活,因为它可以拥有状态(state),事实上,对于相同的函数对象可以设置两个状态不同的实例;普通函数没有状态;

        ②每个函数对象都有其类型,因为你可以将函数对象的类型当做template参数传递,从而指定某种行为;另外容器类型也会因为函数对象的不同而不同;

        ③执行速度上,函数对象通常比函数指针更快;

3. 函数包装器

    (1)本质与作用

        ①统一所有的函数调用形式,全部面向对象

        ②本质是一个类模板,用于包装可调用对象,可以容纳除了类成员之外(函数)指针之外的所有可调用形式;

        ③头文件:#include<functional>; 包装器程序示例:

#include <iostream>
#include <string>
#include <functional>

using namespace std;

template <typename T>
class Add
{
public:
    Add() = default;
    void operator()(T &&a, T &&b)
    {
        cout<<a+b<<endl;
    }
};

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

int main(int argc ,char **argv)
{
    Add<int> c_add;

    auto f_add = [](int a, int b)  //lambda表达式
    {
        return a + b;
    };

    //函数包装器,统一所有的函数调用形式,全部面向对象;
    function<int(int, int)>func1(add);  //对普通函数包装
    cout<<"add : "<<func1(5,6)<<endl;

    function<int(int, int)>func2(f_add);  //对lambda表达式包装
    cout<<"lambda : "<<func2(5,6)<<endl;

    function<void(int, int)>func3(c_add);  //对函数对象包装
    cout<<"functor : ";
    func3(5,6);

    return 0;
}

4. bind 函数适配器

    (1)主要用在函数已经存在,但是现有参数较多,减少实际所需参数个数的一种方法;

    (2)本质,bind也是一个函数模板,返回值是一个仿函数,是可调用对象;

    (3)bind可以绑定的对象:①普通函数;②lambda表达式;③函数对象;④类的成员函数;⑤类的数据成员;

示例:

#include <iostream>
#include <functional>
using namespace std;

template <typename T>
class Add
{
public:
    T operator()(T a, T b, T c)
    {
        print();
        return a + b;
    }

    void operator()(const T &a)
    {
        cout << a << endl;
    }

    void print()
    {
        cout << "function add!" << endl;
    }

    int m_result;
};

int add(int a, int b, int c)
{
    cout << "a = " << a << " b = " << b << endl;
    return a + b + c;
}

int main()
{
    //普通函数
    function<int(int,int)> my_add = std::bind(add,std::placeholders::_1,std::placeholders::_2,0);
    cout << my_add(5,6) << endl;
    function<int()> my_add2 = std::bind(add,7,8,0);
    cout << my_add2() << endl;

    //lambda表达式
    auto lambda_func = [=](int a, int b, int c)
    {
        return a + b + c;
    };
    function<int(int,int)> my_add3 = std::bind(lambda_func,std::placeholders::_2,std::placeholders::_1,0);
    cout << my_add3(3,4) << endl;
    function<int()> my_add4 = std::bind(lambda_func,3,4,0);
    cout << my_add4() << endl;

    //函数对象
    Add<int> c_add;
    function<int(int,int)> my_add5 = std::bind(c_add,std::placeholders::_2,std::placeholders::_1,0);
    cout << my_add5(4,5) << endl;
    function<int()> my_add6 = std::bind(c_add,5,6,0);
    cout << my_add6() << endl;

    return 0;
}

5. 关键字constexpr

    (1)引入的原因:因为const不能确保其修饰的变量、函数能够在编译器优化,从而引入constexpr确保被其修饰的变量或者函数能够在编译器优化;从c++11开始引入;

    (2)主要的优点:让函数执行提前到编译期,减少函数调用返回的消耗;

    (3)注意点

        ①修饰变量时:变量必须是常量、变量必须立即被初始化、变量的类型不可以是类类型;

        ②修饰表达式时:表达式的值不会改变且在编译期就可求值的表达式;

        ③修饰指针时:仅对指针有效,与指向的对象无关,即指针的指向不可发生变化;

        ④constexpr修饰的函数中不可以出现循环体;且函数必须不能是虚函数(基类中被virtual修饰的成员函数);函数体中不可以出现try和goto语句;constex只可以调用其他conste函数,不可以调用非conste函数;

猜你喜欢

转载自blog.csdn.net/m0_72814368/article/details/130913835