C/C++编程:function

1059 篇文章 275 订阅

引入

// STL
<functional>


// boost
#include <boost/function.hpp>
using namespace boost;

是什么

  • function是一个函数对象的“容器”,只要符合它声明中的函数类型,任何普通函数、成员函数、函数对象都可以存储在function对象中,然后在任何时候被调用
    • 如果function存储的是函数对象,那么要求函数对象必须实现operator==,是可以比较的
    • function可以配合bind/lambda使用,存储bind/lambda表达式的结果,使得bind/lambda能够被多次调用
  • 因此它可以被用于回调机制,暂时保管函数或者函数对象,在之后需要的时机调用,使得回调机制拥有更多的弹性
  • 与原始的函数指针相比,function对象的体积要大一点,速度要慢一点,但是它带来的好处更大。

类摘要

boost

boost::function也不是一个单独的类,而是一个大的类家族。它可以容纳0到10个参数的函数,所以也就有多个类,命名分别是function0到function10,但是我们通常不直接使用它们,而是用一个更通用的function类,它的类摘要如下:

在这里插入图片描述
在这里插入图片描述

stl

std::function的类摘要如下:
在这里插入图片描述
std::function与boost::function基本相同,区别在于:

  • 没有clear()和empty()成员函数
  • 没有assign()成员函数
  • explicit显示bool转型

所以,同shared_ptr一样,在函数返回值或者函数参数等语境里转型bool需要使用static_cast< bool >(f)或者!!f的形式

声明形式

  • function只需要一个模板参数,这个参数就是将要容纳的函数类型。比如:
// 将声明一个可以容纳返回值为int,无参函数的function对象
function<int()> func;

// 将声明一个可以容纳返回值为int,两个int类型参数的函数的function对象
function<int (int a, int b)> func2;
function<int (int, int)> func3;
  • 如果我们已经知道了将要容纳的函数,那么也可以用关键字decltype来直接获取函数类型。比如:
int f(int a, int b) {....};
function<decltype(f)> func;

操作函数

function的构造函数:

  • 它可以接受任意符合模板中声明的函数类型的可调用对象,比如函数指针和函数对象,或者是另一个function对象的引用,之后在内部存储一份它的拷贝
  • 无参构造函数或者传入空指针构造将创建一个空function对象,不持有任何可调用物,调用空的function对象将抛出bad_function_call异常,因此在使用function之前最好检测一下它的有效:
    • 可以用empty()测试function是否为空
    • 或者用重载操作符operator!来测试
    • 也可以在一个bool语境中直接测试它是否为空
#include <iostream>
#include <functional>
 
int main()
{
    std::function<int()> f = nullptr;
    try {
        f();
    } catch(const std::bad_function_call& e) {
        std::cout << e.what() << '\n';
    }
}

在这里插入图片描述
function的其他成员函数功能如下:

  • clear()可以直接将function对象置空,相当于直接赋值0
  • 模板成员函数target()可以返回function对象内部持有的可调用物Functor的指针,如果function为空则返回空指针nullptr
  • contains()可以检测function是否持有一个Functor对象
  • function提供了operator(),它把传入的参数转交给内部保存的可调用物,完成真正的函数调用

function还重载了比较操作符operator==和operator!=,可以与被包装的函数或者函数对象进行比较。如果function存储的是函数指针,那么比较相当于:function.target< Functor>() == func_pointer

比如:

function<int (int, int) > func(f);
assert(func == f);

两个function对象不能使用==或者!=直接比较,这是特意的。因为function存在bool的隐式转换,function定义了两个function对象的operator==但没有实现,企图比较两个function对象会导致编译错误

使用

基本用法

在这里插入图片描述

#include <iostream>
#include <functional>

void go()
{
    printf("%s", "go");
}

int  main()
{
    //std::function<返回值(参数)>函数指针 = 指向函数
    std::function <void(void)>fun1 = go;
    fun1();
}

在这里插入图片描述

#include <iostream>
#include <functional>

using namespace std;

void print_num(int i)
{
    printf("%d\n", i);
}
int main() {
    // 存储自由函数
    std::function<void(int)> f_display = print_num;
    f_display(-9);

    // 存储 lambda
    std::function<void()> f_display_42 = []() { print_num(42); };
    f_display_42();

    // 存储到 std::bind 调用的结果
    std::function<void()> f_display_31337 = std::bind(print_num, 31337);
    f_display_31337();
    return 0;
}

在这里插入图片描述

