学习大纲


Ref: 【c++】可调用对象(Callable Objects)

Five kinds of callable objects:

  1. Functions.
  2. Pointers to functions.
  3. Objects of a class that overloads ().
  4. Objects created by bind.
  5. Objects created by lamdba expressions.

函数、函数指针

1-2. Functions and Pointers to functions

普通函数

bool range5to10(int val)  // <-- bool (*p_func)(int) 改为函数指针是一个道理
{
   return (5 <= val && val <= 10);
} vector<int> vec{ , , , , , , , }; auto presult = find_if(vec.begin(), vec.end(), range5to10); if (presult == vec.end())
{
cout << "no" << endl;
}
else
{
cout << "yes" << endl;
}

仿函数

模板de仿函数

更高逼格的template,至少能在类型上更加灵活一些。

template <class T, T lb, T ub>
struct range {
bool operator() (T val) {
return (lb <=val && val <=ub);
}
};

auto presult = find_if(vec.begin(), vec.end(), range<int, 5, 10>{});

3. Object of class

对象de仿函数

class range {
public:
range(int l, int u): lb(l), ub(u) {  // <-- 这里的构建函数的使用体现了其优势:初始化能多做些事情
} bool operator() (int val) {
return (lb <=val && val <=ub);
} private:
int lb, ub;
}; auto presult = find_if(vec.begin(), vec.end(), range{5, 10});

以及补充的三个例子。

struct MyPlus{
int operator() (const int &a , const int &b) const {
return a + b;
}
};
int main()
{
MyPlus a;
cout << MyPlus()(,) << endl;   // 1、通过产生临时对象 调用重载运算符
cout << a.operator()(,) << endl;  // 2、通过对象 显示调用重载运算符
cout << a(,) << endl;   // 3、通过对象 隐示地调用重载运算符
return ;
}

std::function

4. Bind

占位符

一个变量的占位符, 用于函数绑定时使用。

void f(int a, int b, int c)
{
cout << a << " " << b << " " << c << endl;
} auto g1 = bind(f, placeholders::_1, placeholders::_2, );
g1(, );

能绑定什么

Ref: C++11----std::bind/std::placeholder

std::bind 是用来绑定函数调用的参数的,解决的需求 是:

我们有时候可能不会一次性获得调用某个函数的全部参数,通过这个函数,我们可以将部分调用参数提前绑定到函数身上成为一个新的对象,然后在参数齐全后,再完成调用。

#include <functional>
#include <iostream>
int foo(int a, int b) {
return a + b;
}
class Mybind {
public:
int operator() (int a, int b)
{
return a + b;
}
};
int main() {
// 1.将参数一:100绑定到函数 foo 上,但是使用 std::placeholders::_1 来对第一个参数进行占位
auto bindFoo = std::bind(foo, std::placeholders::_1, 100);
// 这时调用 bindFoo 时,只需要提供第一个参数即可
std::cout << bindFoo() << std::endl;

// 2.将参数一:100绑定到lambda上,但是使用 std::placeholders::_1 来对第一个参数进行占位
auto f1 = std::bind([](int a, int b)->int {return a + b; }, std::placeholders::_1, 100);
std::cout << f1() << std::endl;

// 3.将参数一:100绑定到对象上,但是使用 std::placeholders::_1 来对第一个参数进行占位
Mybind m;
auto f2 = std::bind(m, std::placeholders::_1, );
std::cout << f2() << std::endl; std::cin.get();
return ;
}

提示:注意 auto 关键字的妙用。有时候我们可能不太熟悉一个函数的返回值类型,但是我们却可以通过 auto 的使用来规避这一问题的出现。

其中:绑定参数技术多用于设计模式中的适配器模式

参数绑定规则

<示范一>

fun1说明:占位符->第一个参数和函数第一个参数匹配(int),第二个参数和第二个参数匹配(char),第三个参数和第三个参数匹配。

