云计算百科
云计算领域专业知识百科平台

35 C++ STL模板库4-函数适配器

C++ STL模板库4-函数适配器

文章目录

  • C++ STL模板库4-函数适配器
    • 一、绑定器(Binder)
    • 二、取反器和普通函数成员函数适配器
    • 三、推荐替换方案

C++的函数适配器主要用于扩展或特化
函数对象(仿函数)的功能,使其适配算法或特定需求。

一、绑定器(Binder)

将二元函数对象的一个参数绑定到固定值,转换为一元函数对象(就是反函数):

  • bind1st(已弃用)

    • 绑定二元函数对象的第一个参数到指定值。
    • 示例:bind1st(greater<int>(), 5) 将比较值固定为5,检查元素是否大于5 。
    • 需要从unary_function<int, int, void>继承
    • 只能绑定仿函数
    • 在C++11后已弃用
  • bind2nd(已弃用)

    • 绑定二元函数对象的第二个参数到指定值。
    • 示例:bind2nd(less<int>(), 10) 将比较值固定为10,检查元素是否小于10 。
    • 需要从binary_function<int, int, void>继承
    • 只能绑定仿函数
    • 在C++11后已弃用

    示例:

    #include <iostream>
    #include <vector>
    #include <algorithm>//包含for_each/find_if算法头的文件
    #include <functional>//系统定义的仿函数和适配器头文件

    struct Compare:public std::binary_function<int, int, bool> // c++ 以上已经移除
    {
    bool operator()(int a, int b)const
    {
    return a>b;
    }
    };

    int main() {

    std::vector<int> vec = {3, 6, 1, 8, 4};

    // 查找第一个小于5的元素(结果指向3) 绑定参数到仿函数的第一个参数
    auto it = std::find_if(vec.begin(), vec.end(), std::bind1st(Compare(), 5));
    if (it != vec.end()) {
    std::cout << "找到第一个小于 5 的元素: " << *it << std::endl;
    }

    // 查找第一个大于5的元素(结果指向6) 绑定参数到仿函数的第二个参数
    it = std::find_if(vec.begin(), vec.end(), std::bind2nd(Compare(), 5));
    if (it != vec.end()) {
    std::cout << "找到第一个大于 5 的元素: " << *it << std::endl;
    }

    return 0;
    }

  • bind

    • 绑定参数到函数的任意位置。
    • 可绑定成员函数、普通函数及Lambda表达式

    [示例]

    #include <iostream>
    #include <vector>
    #include <algorithm>//包含for_each/find_if算法头的文件
    #include <functional>//系统定义的仿函数和适配器头文件

    bool Compare(int a, int b)
    {
    return a>b;
    }

    void show(int a,char c, float f)
    {
    std::cout<<a<<" "<<c<<" "<<f<<std::endl;
    }
    int main() {

    std::vector<int> vec = {3, 6, 1, 8, 4};

    // 查找第一个小于5的元素(结果指向3) 绑定参数到仿函数的第一个参数
    auto it = std::find_if(vec.begin(), vec.end(), std::bind(Compare,5,std::placeholders::_1));
    if (it != vec.end()) {
    std::cout << "找到第一个小于 5 的元素: " << *it << std::endl;
    }

    // 查找第一个大于5的元素(结果指向6) 绑定参数到仿函数的第二个参数
    it = std::find_if(vec.begin(), vec.end(), std::bind(Compare, std::placeholders::_1, 5));
    if (it != vec.end()) {
    std::cout << "找到第一个大于 5 的元素: " << *it << std::endl;
    }

    show(2, 'A',3.14f);
    auto fun = std::bind(show,3,'C',2.15f);
    fun();//输出是 3 C 2.15
    fun(4, 'H',2.14);//输出还是 3 C 2.15 ,始终是绑定的参数
    show(2, 'A',3.14f);//2 A 3.14 绑定不会改变原来的实际值。

    return 0;
    }

    • 如果某个位置的参数不要绑定可以用std::placeholders::_N占位符,placeholders是一个命名空间,里面定义了_1到_20个占位符对象

    • 占位符占的是传参时可以改变的,_1是第一个参数_2是第二个参数,_3是第三个参数……

    • 占位符的顺序要和函数的顺序一致,结果才能保证正确

    • 参数类型一致性,std::placeholders::_3表示传参时第三个参数所放的位置

      //第三个参数放到二个位置
      std::bind(show,std::placeholders::_1,std::placeholders::_3,9);

      //第三个参数放到第一个位置
      std::bind(show,std::placeholders::_3,std::placeholders::_1,9);

    • std::bind生成的函数会忽略多余参数,但需确保必要参数已绑定。

      #include <iostream>
      #include <vector>
      #include <algorithm>//包含for_each/find_if算法头的文件
      #include <functional>//系统定义的仿函数和适配器头文件

      void show(int n1,int n2, int n3)
      {
      std::cout<<n1<<" "<<n2<<" "<<n3<<std::endl;
      }
      int main() {

      show(1, 2,3);//1 2 3

      //参数第一个用占位符占了,是可以改变的,第二个和第三个被绑定是固定值,传任何参数都不改变
      auto fun = std::bind(show,std::placeholders::_1,4,5);
      fun(30);//输出是 30 4 5
      fun(30,'G',8);//输出是 30 4 5

      std::cout<<"\\n";
      auto fun1 = std::bind(show,std::placeholders::_1,std::placeholders::_2,9);
      fun1(4,5);//4 5 9 –show(4, 5, 9)
      fun1(1,2,3,4,6,7,8,"…..","你好"); //1 2 9 –show(1, 2, 9)
      // fun1(4);// 错误至少两个参数,占位的必须传参

      std::cout<<"\\n"; //调换位置
      auto fun2 = std::bind(show,std::placeholders::_2,std::placeholders::_1,9);
      fun2(4,5);//5 4 9 –show(5, 4, 9)
      fun2(5,4);//4 5 9 –show(4, 5, 9)

      std::cout<<"\\n";
      auto fun3 = std::bind(show,std::placeholders::_1,9,std::placeholders::_2);
      fun3(4,5);//4 9 5 –show(4, 5, 9)

      std::cout<<"\\n";
      auto fun4 = std::bind(show,std::placeholders::_1,9,std::placeholders::_3);
      // fun4(4,5);//错误 ,少一个参数 ,最少三个参数
      fun4(4,5,9);//4 9 9 –show(4, 9, 9)

      std::cout<<"\\n";
      auto fun5= std::bind(show,std::placeholders::_1,std::placeholders::_3,9);
      fun5(4,5,7);//4 7 9 –show(4, 7, 9)
      fun5(4,5,7,1,2,3);//4 7 9 –show(4, 7, 9)
      // fun5(4,5);// 错误 ,少一个参数 ,最少三个参数

      std::cout<<"\\n";
      auto fun6= std::bind(show,std::placeholders::_2,std::placeholders::_2,9);
      fun6(4,5,7);//5 5 9 –show(5, 5, 9)

      return 0;
      }

    • 函数重载需显式指定函数类型 函数重载,std::bind无法自动推断重载版本,导致编译器解析失败 解决方案:显式指定函数类型,例如使用static_cast或模板参数。 注意函数名要加括号

      #include <iostream>
      #include <functional>//系统定义的仿函数和适配器头文件

      void show(int a,char c, float f)
      {
      std::cout<<a<<" "<<c<<" "<<f<<std::endl;
      }

      void show(int n1,int n2, int n3)
      {
      std::cout<<n1<<" "<<n2<<" "<<n3<<std::endl;
      }
      int main() {

      show(2, 'A',3.14f);//2 A 3.14

      // 显式指定绑定函数 show(int a,char c, float f)
      auto fun = std::bind(static_cast<void(*)(int, char, float)>(show),std::placeholders::_1,'C',2.15f);
      fun(30);//输出是 30 C 2.15

      // 显式指定绑定函数 show(int n1,int n2, int n3)
      std::cout<<"\\n";
      auto fun1 = std::bind(static_cast<void(*)(int, char, float)>(show),std::placeholders::_1,'b',std::placeholders::_2);
      fun1(15,1);//15 b 1
      fun1(15,1,3);//15 b 1

      return 0;
      }

    std::bind 仍是合法且有效的工具,但导致复杂逻辑易导致代码冗长。建议改用更简洁、直观,且类型安全的Lambda 表达式.

    二、取反器和普通函数成员函数适配器

    谓词(predicate ) 返回值是bool类型的仿函数。一个参数的是一元谓词,两个参数的是二元谓词。

    翻转函数对象的返回值逻辑:

  • not1

    • 取反一元谓词的返回值。
    • 示例:not1(bind2nd(greater<int>(), 5)) 筛选不大于5的元素。
  • not2

    • 取反二元谓词的返回值。
    • 示例:not2(equal_to<int>()) 判断两个元素是否不相等。
  • 普通函数适配器ptr_fun

    • 将普通函数转换为可被STL算法调用的函数对象。
    • 示例:bind2nd(ptr_fun(myPrintInt01), 1000) 绑定普通函数的第二个参数为1000 。

    not1,not2,ptr_fun都是现代C++中已弃用的功能,建议改用更简洁、直观,且类型安全的Lambda 表达式.

    auto isOdd = [](int n) { return !isEven(n); }; // 直接取反

  • 成员函数适配器 用于将类成员函数转换为可被STL算法调用的函数对象:

  • mem_fun
    • 适配指向对象指针的成员函数。
    • 示例:for_each(v.begin(), v.end(), mem_fun(&Person::show)) 调用对象指针的成员函数。
  • mem_fun_ref
    • 适配引用类型的成员函数。
    • 示例:for_each(v.begin(), v.end(), mem_fun_ref(&Person::show)) 调用对象引用的成员函数。
    • 此两者都已弃用建议改用Lambda 表达式.

    三、推荐替换方案

  • Lambda 表达式 auto predicate = [](int a) { return compare(a, 2); }; // 直接内联逻辑

  • std::function 结合 Lambda

    std::function<bool(int)> predicate =
    [ ](int a) { return compare(a, 2); }; // 显式类型声明

    • 必须显式声明 std::function 的参数和返回类型,否则会导致编译错误。 示例:std::function<void(int)> func = print;(print 函数需接受 int 参数)。

    • 优化建议:在性能敏感场景中,优先使用模板或直接传递 Lambda。

    • std::function 可以被赋值为 nullptr,调用空函数对象会触发 std::bad_function_call 异常。 示例:

      std::function<void()> func = nullptr;
      if (func) func(); // 需要显式检查

    • 不支持存储 const 或 volatile 成员函数(需通过 std::bind 适配)。

    • 避免过度使用- 对于已知类型的函数,直接使用模板或函数指针更高效。

  • 用法示例

  • 转换普通函数

    #include <functional>
    #include <iostream>

    void print(int n) { std::cout << n << " "; }

    int main() {
    std::function<void(int)> func = print; // 直接赋值普通函数
    func(5); // 输出:5
    return 0;
    }

  • 存储 Lambda 表达式

    #include <functional>
    #include <iostream>

    int main() {
    std::function<int(int, int)> add = [](int a, int b) { return a + b; };
    std::cout << add(3, 4) << std::endl; // 输出:7
    return 0;
    }

  • 存储成员函数

    #include <functional>
    #include <iostream>

    class Person {
    public:
    void show() const { std::cout << "Hello" << std::endl; }
    };

    int main() {
    Person p;
    std::function<void(Person*)> func = &Person::show;
    func(&p); // 输出:Hello
    return 0;
    }

  • 存储其他函数对象

    #include <functional>
    #include <iostream>

    struct Adder {
    int operator()(int a, int b) { return a + b; }
    };

    int main() {
    std::function<int(int, int)> func = Adder{};
    std::cout << func(2, 3) << std::endl; // 输出:5
    return 0;
    }

  • 赞(0)
    未经允许不得转载:网硕互联帮助中心 » 35 C++ STL模板库4-函数适配器
    分享到: 更多 (0)

    评论 抢沙发

    评论前必须登录!