#include <iostream>
#include <functional>
int  main()
{
    std::function <void(void)>fun2 = []() { printf("%s", "golamba" ); };
    fun2();
}

在这里插入图片描述

#include <iostream>
#include <functional>

int add(int a, int b)
{
    return a + b;
}
using namespace std;
int  main()
{
    function <int(int, int)>fun3 = add;  //20
    cout << fun3(10, 10) << endl;

    function <int(int, int)>fun4 = [](int a, int b)->int {return a + b; };
    cout << fun4(10, 10) << endl;
}

在这里插入图片描述

函数对象

#include <iostream>
#include <functional>

using namespace std;

struct PrintNum{
    void operator()(int i) const {
        printf("%d\n", i);
    }
};
int main() {
    // 存储到函数对象的调用
    std::function<void(int)> f_display_obj = PrintNum();
    f_display_obj(18);
    return 0;
}

在这里插入图片描述

成员函数

假如我们如下一个类,它既有普通成员函数,又重载了operator()

在这里插入图片描述
怎么存储成员函数呢?

第一种方法:

  • 先用function中指定类的类型
  • 然后用bind绑定成员函数

在这里插入图片描述

第二种方法:

  • 先用function中指定成员函数的签名
  • 然后在bind时直接绑定类的实例

在这里插入图片描述

#include <iostream>
#include <functional>


//使用成员函数()
class C {
public:
    void memfunc(int x, int y) const {printf("%d", x + y);};
};

int  main()
{
    std::function<void(const C&, int, int)> mf;
    mf =  &C::memfunc;
    mf(C(), 42, 77);
}

在这里插入图片描述

#include <iostream>
#include <functional>

using namespace std;

struct Foo {
    Foo(int num) : num_(num) {}
    void print_add(int i) const { printf("%d\n", i + num_); }
    int num_;
};

int main() {
    // 存储到成员函数的调用
    std::function<void(const Foo&, int)> f_add_display = &Foo::print_add;
    const Foo foo(314159);
    f_add_display(foo, 1);
    f_add_display(314159, 1);

    // 存储到数据成员访问器的调用
    std::function<int(Foo const&)> f_num = &Foo::num_;
    std::cout << "num_: " << f_num(foo) << '\n';

    // 存储到成员函数及对象的调用
    using std::placeholders::_1;
    std::function<void(int)> f_add_display2 = std::bind( &Foo::print_add, foo, _1 );
    f_add_display2(2);

    // 存储到成员函数和对象指针的调用
    std::function<void(int)> f_add_display3 = std::bind( &Foo::print_add, &foo, _1 );
    f_add_display3(3);
    return 0;
}

在这里插入图片描述

批量调用函数

#include <iostream>
#include <functional>
#include <vector>

using namespace std;
void func(int x, int y){
    printf("%d ", x + y);
};
int  main()
{
   //initialize collections of tasks
    std::vector<std::function<void(int, int)>> tasks;
    tasks.push_back(func);
    tasks.push_back([](int x, int y) {
        printf("%d, %d", x, y);
    });

    //call each task
    for(std::function<void(int, int)> f : tasks) {
        f(33, 66);
    }
}

在这里插入图片描述

使用ref库

function使用拷贝语义保存参数,当参数很大时或者参数不可以拷贝时怎么办呢?用ref库,它允许以引用的方式传递参数,能够降低function拷贝的代价。

  • function并不要求ref库提供operator(),因为它能够自动识别包装类reference_wrapper,并调用get()方法获得被包装的对象

在这里插入图片描述

  • 如果ref库不提供operator(),那么它将可能无法用于标准库算法,因为很多标准库算法总使用拷贝语义,算法内部的改变不能影响原对象。怎么解决?

在这里插入图片描述

在这里插入图片描述

用于回调

function可以代替函数指针,存储用于回调的函数.

为什么要替代呢?因为它非常灵活,无需改变回调的接口就可以解耦客户代码,使得客户代码不必绑死在一种回调形式上

class demo_class{
private:
    typedef  function<void (int)> func_t;  // func_t代替回调函数
    func_t  func_;                          // function对象
    int n_;                                //内部成员变量
public:
    demo_class(int i) : n_(i){};

    // demo_class使用模板函数accept()接受回调函数。之所以使用模板函数,是
    // 因为它更灵活,用户可以在不必关心内部存储形式情况下传递任何可调用对象
    template <typename  CallBack>
    void accept(CallBack f){         // 存储回调函数
        func_ = f;
    }

    // run()用于调用回调函数
    void run(){
        func_(n);
    }
};