fun2说明:占位符->第二个参数和函数第一个参数匹配(int),第三个参数和第二个参数匹配(char),第一个参数和第三个参数匹配。

fun3说明:占位符->第一个参数和函数第一个参数匹配(int),第二个参数和第二个参数匹配(char),第三个参数默认为98.77

#include <functional>
#include <iostream> int TestFunc(int a, char c, float f)
{
std::cout << a << std::endl;
std::cout << c << std::endl;
std::cout << f << std::endl;
return a;
} int main(void)
{
auto fun1 = std::bind(TestFunc, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3);
auto fun2 = std::bind(TestFunc, std::placeholders::_2, std::placeholders::_3, std::placeholders::_1);
auto fun3 = std::bind(TestFunc, std::placeholders::_1, std::placeholders::_2, 98.77); fun1(, 'C',100.1);
fun2(100.1, , 'C');
fun3(,'C'); return 0;
}

<示范二>

这里placeholders的位置发生变化,看似对结果没有影响。

 #include <functional>
#include <iostream>
#include <vector>
#include <algorithm> using namespace std; class range {
public:
bool operator() (int lb, int ub, int val) {
cout << "lb = " << lb << endl;
cout << "ub = " << ub << endl;
cout << "val = " << val << endl;
return (lb <= val && val <= ub);
}
}; int main()
{
vector<int> vec = {,,,,,,,,,};
auto presult = find_if(vec.begin(), vec.end(), std::bind(range{}, , , std::placeholders::_1));
auto second = find_if(vec.begin(), vec.end(). std::bind(range{}, std::placeholders::_1, , ));  // 注意,placeholders放在第一位也可以。 cout << *presult << endl;
return ;
}

std::function

初步认识,到底是个啥? 

std::function 在C++11后加入标准,可以用它来描述C++中所有可调用实体,它是 可调用对象的包装器,声明如下:

#include <functional> 

比 “函数指针” 更强大?

std::function 强大的地方在于,它能够 兼容所有具有相同参数类型的函数实体。

相比较于函数指针,std::function能兼容带捕获的lambda函数,而且对类成员函数提供支持。

能力展示,遛一遛?

可以承接各种类型的函数,只要参数一致。

#include <iostream>
#include <functional> // std::function
std::function<int(int, int)> SumFunction; // 普通函数
int func_sum(int a, int b) {
return a + b;
} // 类(成员、静态)函数
class Calcu
{
public:
int base = ;
// 类的成员方法,参数包含this指针
int class_func_sum(const int a, const int b) const { return this->base + a + b; };
// 类的静态成员方法,不包含this指针
static int class_static_func_sum(const int a, const int b) { return a + b; };
}; // 仿函数
class ImitateAdd
{
public:
int operator() (const int a, const int b) const { return a + b; };
}; // lambda函数
auto lambda_func_sum = [](int a, int b) -> int { return a + b; }; // 函数指针
int (*func_pointer)(int, int); int main(void)
{
int x = ;
int y = ; // 普通函数
SumFunction = func_sum;
int sum = SumFunction(x, y);
std::cout << "func_sum:" << sum << std::endl; // 类成员函数
Calcu obj;
SumFunction = std::bind(&Calcu::class_func_sum, obj, std::placeholders::_1, std::placeholders::_2); // 绑定this对象
sum = SumFunction(x, y);
std::cout << "Calcu::class_func_sum:" << sum << std::endl; // 类静态函数
SumFunction = Calcu::class_static_func_sum;
sum = SumFunction(x, y);
std::cout << "Calcu::class_static_func_sum:" << sum << std::endl; // lambda函数
SumFunction = lambda_func_sum;
sum = SumFunction(x, y);
std::cout << "lambda_func_sum:" << sum << std::endl; // 带捕获的lambda函数
int base = ;
auto lambda_func_with_capture_sum = [&base](int x, int y)->int { return x + y + base; };
SumFunction = lambda_func_with_capture_sum;
sum = SumFunction(x, y);
std::cout << "lambda_func_with_capture_sum:" << sum << std::endl; // 仿函数
ImitateAdd imitate;
SumFunction = imitate;
sum = SumFunction(x, y);
std::cout << "imitate func:" << sum << std::endl; // 函数指针
func_pointer = func_sum;
SumFunction = func_pointer;
sum = SumFunction(x, y);
std::cout << "function pointer:" << sum << std::endl; return ;
}

