[c++] lambda 表达式

在 c++ 中,一个可以执行的任务有多种形式:函数,lambda 表达式,std::function 对象,std::bind 绑定的任务,std::packaged_task 封装的任务。
 

1 lambda 表达式

lambda 表达式类似于匿名函数,在需要传递一段计算逻辑时,往往不想也不需要把这段逻辑声明定义到一个函数里,这个时候就可以使用 lambda 表达式。使用 lambda 表达式,就像使用一个局部变量一样方便,在函数内部声明,也可以将 lambda 表达式当成一个参数进行传递。

lambda 表达式的格式如下:

[捕获列表](形参列表) -> 选项 返回值类型 {函数体}

如下是一个 lambda 表达式的例子,在捕获列表中捕获了 base,函数形参是两个整型数,函数体中计算了两个整型数的以及 base 的和,返回值是 int 类型。

lambda 表达式函数体内只可以使用捕获列表中的变量,形参列表中的变量。形参列表是使用 lambda 表达式时传递过来的参数,捕获列表是捕获的外部的变量。

#include <iostream>
#include <string>

int main() {
  int base = 100;
  auto sum_func = [base](int a, int b) -> int {
    return base + a + b;
  };

  std::cout << sum_func(1, 2) << std::endl;
  return 0;
}

1.1 值捕获,引用捕获

值捕获与引用捕获和调用函数时的值传递和引用传递的语义是类似的。值捕获,在创建表达式的时候,已经把值捕获了,也就是说创建表达式之后再修改这个值,对 lambda 表达式也是没有影响的;引用捕获,在表达式调用的时候才会去取值,所以如果创建表达式之后又修改了引用的值,那么表达式中捕获的值也会发生变化。

如下代码,两个 lambda 表达式,分别对 base做了值捕获和引用捕获。在捕获之前,base 值是 100,在创建 lambda 表达式之后,将 base 的值修改为 200,之后再执行 lambda 表达式。可以看到值捕获的 base 没有发生改变,还是 100;引用捕获的 base 变成了 200。

#include <iostream>
#include <string>

int main() {
  int base = 100;
  auto sum_func_value = [base](int a, int b) -> int {
    return base + a + b;
  };
  auto sum_func_ref = [&base](int a, int b) -> int {
    return base + a + b;
  };

  base = 200;
  std::cout << "lambda value captutre: " << sum_func_value(1, 2) << std::endl;
  std::cout << "lambda ref captutre: " << sum_func_ref(1, 2) << std::endl;
  return 0;
}

1.2 隐式捕获

如果捕获的参数比较多的话,又不想一个一个写,这个时候可以使用隐式捕获。

(1)隐式捕获也分隐式值捕获和隐式引用捕获:[=],[&]

(2)隐式捕获可以和显式捕获混合使用,但是要注意两点规范

  ① 隐式捕获需要放在捕获列表的第一个位置

  ② 隐式引用捕获和显式值捕获可以混合使用,隐式值捕获和显式引用捕获可以混合使用;隐式引用捕获和显式引用捕获可以在一块使用,但是回报编译告警;隐式值捕获和显式值捕获也可以在一块使用,也会有编译告警。

#include <iostream>
#include <string>

int main() {
  int base = 100;
  int extend = 200;
  auto sum_func1 = [=]() -> int {
    return base + extend;
  };
  auto sum_func2 = [&]() -> int {
    return base + extend;
  };

  auto sum_func3 = [&, base]() -> int {
    return base + extend;
  };

  // 隐式引用捕获和显式引用捕获
  // 一般不这么用,因为都是引用捕获,只使用隐式捕获就可以了
  // 这样使用会有编译告警
  // 隐式引用捕获和显式值捕获
  // 隐式值捕获和显式引用捕获,一般这么用
  auto sum_func4 = [&, &base]() -> int {
    return base + extend;
  };

  auto sum_func5 = [=, &base]() -> int {
    return base + extend;
  };

  // 隐式捕获需要放在捕获列表的第一个位置
  /*
  auto sum_func6 = [&base, =]() -> int {
    return base + extend;
  };
  */

  // 隐式捕获可以和显式捕获混合使用
  // 但是,隐式捕获需要放在捕获列表的第一个位置
  /*
  auto sum_func7 = [base, &]() -> int {
    return base + extend;
  };
  */

  base = 110;
  extend = 210;
  std::cout << "sum func1: " << sum_func1() << std::endl;
  std::cout << "sum func2: " << sum_func2() << std::endl;
  std::cout << "sum func3: " << sum_func3() << std::endl;
  std::cout << "sum func4: " << sum_func4() << std::endl;
  std::cout << "sum func5: " << sum_func5() << std::endl;
  return 0;
}

