美文网首页
C++ 中几种调用对象

C++ 中几种调用对象

作者: wayyyy | 来源:发表于2018-07-05 23:29 被阅读0次

    C++中一共有5种调用对象:函数函数指针重载了函数调用运算符的类(仿函数)bind创建的对象lambda表达式

    函数指针

    仿函数

    lambda表达式

    没有lambda的话,函数对象的定义太麻烦了,你得定义一个类,重载operator(),然后再创建这个类的实例。所以lambda表达式可以看成是函数对象的语法糖,在你需要的时候,它可以很简洁地给你生成一个函数对象。

    语法格式
    [capture list] (param list) -> return type  { function body  }
    

    [capture list]是一个所在函数中定义的局部变量(非static)的列表,param listreturn typefunction body和普通的函数一样表示返回类型(尾置返回),参数列表,和函数体。

    我们可以忽略参数列表和返回类型,但必须包含捕获列表和函数体

    auto f = [] {  return 42;  }    // 必须包含捕获列表和函数体。
    

    当定义一个lambda时,编译器生成一个与lambda对应的新的(未命名的)类类型。默认情况下,lambda生成的类都包含一个对应该lambda所捕获的变量的数据成员,在lambda创建时被初始化。

    向lambda传递参数

    与一个普通函数类似,调用一个lambda时给定的实参被用来初始化lambda的形参。通常,实参和形参类型必须匹配。但与普通函数不同,lambda不能有默认函数参数。

    使用捕获列表
    • 值捕获
      采用值捕获的前提是 变量可以被拷贝,另外被捕获的变量的值是在lambda创建时拷贝,而不是调用时拷贝
      void func
      {
          int v1 = 42;
          auto f = [v1] {  return v1;  }
          v1 = 0;
          auto j = f();      // j = 42,  v1在lambda创建时拷贝,而不是调用时拷贝
      }
      
    • 引用捕获
      采用引用捕获时,必须确保被引用的对象在lambda执行的时候是存在的(lambda捕获的都是局部变量,这些变量在函数结束后就不存在了)
      void func()
      {
          int val = 42;
          auto f = [&val]() {   return val; };
          val = 0;
          auto j = f();      // j = 0,引用捕获。
      }
      
    • 隐式捕获
      除了显示列出我们希望使用来自函数的变量之外,我们可以让编译器隐式推断lambda体中的代码来推断我们使用了哪些变量。为了指示编译器推断捕获列表,我们应在捕获列表写&和=,
      • &告诉编译器采用引用捕获方式
      • =表示采用值捕获的方式。
      wc = find_if(words.begin(),  words.end(),  [=](const string &s) {  return s.size() > sz;  })
      
    可变lambda
    • mutable
      对于lambda表达式。默认情况下,lambda不会改变其值,如果我们希望能改变一个被捕获变量的值,就必须在参数列表首加上关键字mutable。
      void func()
      {
          int val = 42;
          // auto f = [val]() mutable { return ++val;   };    // 编译错误,v1只读。
          auto f = [val]() mutable {    return ++val;   };    // 加上mutable关键字,编译正确。
          int j = f();    // j = 43
          cout << j << " " << val << endl;    // 输出 43 和 42
      }
      

    加上mutable关键字后,值捕获也会改变被捕获变量的值。

    返回类型

    默认情况下,如果一个lambda体包含return之外的任何语句,编译器假定此lambda返回void。所以此时我们就要显示指定尾指返回类型。

    transfrom(v.begin(), v.end(), 
              [](int i) {  
                  if (i < 0) 
                      return -i; 
                  else 
                      return i;  
              })  // 错误
    transfrom(v.begin(), v.end(), 
              [](int i) -> int {  
                  if (i < 0) 
                      return -i; 
                  else 
                      return i;  
              })
    

    参数绑定bind函数

    我们需要在一个std::vector<std::string>中寻找大于某长度单词。那么我们可以这样写:

    auto it = find_if(vec.begin(), vec.end(), 
                      [](const std::string& s) {    
                          return (s.size() > 5);    
                      });
    

    同样,我们可以用函数去实现。

    bool check_size(const std::string& s)
    {
        return s.size() > 5;
    }
    auto it = find_if(vec.begin(), vec.end(), check_size);
    

    但假设,我们想要指定长度来筛选。那用函数是不能实现的。

    std::string::size_type sz = 5;
    auto it = find_if(vec.begin(), vec.end(), 
                      [sz](const std::string& s) {  
                          return (s.size() > sz);   
                      });
    
    bool check_size(const std::string& s, std::string::size_type sz)
    {
        return s.size() > sz;
    }
    

    但是这个函数不用你管作为find_if的参数,因为find_if接受的是一元谓词。

    // find_if 可能实现
    template<class InputIt, class UnaryPredicate>
    InputIt find_if(InputIt first, InputIt last, UnaryPredicate p)
    {
        for (; first != last; ++first) {
            if (p(*first)) {    // p只接受一个参数
                return first;
            }
        }
        return last;
    }
    

    但我们可以标准库的bind函数,它定义在头文件functional中,可以将它看成一个通用的函数适配器,它接受一个可调用的对象,生成一个新的可调用对象来适应原对象的参数列表

    auto newCallable = bind(callable, arg_list);
    

    那么现在可以这样写:

    auto it = find_if(vec.begin(), vec.end(), bind(check_size, std::placegolders::_1, sz));
    

    此处的bind调用生成一个可调用对象,将check_size的第二个参数绑定到sz的值,当find_if对vec中的std::string调用这个对象时。它会将给定的参数std::stringsz传递给check_size函数。

    • 使用placegolders名字
      名字_n都定义在名为placegolders的命名空间中,而这个命名空间本身定义在std命名空间中。它表示占位符,意味着将自己第n个参数按照顺序传递给原调用对象。

      auto g = bind(f, a, b, std::placegolders::_2, c, std::placegolders::_1);
      g(x, y) == f(a, b, y, c, z);
      

      在上面的例子中,g表示一个有两个参数的新的调用对象,原调用对象f有5个参数。g的第一个参数是f的第5个参数,g的第2个参数是f的第3个参数。

    • 绑定引用参数
      默认情况下,bind那些不是占位符的参数被拷贝到bind返回的可调用对象中。但是和lambda一样,有时对绑定的参数我们希望以引用的方式传递,或是要绑定的参数类型无法拷贝。

      例如,我们希望在打印每个vec中的单词后输出一个换行。

      for_each(vec.begin(), vec.end(), [&os, c](const std::string& s){  os << s << c;  });
      
      // 很容易编写一个对应的函数版本:
      ostream& print(ostream& os, const std::string& s, char c)  {  return os << s << c;  } 
      // 错误:不能拷贝os
      for_each(vec.begin(), vec.end(), bind(print, os, _1, ' '));
      

      那么,这时我们希望传递给bind的是一个对象而又不拷贝它,那么就必须使用refcref函数。它返回一个对象的引用。

      for_each(vec.begin(), vec.end(), bind(print, std::ref(os), _1, ' '));
      

    相关文章

      网友评论

          本文标题:C++ 中几种调用对象

          本文链接:https://www.haomeiwen.com/subject/cicuuftx.html