Lambda 函数

5. Lambda Functions

初步认识

可参考:[c++] C Language Features

“Lambda 表达式” (lambda expression)是一个匿名函数,Lambda表达式基于数学中的λ演算得名,直接对应于其中的lambda抽象(lambda abstraction),是一个匿名函数,即没有函数名的函数。

Lambda表达式可以表示闭包(注意和数学传统意义上的不同)。

返回值

由于Lambda的类型是唯一的,不能通过类型名来显式声明对应的对象,但可以利用auto关键字和类型推导:

auto f=[](int a,int b){return a>b;};

或者,返回值类型转换。

std::cout << [](float f)        { return std::abs(f); } (-3.5);
std::cout << [](float f) -> int { return std::abs(f); } (-3.5);

这个语句与前面的不同之处在于,lambda 表达式的返回时不是 float 而是 int。

第一个返回3.5;第二个返回3。

Return Type for Lambdas

参数

基本定义

[]        // 不捕获任何外部变量
[=] // 以值的形式捕获所有外部变量
[&] // 以引用形式捕获所有外部变量
[x, &y] // x 以传值形式捕获,y 以引用形式捕获
[=, &z] // z 以引用形式捕获,其余变量 以传值形式捕获
[&, x] // x 以值的形式捕获,其余变量 以引用形式捕获

捕获外部变量

float f0 = 1.0;
std::cout << [=](float f) { return f0 + std::abs(f); } (-3.5); 传值:其输出值是 4.5 --------------------------------------------------------------------------------- float f0 = 1.0;
std::cout << [&](float f) { return f0 += std::abs(f); } (-3.5);
std::cout << '\n' << f0 << '\n'; 传引用:输出值是 4.5 和 4.5 --------------------------------------------------------------------------------- float f0 = 1.0;
std::cout << [=](float f) mutable { return f0 += std::abs(f); } (-3.5);
std::cout << '\n' << f0 << '\n'; 如果以传值的形式捕获外部变量,那么,lambda 体不允许修改外部变量。
你会觉得输出值是什么呢?答案是,4.5 和 1.0。 --------------------------------------------------------------------------------- float f0 = 1.0f;
float f1 = 10.0f;
std::cout << [=, &f0](float a) { return f0 += f1 + std::abs(a); } (-3.5);
std::cout << '\n' << f0 << '\n'; 混合机制:这个例子的输出是 14.5 和 14.5。

不捕获外部变量

** 捕获 **
auto f = [x](int a,int b){return a>x;}); // x被捕获复制
int x=,y=;
auto g = [&](int x){return ++y;});    // y被捕获引用,调用g后会修改y,需要注意y的生存期 (y是返回值)
auto g = [&y](int x) {return ++y;});   // 这样也是可以的貌似。 ** 未捕获 **
bool(*fp)(int,int) = [](int a,int b){return a>b;}); // 不捕获时才可转换为“函数指针”

End.