1.3 表达式捕获

如下是一个表达式捕获的例子,在捕获列表中是一个表达式,表达式左边的值不需要指定类型,指定类型会报编译错误。

#include <iostream>
#include <string>

int main() {
  int base = 100;
  int extend = 200;
  // 表达式捕获不需要指定参数类型
  auto sum_func_value = [/*int*/ value = base + extend](int a, int b) -> int {
    return value + a + b;
  };
  std::cout << "lambda value captutre: " << sum_func_value(1, 2) << std::endl;
  return 0;
}

1.4 选项

默认情况下,捕获的值是只读的,不能修改;如果选项中使用 mutable 关键字修饰,则可以修改。

#include <iostream>
#include <string>

int main() {
  int base = 100;
  // 默认情况下,捕获的值是只读的,不能修改
  // 所以在函数内修改会报编译错误
  /*
  auto func1 = [base]() -> int {
    base = base + 1;
    return base;
  };
  */

  // 选项中使用 mutable,那么捕获的变量在函数体内可以修改
  auto func2 = [base]() mutable -> int {
    base = base + 2;
    return base;
  };

  // std::cout << "func1: " << func1() << std::endl;
  std::cout << "func2: " << func2() << std::endl;
  return 0;
}

2 std::function

std::function 可以是一个函数,一个 lambda 表达式或者是一个通过 std::bind 封装的任务。std::function 为函数作为参数传递提供了方便。

如下是 std::function 中的一个示例代码。

#include <functional>
#include <iostream>

struct Foo
{
    Foo(int num) : num_(num) {
      std::cout << "Foo(), num_ = " << num_ << std::endl;
    }
    void print_add(int i) const { std::cout << num_ + i << '\n'; }
    int num_;
};

void print_num(int i)
{
    std::cout << i << '\n';
}

struct PrintNum
{
    void operator()(int i) const
    {
        std::cout << i << '\n';
    }
};

int main()
{
    // 普通函数
    std::cout << "普通函数 ----------------\n";
    std::function<void(int)> f_display = print_num;
    f_display(-9);

    // lamda 表达式
    std::cout << "lambda 表达式 ----------------\n";
    std::function<void()> f_display_42 = []() { print_num(42); };
    f_display_42();

    // std::bind 包装的任务
    std::cout << "std::bind 普通函数 ----------------";
    std::function<void()> f_display_31337 = std::bind(print_num, 31337);
    f_display_31337();

    // 保存一个成员函数
    // 使用成员函数的时候需要指定一个对象
    std::cout << "类成员函数 ----------------";
    std::function<void(const Foo&, int)> f_add_display = &Foo::print_add;
    const Foo foo(314159);
    f_add_display(foo, 1);
    f_add_display(314160, 1);

    // store a call to a data member accessor
    // 还能使用一个类成员进行赋值 ??? 这种使用方法很少见
    std::cout << "成员变量 ----------------";
    std::function<int(Foo const&)> f_num = &Foo::num_;
    std::cout << "num_: " << f_num(foo) << '\n';

    // store a call to a member function and object
    // std::bind 类的成员函数
    std::cout << "std::bind 成员函数 ----------------";
    using std::placeholders::_1;
    std::function<void(int)> f_add_display2 = std::bind(&Foo::print_add, foo, _1);
    f_add_display2(2);

    // store a call to a member function and object ptr
    std::function<void(int)> f_add_display3 = std::bind(&Foo::print_add, &foo, _1);
    f_add_display3(3);

    // store a call to a function object
    std::cout << "类重载符 ----------------";
    std::function<void(int)> f_display_obj = PrintNum();
    f_display_obj(18);

    std::cout << "递归调用 ----------------";
    auto factorial = [](int n)
    {
        // store a lambda object to emulate "recursive lambda"; aware of extra overhead
        std::function<int(int)> fac = [&](int n) { return (n < 2) ? 1 : n * fac(n - 1); };
        // note that "auto fac = [&](int n) {...};" does not work in recursive calls
        return fac(n);
    };
    for (int i{5}; i != 8; ++i)
        std::cout << i << "! = " << factorial(i) << ";  ";
    std::cout << '\n';
}