//用于回调的普通函数
void call_back_func(int i){
    cout << "gerarator call_back_func :" << i * 2 << "\n";
}

// 用于回调的类
class call_back_obj{
private:
    int x_; //内部状态
public:
    call_back_obj(int i) : x_(i) {};

    void operator()(int i){
        cout << "class call_back_func :" << i * 2 << "\n";
    }
};
// 回调函数工厂类
class call_back_factory{
public:
    void call_back_obj1(int i){
        cout << "call_back_factory1 :" << i * 2 << "\n";
    }
    void call_back_obj2(int i, int j){
        cout << "call_back_factory1 :" << i + j << "\n";
    }
};


// function既可以接受函数指针也可以接受函数对象
int test_function_call(){
    // 回调普通函数
    demo_class dc(10);
    dc.accept(test_function_call); //接受
    dc.run();  //调用


    //回调类
    demo_class dc1(10);
    call_back_obj cbo(2);
    dc1.accept(ref(cbo));
    dc1.run();

    // 搭配bind
    demo_class dcc(10);
    call_back_factory cbf;

    dcc.accept(bind(&call_back_factory::call_back_obj1, cbf, _1));
    dcc.run();

    dcc.accept(bind(&call_back_factory::call_back_obj2, cbf, _1, 5));
    dcc.run();
}

对比auto

有时候关键字auto可以近似的取代function。比如:
在这里插入图片描述
但是它们的实现由很大的不同:

  • function类似一个容器,可以容纳任意有operator()的类型(函数指针、函数对象、lambda表达式),它是运行时的,可以任意拷贝、赋值、存储其他可调用物
  • 而auto仅是在编译期推导出的一个静态类型变量,很难再赋予其他值,也无法容纳其他的类型,不能用于泛型编程

那应该怎么选择呢?

  • 当存储一个可调用物用于回调的时候,最好使用function,它具有更多的灵活性,特别是把回调作为类的一个成员的时候我们只能使用function
  • auto也有它的优点,它的类型是在编译器推导的,没有运行时的开销,效率上要比function要高一点。但是它声明的变量不能存储其他类型的可调用物,不具有灵活性,只能用于有限范围的延后回调。

总结

  • 类std::function<>,声明于<function>,提供多态包装器,允许你把可调用对象(比如function、member function、function object、lambda等)当作最高级对象(也叫做函数包装器)
  • 函数包装器目的是为了把函数包装起来,原理是函数指针
  • 使用方法:std::function <返回值(函数参数)>函数指针 = 指向函数
  • 0
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
C++是在C语言的基础上发展而来的一种编程语言,它在语法和特性上相对于C语言有一些重要的别。下面是些例子来说明C++编程C语言之间的区别: 1. 面向对象编程(Object-Oriented Programming,OOP):C++是一种支持面向对象编程的语言,而C语言不直接支持。在C++中,可以使用类、对象、继承、多态等OOP的概念和特性来组织和设计程序,以提高代码的可重用性和可维护性。 2. 标准库(Standard Library):C++提供了一个丰富的标准库,包括容器(如vector、list)、算法(如排序、查找)、字符串处理、输入输出等。这些库提供了许多高级功能和数据结构,可以方便地完成各种任务,而C语言相对较少提供这样的标准库。 3. 异常处理(Exception Handling):C++引入了异常处理机制,可以通过抛出和捕获异常来处理程序中的错误。这使得在遇到错误时程序可以优雅地处理异常情况,并提供了更好的错误处理和代码健壮性。 4. 模板(Templates):C++引入了模板机制,允许编写泛型代码。模板可以实现通用的数据结构和算法,可以根据不同类型的数据进行参数化,从而提高代码的灵活性和可重用性。C语言没有这样的模板机制。 5. 命名空间(Namespace):C++引入了命名空间的概念,可以将代码分组到不同的命名空间中,避免命名冲突并提高代码的可读性和可维护性。C语言没有这样的命名空间机制。 6. 函数重载(Function Overloading):C++允许在同一个作用域内定义多个同名函数,但它们的参数列表不同。这被称为函数重载,它可以根据不同的参数类型和个数来自动选择调用哪个函数。而在C语言中,函数重载是不允许的。 上述例子只是一些常见的区别,C++还有很多其他特性,如引用、运算符重载、虚函数等,这些特性使得C++更加强大和灵活。但同时也需要注意,C++仍然保留了与C语言兼容的部分,所以在使用C++编程时可以兼顾使用C语言的部分特性。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值