[c++] Callable Objects的更多相关文章

  1. Callable Objects

    We learned in 7.11 that there are "array-like" objects that are not true arrays but can be ...

  2. c++11 Using Callable Objects, std::thread, std::bind, std::async, std::call_once

  3. is_callable Callbacks / Callables What is a “callable”? 可调用 回调函数

    PHP: Callback / Callable 类型 - Manual https://www.php.net/manual/zh/language.types.callable.php Callb ...

  4. C++11语法糖

    1.constexpr变量:声明为constexpr的变量一定是一个常量,新标准允许定义一种特殊的constexpr函数使得编译时就可计算结果,这样就能用constexpr函数去初始化constexp ...

  5. 【翻译十九】-java之执行器

    Executors In all of the previous examples, there's a close connection between the task being done by ...

  6. [Code::Blocks] Install wxWidgets & openCV

    The open source, cross platform, free C++ IDE. Code::Blocks is a free C++ IDE built to meet the most ...

  7. C++11lambda表达式

    [C++11lambda表达式] mutable 修饰符,用于修改[]中以值传递的变量,无mutable修饰符的话则不行. 使用示例: #include <vector> #include ...

  8. 在 tornado 中异步无阻塞的执行耗时任务

    在 tornado 中异步无阻塞的执行耗时任务 在 linux 上 tornado 是基于 epoll 的事件驱动框架,在网络事件上是无阻塞的.但是因为 tornado 自身是单线程的,所以如果我们在 ...

  9. Java Concurrency - 线程执行器

    Usually, when you develop a simple, concurrent-programming application in Java, you create some Runn ...

随机推荐

  1. const 使用一二

    Primer C++ 练习题4.20: int i = -1; const int ic = i; 对于这个,一开始认为,ic 作为const 类型变量,定义时应该给其赋常值,而此处给的是变量i,因此 ...

  2. java 连接数据库

    1.获取服务器端数据库blog中记录数 package dataprocess; import java.io.BufferedWriter; import java.io.FileWriter; i ...

  3. FAT32 FAT区__FAT表解析

    一. FAT 表概述 位置: 紧跟在文件系统的“保留区”之后 : 有两个数据结构完全相同的FAT(FAT,File Allocation Tbale 文件分配表)组成. 作用: FAT表项,描述文件系 ...

  4. JavaScript判断移动端及pc端访问不同的网站

    JavaScript判断移动端及pc端访问不同的网站 现在很多网站都是分为两个版本,一个pc端的一个移动端的(响应式除外),针对这两个版本,就需要对访问的设备进行判断,如果是pc,就直接访问pc网站, ...

  5. java学习之面向对象(4)

    之前介绍了java面向对象三大特性之一封装,现在来说说三大特性之一继承和抽象类.这些只是我个人的认识,不足之处还请见谅. 1. 继承是面向对象的三大特征之一,那么何为继承呢? 继承是指一个对象直接使用 ...

  6. 导向矢量(Steering Vector)

    导向矢量是阵列天线的所有阵元对具有单位能量窄带信源的响应. 由于阵列响应在不同方向上是不同的,导向矢量与信源的方向是相互关联的,这种关联的独特性依赖于阵列的几何结构.对于同一阵元阵列,导向矢量的每一个 ...

  7. .NET开源OpenID和OAuth解决方案Thinktecture IdentityServer

    现代的应用程序看起来像这样: 典型的交互操作包括: 浏览器与 web 应用程序进行通信 Web 应用程序与 web Api (有时是在他们自己的有时代表用户) 通信 基于浏览器的应用程序与 web A ...

  8. Entity Framework 基础知识走马观花

    本文目录: 一.EF中的edmx文件探秘 二.EF中的代理模式探秘 三.EF中的延迟加载与即时加载 一.EF中的edmx文件 1.1 emdx文件本质:一个XML文件 (1)通过选择以XML方式打开e ...

  9. 实现Div拖拽

    直观的理解div拖拽:当鼠标对着可拖拽部分按住后并拖动,div会跟着鼠标一起运动,并且其运动空间限制在浏览器内部,当放开鼠标时,则div停止运动. 实现div拖拽需要三个重要的事件: (1)onmou ...

  10. lua中清空目录和递归创建目录

    lua中的 lfs.mkdir lfs.rmdir只能针对单个目录,且lfs.rmdir不能清空文件夹 于是我想到了使用os.execute 递归创建目录如下os.execute("mkdi ...