在使用 std::function 的时候,也经常使用下边的方式。

(1)使用 using 定义函数类型

(2)std::function 的模板类型就是使用 using 定义的函数类型

当写一个框架,接收外部传递进来的函数时,常常这么使用。using 定义的函数类型,还可以当做一个类型,对函数地址进行类型转换。

#include <iostream>
#include <string>
#include <functional>

using callback = int(int a, int b);
std::function<callback> func;

int sum(int a, int b) {
  return a + b;
}

int sub(int a, int b) {
  return a - b;
}

int main() {
  func = sum;
  std::cout << func(1, 2) << std::endl;

  func = (callback *)sub;
  std::cout << func(1, 2) << std::endl;

  auto sum_addr = (void *)sum;
  std::cout << "sum addr = " << sum_addr << std::endl;
  func = (callback *)sum_addr;
  std::cout << func(10, 20) << std::endl;
  return 0;
}

3 std::packaged_task

std::package_task,从名字也可以看出来,表示一个打包的任务。这个任务自己并不能执行,而是需要显式的调用来执行。

如下是官网的示例代码。

(1)task 能够封装的任务类型包括 lambda 表达式,bind 的函数,也可以是一个单纯的函数。

std::packaged_task 是对执行任务进行的封装,封装之后 std::packaged_task 还有自己的属性,比如 std::future 可以获取任务执行的结果;std::function 可以用一个任务进行赋值,没有增加其它属性。

(2)与 std::promise 类似,std::packaged_task 也可以获取一个 std::future,std::future 可以获取到 std::package_task 执行的结果

std::packaged_task - cppreference.com

#include <cmath>
#include <functional>
#include <future>
#include <iostream>
#include <thread>
 
// unique function to avoid disambiguating the std::pow overload set
int f(int x, int y) { return std::pow(x, y); }
 
void task_lambda()
{
    std::packaged_task<int(int, int)> task([](int a, int b)
    {
        return std::pow(a, b);
    });
    std::future<int> result = task.get_future();
 
    // 封装一个 lamda 表达式,可以将 task 当成函数名来直接调用
    task(2, 9);
 
    std::cout << "task_lambda:\t" << result.get() << '\n';
}
 
void task_bind()
{
    std::packaged_task<int()> task(std::bind(f, 2, 11));
    std::future<int> result = task.get_future();
 
    // 封装的 bind() 函数,可以直接调用
    task();
 
    std::cout << "task_bind:\t" << result.get() << '\n';
}
 
void task_thread()
{
    std::packaged_task<int(int, int)> task(f);
    std::future<int> result = task.get_future();
    // 非 bind 方式,不能这样直接调用
    // std::cout << "directly call: " << task(2, 10) << std::endl;
    std::thread task_td(std::move(task), 2, 10);
    task_td.join();
 
    std::cout << "task_thread:\t" << result.get() << '\n';
}
 
