C++中function,bind,lambda

简介: C++中function,bind,lambda

c++11之前,STL中提供了bind1st以及bind2nd绑定器

首先来看一下他们如何使用:

如果我们要对vector中的元素排序,首先会想到sort,比如:

void output(const vector<int> &vec)
{
  for (auto v : vec) {
    cout << v << " ";
  }
  cout << endl;
}
int main() {
  vector<int> vec;
  srand(time(nullptr));
  for (int i = 0; i < 20; i++) {
    vec.push_back(rand() % 100 + 1);
  }
  output(vec);
  sort(vec.begin(), vec.end());
  output(vec);
  //greater 从大到小排序
  sort(vec.begin(), vec.end(), greater<int>());
  output(vec);
  //less 从小到大排序
  sort(vec.begin(), vec.end(), less<int>());
  output(vec);
  return;
}

sort最后一个参数传入的greater或less都被称为函数对象,顾名思义,表现像函数的对象,因为他们的调用都是在后面加上"()"。

其实这是因为他们都重载了operator()。

来看下greater的定义:

template<class _Ty = void>
  struct greater
  { // functor for operator>
  _CXX17_DEPRECATE_ADAPTOR_TYPEDEFS typedef _Ty first_argument_type;
  _CXX17_DEPRECATE_ADAPTOR_TYPEDEFS typedef _Ty second_argument_type;
  _CXX17_DEPRECATE_ADAPTOR_TYPEDEFS typedef bool result_type;
  constexpr bool operator()(const _Ty& _Left, const _Ty& _Right) const
    { // apply operator> to operands
    return (_Left > _Right);
    }
  };

可以看到确实重载了operator(),需要传入两个参数,所以它还是一个二元函数对象。函数对象的概念至关重要!

那什么时候会用到bind1st、bind2nd呢?

比如我们现在要找到第一个小于70的位置,插入70。

可以使用find_if:

auto it1 = find_if(vec.begin(), vec.end(),
    bind1st(greater<int>(), 70));
  if (it1 != vec.end()) {
    vec.insert(it1, 70);
  }

这里使用bind1st(greater<int>(), 70)作为find_if的第三个参数,bind1st的作用,首先,将70绑定到二元函数对象(greater)的第一个参数上,其次,将二元函数对象(greater)转为一元函数对象(因为70已知了),传入到find_if的第三个参数中。这便是他的应用场景,或者还可以使用bind2nd和less的搭配:

auto it1 = find_if(vec.begin(), vec.end(),
    bind2nd(less<int>(), 70));
  if (it1 != vec.end()) {
    vec.insert(it1, 70);
  }

关于bind1st(greater(), 70)和bind2nd(less(), 70)的理解

因为我们要找小于70的位置,所以,

对于greater来说,left > right,所以绑定到第一个位置

对于less来说,left < right,所以绑定到第二个位置

理解了绑定器后,再来看看function

function需要一个函数类型进行实例化:

void hello1()
{
  cout << "hello world!" << endl;
}
void hello2(string str)
{
  cout << str << endl;
}
class Test
{
public:
  void hello(string str) { cout << str << endl; }
};
int main() {
  function<void()> func1 = hello1;
  //function<void()> func1(hello1);
  func1();//func1.operator() => hello1();
  function<void(string)> func2 = hello2;
  func2("gao");//func2.operator()(string str) => hello2(str);
  function<int(int, int)> func3 = [](int a, int b) -> int { return a + b; };
  cout << func3(100, 200) << endl;
  //通过function调用类的成员方法
  function<void(Test*, string)> func5 = &Test::hello;
  func5(&Test(), "call Test::hello!");
  return 0;
}

对function的调用,实际上是调用了function的()重载,从而调用原函数。上面的例子中可以看到lambda表达式也可以通过function调用。这其实就说明了function的真正用途:保存函数对象的类型,也是对函数对象的封装。这也是它和c语言的函数指针的区别(lambda无法通过函数指针调用)。

现在有这样一个场景:

两(多)个函数,有大部分的代码都是一样的,其中只有一两行代码有不一样的地方,我们可以对这个不一样的地方,使用function做一个抽象,比如:

有两个vector打印函数,一个打印模5=0的元素,一个打印大于10的元素:

void print(vector<int> &number, function<bool(int)> filter) {
  for (const int &i : number) {
    if (filter(i)) {
      cout << i << endl;
    }
  }
}
print(numbers, [](int i){ return i % 5 == 0; });
print(numbers, [](int i){ return i > 10; });

这样就不用定义两个不同的打印函数了。

关于闭包的概念:

下面是维基百度对于闭包的定义:

在计算机科学中,闭包(英语:Closure),又称词法闭包(Lexical Closure)或函数闭包(function closures),是引用了自由变量的函数。 这个被引用的自由变量将和这个函数一同存在,即使已经离开了创造它的环境也不例外。