int main()
{
    task_lambda();
    task_bind();
    task_thread();
    return 0;
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mfbz.cn/a/464134.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

MySQL | 表的约束

目录 1. 空属性 NULL 2. 默认值 DEFAULT 3. 列描述comment 4. zerofill 5. 主键 PRIMARY KEY 6. 自增长AUTO_INCREMENT 7. 唯一键UNIQUE 8. 外键 真正约束字段的是数据类型&#xff0c;但是数据类型约束很单一&#xff0c;需要有一些额外的约束&#xff0c;更好的保证数…

VS2019加QT5.14中Please assign a Qt installation in ‘Qt Project Settings‘.问题的解决

第一篇&#xff1a; 原文链接&#xff1a;https://blog.csdn.net/aoxuestudy/article/details/124312629 error:There’ no Qt version assigned to project mdi.vcxproj for configuration release/x64.Please assign a Qt installation in “Qt Project Settings”. 一、分…

AG32 MCU以太网应用实例demo

一. 前言 AGM32系列32位微控制器旨在为MCU用户提供新的自由度和丰富的兼容外设&#xff0c;以及兼容的引脚和功能。AG32F407系列产品具有卓越的品质&#xff0c;稳定性和卓越的价格价值。 AG32产品线支持其所有接口外设尽可能接近主流兼容性&#xff0c;并提供丰富的参考设计…

机器人路径规划:基于深度优先搜索(Depth-First-Search,DFS)算法的机器人路径规划(提供Python代码)

一、深度优先搜索算法介绍 深度优先搜索算法&#xff08;Depth-First-Search&#xff09;的基本思想是沿着树的深度遍历树的节点&#xff0c;尽可能深的搜索树的分支。当节点v的所有边都己被探寻过&#xff0c;搜索将回溯到发现节点v的那条边的起始节点。这一过程一直进行到已…

代码学习记录21--回溯算法第二天

随想录日记part21 t i m e &#xff1a; time&#xff1a; time&#xff1a; 2024.03.16 主要内容&#xff1a;今天主要是结合类型的题目加深对回溯算法的理解&#xff1a;1&#xff1a;组合总和&#xff1b;2&#xff1a;电话号码的字母组合 216.组合总和III17.电话号码的字母…

维基百科推广秘诀13个方法助你成为行业领导者-华媒舍

维基百科&#xff08;Wikipedia&#xff09;作为全球最大、最权威的在线百科全书&#xff0c;拥有海量的知识内容&#xff0c;被广大用户广泛使用。对于任何一个领域的从业者来说&#xff0c;建立自己的维基百科页面&#xff0c;无疑是提升行业影响力的重要手段。本文将向您介绍…

LEETCODE 100255. 成为 K 特殊字符串需要删除的最少字符数

整体思路: 1.可以看到这道题是要求是最小的&#xff0c;那么可以想到遍历所有情况 2.把题干已知条件转换为一个数组&#xff0c;那么只需要以数组每个元素为开头遍历所有情况即可。 3.对于一个数考虑其后面的情况&#xff0c;其后每个数等于这个数k和数本身的最小值(遍历累计求…

【C语言】指针基础知识(一)

计算机上CPU&#xff08;中央处理器&#xff09;在处理数据的时候&#xff0c;需要的数据是在内存中读取的&#xff0c;处理后的数据也会放回内存中。 一,内存和地址 内存被分为一个个单元&#xff0c;一个内存单元的大小是一个字节。 内存单元的编号&#xff08;可以理解为门…

Ypay源支付2.8.8免授权聚合免签系统

本帖最后由 renleixiaoxu 于 2024-3-15 09:46 编辑 产品介绍 XPay是专为个人站长打造的聚合免签系统&#xff0c;拥有卓越的性能和丰富的功能。采用全新轻量化的界面UI&#xff0c;让您可以更加方便快捷地解决 知识付费和运营赞助的难题。同时&#xff0c;它基于高性能的Thin…

ubuntu安装docker的详细教程

检查卸载老版本docker ubuntu下自带了docker的库&#xff0c;不需要添加新的源。 但是ubuntu自带的docker版本太低&#xff0c;需要先卸载旧的再安装新的。 注&#xff1a;docker的旧版本不一定被称为docker&#xff0c;docker.io 或 docker-engine也有可能&#xff0c;所以卸…

Hypermesh碰撞安全之头部撞击模拟

1、首先到自定义工作面板中选择Engineering Solutions(工程解决方案&#xff09; 2、进入行人保护建模流程模块 3、导入所需要的模型 4、对模型进行切割&#xff0c;选择所需要保留的区域 5、单击next进入下一界面 6、选择打击类型 下一步进入&#xff1a; 这样就完成了打击点…

基于深度学习的唇语识别系统的设计与实现

概要 人工智能作为三大工程之一&#xff0c;从上个世纪至今仍然活跃于各个行业的研究与应用之中&#xff0c;应时代的热潮方向&#xff0c;本 课题主要针对深度学习技术应用于唇语识别当中&#xff0c;实现词语唇语的翻译功能。唇语识别在图像处理中一直是一个富 有挑战性的课题…

基础知识学习 -- qnx 系统

QNX是一个基于优先级抢占的系统。 这也导致其基本调度算法相对比较简单。因为不需要像别的通用操作系统考虑一些复杂的“公平性”&#xff0c;只需要保证“优先级最高的线程最优先得到 CPU”就可以了。 基本调度算法 调度算法&#xff0c;是基于优先级的。QNX的线程优先级&a…

【LabVIEW FPGA入门】单周期定时循环

单周期定时循环详解 单周期定时环路是FPGA编程中最强大的结构之一。单周期定时循环中的代码更加优化&#xff0c;在FPGA上占用更少的空间&#xff0c;并且比标准While循环中的相同代码执行得更快。单周期定时环路将使能链从环路中移除&#xff0c;以节省FPGA上的空间。…

C++算法学习心得八.动态规划算法(4)

1.零钱兑换&#xff08;322题&#xff09; 题目描述&#xff1a; 给定不同面额的硬币 coins 和一个总金额 amount。编写一个函数来计算可以凑成总金额所需的最少的硬币个数。如果没有任何一种硬币组合能组成总金额&#xff0c;返回 -1。 你可以认为每种硬币的数量是无限的。…

QTextToSpeech的使用——Qt

前言 之前随便看了几眼QTextToSpeech的帮助就封装使用了&#xff0c;达到了效果就没再管了&#xff0c;最近需要在上面加功能&#xff08;变换语速&#xff09;&#xff0c;就写了个小Demo后&#xff0c;发现不对劲了。 出现的问题 场景 写了个队列添加到语音播放子线程中&a…

多线程(代码案例: 单例模式, 阻塞队列, 生产者消费者模型,定时器)

设计模式是什么 类似于棋谱一样的东西 计算机圈子里的大佬为了能让小菜鸡的代码不要写的太差 针对一些典型的场景, 给出了一些典型的解决方案 这样小菜鸡们可以根据这些方案(ACM里面叫板子, 象棋五子棋里叫棋谱, 咱这里叫 设计模式), 略加修改, 这样代码再差也差不到哪里去 … …

官方安装配置要求服务器最低2核4G

官方安装配置要求服务器至少2核、4G。 如果服务器低于这个要求&#xff0c;就没有必要安装&#xff0c;因为用户体验超级差。 对于服务器CPU来说&#xff0c;建议2到4核就完全足够了&#xff0c;太多就浪费了&#xff0c;但是内存越大越好&#xff0c;最好是4G以上。 如果服务器…

数据库 | Mysql - [binlog]

INDEX 1 什么是 binlog2 作用3 数据恢复4 主从复制 1 什么是 binlog Mysql server 的日志文件 自动开启 2 作用 数据恢复主从复制 3 数据恢复 实际场景 01.00&#xff1a;数据全量备份08.00&#xff1a;数据丢失&#xff08;比如被人误删&#xff09;09.00&#xff1a;故…
最新文章