简单来说:闭包可以记忆住创建它时候的那些变量。

下面,我们再通过一个例子来说明。

现在,假设我们的需求是:获取一个集合中最小和最大值,并在稍后的时候(可能是另外一个函数中)打印它们。 这里,我们常规的做法通常是:通过一个函数获取集合的最大,最小值,然后保存住,最后在需要的时候访问这两个值,然后打印它们。

这样做就会需要解决:如何保存和传递最大,最小这两个值。

但实际上,这里我们可以考虑用闭包来实现这个功能,让闭包把最大,最小两个值捕获下来,然后在需要的地方调用就可以了。

请看一下下面这段代码:

void getMinMax(vector<int>& number, function<void ()>& printer) {
  int min = number.front();
  int max = number.front();
  for (int i : number) {
      if (i < min) {
          min = i;
      }
      if (i > max) {
          max = i;
      }
  }
  printer = [=] () {
    cout << "min:" <<min<< endl;
    cout << "max:" << max << endl;
  };
}

这里,我们通过function<void ()>& printer传递出这个闭包。 然后,在需要的地方,这样即可:

function<void()> printer;
getMinMax(numbers, printer);
......
printer();

这里的printer其实是我们前面从getMinMax函数出传出的闭包,这个闭包捕获了min和max。我们直接传递这个闭包给需要的地方使用,而不用传递裸的两个数值,是不是优雅的不少?

bind

/*
c++11 bind绑定器 -> 返回的结果还是一个函数对象
*/
void hello(string str) { cout << str << endl; }
int sum(int a, int b) { return a + b; }
class Test 
{
public:
  int sum(int a, int b) { return a + b; }
};
int main()
{
  bind(hello, "hello, bind!")();
  cout << bind(sum, 10, 20)() << endl;
  cout << bind(&Test::sum, Test(), 20, 30)() << endl;
  //参数占位符 绑定器出了语句,无法继续使用
  bind(hello, placeholders::_1)("hello bind 2!");
  cout << bind(sum, placeholders::_1, placeholders::_2)(200, 300) << endl;
  //此处把bind返回的绑定器binder就复用起来了
  function<void(string)> func1 = bind(hello, placeholders::_1);
  func1("hello gao");
  return 0;
}

使用bind和function的线程池例子:

class Thread
{
public:
  //接收一个函数对象,参数都绑定了,所以不需要参数
  Thread(function<void()> func) : _func(func) {}
  thread start()
  {
    thread t(_func);
    return t;
  }
private:
  function<void()> _func;
};
class ThreadPool
{
public:
  ThreadPool() {}
  ~ThreadPool() {
    for (int i = 0; i < _pool.size(); i++) {
      delete _pool[i];
    }
  }
  void startPool(int size)
  {
    for (int i = 0; i < size; i++) {
      //成员方法充当线程函数,绑定this指针
      _pool.push_back(new Thread(bind(&ThreadPool::runInThread, this, i)));
    }
    for (int i = 0; i < size; i++) {
      _handler.push_back(_pool[i]->start());
    }
    for (thread &t : _handler) {
      t.join();
    }
  }
private:
  vector<Thread*> _pool;
  vector<thread> _handler;
  void runInThread(int id) {
    cout << "call runInThread! id:" << id << endl;
  }
};
int main()
{
  ThreadPool pool;
  pool.startPool(10);
  return 0;
}

lambda(匿名函数对象)

lambda表达式的语法

[捕获外部变量](形参列表)->返回值{操作代码};
[]:表示不捕获任何外部变量
[=]:表示以传值的方式捕获外部的所有变量
[&]:表示以传引用的方式捕获外部的所有变量
[this]:捕获外部的this指针
[=,&a]: 表示以传值的方式捕获外部的所有变量,但是a变量以传引用的方式捕获
[a,b]:表示以值传递的方式捕获外部变量a和b
[a,&b]:a以值传递捕获,b以引用捕获

使用举例:

int main()
{
  auto func1 = []()->void {cout << "hello world!" << endl; };
  func1();
  //[]:表示不捕获任何外部变量
  //编译报错
  /*int a = 10;
  int b = 20;
  auto func3 = []()
  {
    int tmp = a;
    a = b;
    b = tmp;
  };
  */
  //以值传递a,b,lambda实现的重载函数operator()中,是const方法,不能修改成员变量
  //如果一定要修改,将lambda修饰成mutable,但是这并不会改变a的值,因为这是值传递
  //int a = 10;
  //int b = 20;
  //auto func3 = [a, b]() /*mutable*/
  //{
  //  int tmp = a;
  //  a = b;
  //  b = tmp;
  //};
  vector<int> vec;
  vec.push_back(1);
  vec.push_back(2);
  vec.push_back(3);
  for_each(vec.begin(), vec.end(), [](int a) {
    cout << a << endl;
  });
  return 0;
}
class Data
{
public:
  Data(int a, int b) : ma(a), mb(b) {}
  int ma;
  int mb;
};
int main()
{
  map<int, function<int(int, int)>> caculateMap;
  caculateMap[1] = [](int a, int b)->int {return a + b; };
  caculateMap[2] = [](int a, int b)->int {return a - b; };
  cout << caculateMap[1](1, 2) << endl;
  //智能指针自定义删除器
  unique_ptr<FILE, function<void(FILE*)>>
    ptr1(fopen("data.txt", "w"), [](FILE *pf) { fclose(pf); });
  //优先队列
  using FUNC = function<bool(Data&, Data&)>;
  priority_queue<Data, vector<Data>, FUNC>
    maxHeap([](Data &d1, Data &d2)->bool
    {
    return d1.mb > d2.mb;
    });
  maxHeap.push(Data(10, 20));
  return 0;
}

lambda表达式是如何实现的?

其实是编译器为我们了创建了一个类,这个类重载了(),让我们可以像调用函数一样使用。所以,你写的lambda表达式和真正的实现,是这个样子的:

而对于捕获变量的lambda表达式来说,编译器在创建类的时候,通过成员函数的形式保存了需要捕获的变量,所以看起来是这个样子:

似乎也没有什么神奇的地方。但正是由于编译器帮我们实现了细节,使我们的代码变得优雅和简洁了许多。


相关文章
|
1月前
|
算法 编译器 C++
【C++11】lambda表达式
C++11 引入了 Lambda 表达式,这是一种定义匿名函数的方式,极大提升了代码的简洁性和可维护性。本文详细介绍了 Lambda 表达式的语法、捕获机制及应用场景,包括在标准算法、排序和事件回调中的使用,以及高级特性如捕获 `this` 指针和可变 Lambda 表达式。通过这些内容,读者可以全面掌握 Lambda 表达式,提升 C++ 编程技能。
77 3
|
3月前
|
存储 算法 程序员
C++ 11新特性之function
C++ 11新特性之function
65 9
|
3月前
|
存储 编译器 调度
C++ 11新特性之bind
C++ 11新特性之bind
37 1
|
2月前
|
C++ 容器
函数对象包装器function和bind机制
函数对象包装器function和bind机制
24 0
|
3月前
|
算法 编译器 程序员
C++ 11新特性之Lambda表达式
C++ 11新特性之Lambda表达式
20 0
|
5月前
|
存储 C++ 运维
开发与运维函数问题之使用C++标准库中的std::function来简化回调函数的使用如何解决
开发与运维函数问题之使用C++标准库中的std::function来简化回调函数的使用如何解决
57 6
|
5月前
|
安全 编译器 C++
C++一分钟之-泛型Lambda表达式
【7月更文挑战第16天】C++14引入泛型lambda,允许lambda接受任意类型参数,如`[](auto a, auto b) { return a + b; }`。但这也带来类型推导失败、隐式转换和模板参数推导等问题。要避免这些问题,可以明确类型约束、限制隐式转换或显式指定模板参数。示例中,`safeAdd` lambda使用`static_assert`确保只对算术类型执行,展示了一种安全使用泛型lambda的方法。
73 1
|
6月前
|
算法 编译器 C++
C++一分钟之—Lambda表达式初探
【6月更文挑战第22天】C++的Lambda表达式是匿名函数的快捷方式,增强函数式编程能力。基本语法:`[capture](params) -&gt; ret_type { body }`。例如,简单的加法lambda:`[](int a, int b) { return a + b; }`。Lambda可用于捕获外部变量(值/引用),作为函数参数,如在`std::sort`中定制比较。注意点包括正确使用捕获列表、`mutable`关键字和返回类型推导。通过实践和理解这些概念,可以写出更简洁高效的C++代码。
63 13
|
5月前
|
存储 C++
【C++】string类的使用③(非成员函数重载Non-member function overloads)
这篇文章探讨了C++中`std::string`的`replace`和`swap`函数以及非成员函数重载。`replace`提供了多种方式替换字符串中的部分内容,包括使用字符串、子串、字符、字符数组和填充字符。`swap`函数用于交换两个`string`对象的内容,成员函数版本效率更高。非成员函数重载包括`operator+`实现字符串连接,关系运算符(如`==`, `&lt;`等)用于比较字符串,以及`swap`非成员函数。此外,还介绍了`getline`函数,用于按指定分隔符从输入流中读取字符串。文章强调了非成员函数在特定情况下的作用,并给出了多个示例代码。
|
6月前
|
C++
C++语言的lambda表达式
C++从函数对象到lambda表达式以及操作参数化