C++11新特性探索:Lambda表达式与函数包装器的实用指南

news2024/11/24 12:47:36

在这里插入图片描述

文章目录

  • 前言
    • 🍉一、Lambda表达式(匿名函数)
      • 🍓1.1 Lambda 表达式的基本语法
      • 🍓1.2 示例:基本 Lambda 表达式
      • 🍓1.3 捕获列表(Capture)
      • 🍓1.4 使用 Lambda 表达式在算法中自定义操作
      • 🍓1.5 `mutable` 关键字
      • 🍓1.6 Lambda 表达式的返回类型推导
    • 🍉二、模板的可变参数
      • 🍓2.1 可变参数模板的基本语法
      • 🍓2.2 基本示例
      • 🍓2.3 使用 `sizeof...` 获取参数数量
      • 🍓2.4 实现 `std::forward` 完美转发
      • 🍓2.5 应用场景
    • 🍉三、通用函数包装器`std::function`
      • 🍓3.1 `std::function` 的基本语法
      • 🍓3.2 使用 `std::function` 包装不同类型的可调用对象
        • 🍑1. 包装普通函数
        • 🍑2. 包装 lambda 表达式
        • 🍑3. 包装函数对象(仿函数)
      • 🍓3.3 使用 `std::function` 作为参数
      • 🍓3.4 `std::function`的实际应用
    • 🍉四、绑定参数`std::bind`
      • 🍓4.1 `std::bind` 的基本语法
      • 🍓4.2 示例:将参数绑定到普通函数
      • 🍓4.3 占位符的使用
      • 🍓4.4 绑定到成员函数
      • 🍓4.5 绑定到成员变量
      • 🍓4.6 `std::bind` 的应用场景
  • 结语


前言

C++11 的发布为现代 C++ 带来了许多革命性的特性,其中 Lambda 表达式和函数包装器是提升代码简洁性和灵活性的代表性工具。Lambda 表达式让开发者能够像函数一样轻松地创建匿名函数,而函数包装器则为灵活地管理和调用可调用对象提供了一个强大的抽象。在这篇文章中,我们将详细探讨 Lambda 表达式和函数包装器的概念、用法以及它们如何在实际项目中提升代码的可读性和效率。


🍉一、Lambda表达式(匿名函数)

在 C++11 中,lambda 表达式(匿名函数)是一种便捷的语法,用于定义短小的函数或回调,特别适合在局部范围内或传递给算法使用。lambda 表达式使代码更简洁、清晰,尤其在需要自定义操作的 STL 算法中非常实用。

🍓1.1 Lambda 表达式的基本语法

Lambda 表达式的基本语法如下:

[capture](parameters) -> return_type {
    // 函数体
};
  • capture:捕获列表,用于捕获 lambda 外部作用域的变量。
  • parameters:参数列表,与普通函数的参数列表相同。
  • return_type:返回类型,可以省略,编译器会自动推导。
  • 函数体:lambda 表达式的执行代码。

🍓1.2 示例:基本 Lambda 表达式

一个简单的 lambda 表达式示例:

#include <iostream>

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

    std::cout << "Sum: " << add(3, 5) << std::endl;  // 输出:Sum: 8
    return 0;
}

这里 add 是一个 lambda 表达式,定义了一个接收两个整数并返回它们和的匿名函数。

🍓1.3 捕获列表(Capture)

捕获列表用于在 lambda 表达式中访问外部变量,常见的捕获方式包括以下几种:

  • 按值捕获 [=]:按值捕获所有外部变量(只读)。
  • 按引用捕获 [&]:按引用捕获所有外部变量(可修改)。
  • 混合捕获 [=, &var]:按值捕获除 var 外的变量,var 按引用捕获。
  • 显式捕获 [a, &b]:按值捕获 a,按引用捕获 b

示例:

#include <iostream>

int main() {
    int x = 10;
    int y = 20;

    auto lambda1 = [=]() { return x + y; };  // 按值捕获 x 和 y
    auto lambda2 = [&]() { x += 10; y += 10; };  // 按引用捕获 x 和 y

    lambda2();
    std::cout << "x: " << x << ", y: " << y << std::endl;  // 输出 x: 20, y: 30

    return 0;
}

🍓1.4 使用 Lambda 表达式在算法中自定义操作

Lambda 表达式在 STL 算法中非常有用,例如,可以用 std::sort 对容器中的元素进行自定义排序:

#include <vector>
#include <algorithm>
#include <iostream>

int main() {
    std::vector<int> numbers = {3, 1, 4, 1, 5};

    // 使用 Lambda 表达式进行降序排序
    std::sort(numbers.begin(), numbers.end(), [](int a, int b) {
        return a > b;
    });

    for (int num : numbers) {
        std::cout << num << " ";
    }
    // 输出: 5 4 3 1 1

    return 0;
}

🍓1.5 mutable 关键字

默认情况下,lambda 表达式按值捕获的变量是只读的。如果需要修改这些变量,可以使用 mutable 关键字:

int value = 10;
auto lambda = [=]() mutable { value += 5; return value; };
std::cout << lambda() << std::endl;  // 输出 15
std::cout << value << std::endl;      // 输出 10,不影响原变量

在这个例子中,mutable 允许对按值捕获的 value 进行修改,但不会影响原变量 value 的值。

🍓1.6 Lambda 表达式的返回类型推导

如果 lambda 表达式的返回类型可以推导,通常可以省略 -> return_type。不过在条件语句等场景下,若返回类型不明确,可以显式指定:

auto divide = [](int a, int b) -> double {
    return static_cast<double>(a) / b;
};

🍉二、模板的可变参数

C++11 引入了 可变参数模板(Variadic Templates),允许模板接受不定数量的模板参数。这种特性极大地增强了模板的灵活性,适用于泛型编程场景,特别是那些参数个数不固定的情况,例如容器的初始化、递归调用和日志函数等。

🍓2.1 可变参数模板的基本语法

可变参数模板使用...来表示不定数量的模板参数。例如:

template<typename... Args>
void func(Args... args) {
    // 函数体
}

其中,Args... 表示可以接收任意数量和类型的模板参数。这些参数在函数体内可以通过 args... 进行展开和使用。

🍓2.2 基本示例

可变参数模板允许编写能接受任意数量参数的函数。在 C++11 中,由于没有折叠表达式,可以通过递归方式处理这些参数:

#include <iostream>

void print() {}  // 基础情况

template<typename T, typename... Args>
void print(T first, Args... args) {
    std::cout << first << " ";
    print(args...);  // 递归调用
}

int main() {
    print(1, 2.5, "Hello", 'A');  // 输出: 1 2.5 Hello A
    return 0;
}

在这个例子中,print 函数每次取出一个参数,然后递归调用自己处理剩下的参数,直到没有参数为止。

🍓2.3 使用 sizeof... 获取参数数量

C++11 提供了 sizeof... 运算符,用于获取可变参数的数量:

template<typename... Args>
void countArgs(Args... args) {
    std::cout << "参数数量: " << sizeof...(args) << std::endl;
}

调用 countArgs(1, 2, 3); 会输出 参数数量: 3

🍓2.4 实现 std::forward 完美转发

在泛型编程中,使用可变参数模板和 std::forward 可以实现完美转发,特别适合构造和包装对象。例如,可以用一个工厂函数将参数转发给构造函数:

#include <utility>
#include <memory>

template<typename T, typename... Args>
std::unique_ptr<T> createObject(Args&&... args) {
    return std::make_unique<T>(std::forward<Args>(args)...);  // 完美转发
}

这段代码将传入的参数 args... 以完美转发的方式传递给对象 T 的构造函数,从而创建对象。

🍓2.5 应用场景

  • 日志和调试:可变参数模板可以轻松实现日志函数,支持输出任意数量的参数。
  • 工厂函数:通过完美转发和可变参数模板,可以创建一个工厂函数,用来构造任意数量参数的对象。
  • 容器初始化:可以实现一个函数,用来向容器中批量插入元素。

🍉三、通用函数包装器std::function

std::function 是 C++11 引入的一个通用函数包装器,可以存储、复制和调用任何可调用对象,包括普通函数、lambda 表达式、函数指针和函数对象。它提供了一个统一的接口,方便将各种不同类型的可调用对象作为参数传递或返回值返回。

🍓3.1 std::function 的基本语法

std::function 是一个模板类,接受一个函数签名(即返回类型和参数列表)作为模板参数。例如:

#include <functional>
#include <iostream>

// 泛型
std::function<ReturnType(ParameterType1,...,ParameterTypeN)>
// 例如定义一个函数类型:接收两个 int,返回 int
std::function<int(int, int)> func;
  • **ReturnType:**返回值类型
  • **ParameterType1:**形参1
  • **ParameterTypeN:**形参N

在这个例子中,func 可以包装任何符合 int(int, int) 签名的可调用对象,即接收两个 int 参数并返回 int

🍓3.2 使用 std::function 包装不同类型的可调用对象

🍑1. 包装普通函数
int add(int a, int b) {
    return a + b;
}

int main() {
    std::function<int(int, int)> func = add;
    std::cout << "Result: " << func(2, 3) << std::endl;  // 输出:Result: 5
    return 0;
}
🍑2. 包装 lambda 表达式
int main() {
    std::function<int(int, int)> func = [](int a, int b) {
        return a + b;
    };
    std::cout << "Result: " << func(2, 3) << std::endl;  // 输出:Result: 5
    return 0;
}
🍑3. 包装函数对象(仿函数)
#include <iostream>
#include <functional>

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

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

🍓3.3 使用 std::function 作为参数

std::function 可以用来定义一个函数参数,允许将任何符合签名的可调用对象传入该参数。

#include <iostream>
#include <functional>

void execute(std::function<void(int)> func, int value) {
    func(value);  // 调用传入的可调用对象
}

int main() {
    // 使用 lambda 表达式作为参数
    execute([](int x) { std::cout << "Value: " << x << std::endl; }, 10);  // 输出:Value: 10
    return 0;
}

🍓3.4 std::function的实际应用

力扣真题[逆波兰表达式求值](https://leetcode.cn/problems/evaluate-reverse-polish-notation/description/)

class Solution {
public:
    int evalRPN(vector<string>& tokens) {
        map<string, function<int(int, int)>> mp = {
            {"+", [](int x, int y){return x + y;}},
            {"-", [](int x, int y){return x - y;}},
            {"*", [](int x, int y){return x * y;}},
            {"/", [](int x, int y){return x / y;}}
        };
        stack<int> s;
        for(const auto& str :tokens){
            if(mp.find(str) != mp.end()){
                int right = s.top();
                s.pop();
                int left = s.top();
                s.pop();
                int ret = mp[str](left, right);
                s.push(ret);
            }
            else{
                s.push(stoi(str));
            }
        }

        return s.top();
    }
};

🍉四、绑定参数std::bind

std::bind 是 C++11 引入的一个函数工具,用于将函数的某些参数绑定到特定的值,从而生成一个新的可调用对象。这个新对象可以在需要的时候被调用,减少了重复传参的麻烦。std::bind 可以将普通函数、成员函数、函数对象的部分参数预先绑定,也可以为其指定占位符,从而延迟参数传递。

🍓4.1 std::bind 的基本语法

std::bind 的基本用法如下:

std::bind(callable, arg1, arg2, ..., argN);
  • callable:可以是普通函数、成员函数、lambda 表达式或函数对象。
  • arg1, arg2, …, argN:指定函数的参数,可以是具体的值或占位符(std::placeholders::_1std::placeholders::_2 等)。

🍓4.2 示例:将参数绑定到普通函数

假设有一个加法函数 add,我们可以用 std::bind 将其中一个参数绑定到特定的值:

#include <iostream>
#include <functional>

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

int main() {
    // 使用 std::bind 将第一个参数绑定为 10
    auto addTen = std::bind(add, 10, std::placeholders::_1);
    std::cout << "Result: " << addTen(5) << std::endl;  // 输出:Result: 15
    return 0;
}

在这里,addTen 是一个新的可调用对象,它把 add 函数的第一个参数固定为 10,只需要传入第二个参数即可调用。

🍓4.3 占位符的使用

std::bind 使用占位符 std::placeholders::_1, std::placeholders::_2, 等等,来指定调用时需要传递的参数位置。例如:

#include <iostream>
#include <functional>

void print(int x, int y, int z) {
    std::cout << x << ", " << y << ", " << z << std::endl;
}

int main() {
    // 使用 std::bind 重新排列参数顺序
    auto printReversed = std::bind(print, std::placeholders::_3, std::placeholders::_2, std::placeholders::_1);
    printReversed(1, 2, 3);  // 输出:3, 2, 1
    return 0;
}

在这个例子中,printReversed 是一个新的可调用对象,参数顺序被反转,传入 (1, 2, 3) 时,输出为 3, 2, 1

🍓4.4 绑定到成员函数

std::bind 也可以绑定成员函数,使用时需要传入类对象的指针:

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

class MyClass {
public:
    void printMessage(const std::string& message) {
        std::cout << "Message: " << message << std::endl;
    }
};

int main() {
    MyClass obj;

    // 将成员函数与对象绑定(三种方法)
    // 1.对象地址
    auto printFunc1 = std::bind(&MyClass::printMessage, &obj, std::placeholders::_1);
    printFunc1("Hello, world1");  // 输出:Message: Hello, world1
    // 2.对象
    auto printFunc2 = std::bind(&MyClass::printMessage, obj, std::placeholders::_1);
    printFunc2("Hello, world2");  // 输出:Message: Hello, world2
    // 3.匿名对象
    auto printFunc3 = std::bind(&MyClass::printMessage, MyClass(), std::placeholders::_1);
    printFunc3("Hello, world3");  // 输出:Message: Hello, world3

    return 0;
}

在这里,printFuncobj 对象和 printMessage 函数绑定在一起,因此 printFunc 成为一个只需传入 message 参数的可调用对象。

🍓4.5 绑定到成员变量

std::bind 同样可以绑定类的成员变量,用于获取或设置特定对象的成员变量值。

#include <iostream>
#include <functional>

class MyClass {
public:
    int value = 0;
};

int main() {
    MyClass obj;
    // 绑定成员变量,创建 getter 和 setter
    auto getValue = std::bind(&MyClass::value, &obj);
    auto setValue = std::bind(&MyClass::value, &obj) = std::placeholders::_1;

    setValue(42);
    std::cout << "Value: " << getValue() << std::endl;  // 输出:Value: 42

    return 0;
}

🍓4.6 std::bind 的应用场景

  • 回调函数:在事件驱动编程中,可以通过 std::bind 将某个函数的参数预设,用于异步调用。
  • 延迟执行std::bind 可以将函数和参数绑定在一起,在之后的特定时刻再执行。
  • 函数适配器:使用 std::bind 可以适配不同函数的参数列表,减少代码重复。

结语

C++11 的 Lambda 表达式和函数包装器为开发者提供了更灵活、更高效的工具,用于解决复杂的编程问题。从简化回调函数到灵活管理可调用对象,这些特性为现代 C++ 编程提供了新的思路。在深入理解它们的原理和最佳实践后,你将能更自信地运用这些工具编写出简洁、可维护的代码。未来的 C++ 学习之旅中,期待你用这些新特性,探索更强大的编程世界!
在这里插入图片描述

今天的分享到这里就结束啦!如果觉得文章还不错的话,可以三连支持一下,17的主页还有很多有趣的文章,欢迎小伙伴们前去点评,您的支持就是17前进的动力!

在这里插入图片描述

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

msvcp110.dll丢失修复的多种科学方法分析,详细解析msvcp110.dll文件

遇到“msvcp110.dll丢失”的错误时&#xff0c;这表明你的系统缺少一个关键文件&#xff0c;但解决这一问题比较直接。本文将指导你通过几个简单的步骤迅速修复此错误&#xff0c;确保你的程序或游戏可以顺利运行。接下来的操作将非常简洁明了&#xff0c;易于理解和执行。 一.…

HDR视频技术之四:HDR 主要标准

HDR 是 UHD 技术中最重要维度之一&#xff0c;带来新的视觉呈现体验。 HDR 技术涉及到采集、加工、传输、呈现等视频流程上的多个环节&#xff0c;需要定义出互联互通的产业标准&#xff0c;以支持规模化应用和部署。本文整理当前 HDR 应用中的一些代表性的国际标准。 1 HDR 发…

Bug Fix 20241122:缺少lib文件错误

今天有朋友提醒才突然发现 gitee 上传的代码存在两个很严重&#xff0c;同时也很低级的错误。 因为gitee的默认设置不允许二进制文件的提交&#xff0c; 所以PH47框架下的库文件&#xff08;各逻辑层的库文件&#xff09;&#xff0c;以及Stm32Cube驱动的库文件都没上传到Gi…

c++源码阅读__smart_ptr__正文阅读

文章目录 简介源码解析1. 引用计数的实现方式2. deleter静态方法的赋值时间节点3.make_smart的实现方式 与 好处4. 几种构造函数4.1 空构造函数4.2 接收指针的构造函数4.3 接收指针和删除方法的构造函数 , 以及auto进行模板lambda的编写4.4 拷贝构造函数4.5 赋值运算符 5. rele…

【BUG】ES使用过程中问题解决汇总

安装elasticsearch内存不足问题 问题回顾 运行kibana服务的时候&#xff0c;无法本地访问 解决 首先排查端口问题&#xff0c;然后检查错误日志 无法运行kibana服务&#xff0c;是因为elasticsearch没有启动的原因 发现致命错误&#xff0c;确定是elasticsearch服务没有运行导…

C语言--分支循环编程题目

第一道题目&#xff1a; #include <stdio.h>int main() {//分析&#xff1a;//1.连续读取int a 0;int b 0;int c 0;while (scanf("%d %d %d\n", &a, &b, &c) ! EOF){//2.对三角形的判断//a b c 等边三角形 其中两个相等 等腰三角形 其余情…

Linux——用户级缓存区及模拟实现fopen、fweite、fclose

linux基础io重定向-CSDN博客 文章目录 目录 文章目录 什么是缓冲区 为什么要有缓冲区 二、编写自己的fopen、fwrite、fclose 1.引入函数 2、引入FILE 3.模拟封装 1、fopen 2、fwrite 3、fclose 4、fflush 总结 前言 用快递站讲述缓冲区 收件区&#xff08;类比输…

git(Linux)

1.git 三板斧 基本准备工作&#xff1a; 把远端仓库拉拉取到本地了 .git --> 本地仓库 git在提交的时候&#xff0c;只会提交变化的部分 就可以在当前目录下新增代码了 test.c 并没有被仓库管理起来 怎么添加&#xff1f; 1.1 git add test.c 也不算完全添加到仓库里面&…

GESP2023年9月认证C++四级( 第三部分编程题(1-2))

编程题1&#xff08;string&#xff09;参考程序&#xff1a; #include <iostream> using namespace std; long long hex10(string num,int b) {//int i;long long res0;for(i0;i<num.size();i) if(num[i]>0&&num[i]<9)resres*bnum[i]-0;else //如果nu…

Ultiverse 和web3新玩法?AI和GameFi的结合是怎样

Gamef 和 AI 是我们这个周期十分看好两大赛道之一&#xff0c;(Gamef 拥有极强的破圈效应&#xff0c;引领 Web2 用户进军 Web3 最佳利器。AI是这个周期最热门赛道&#xff0c;无论 Web2的 OpenAl&#xff0c;还是 Web3&#xff0c;都成为话题热议焦点。那么结合 GamefiA1双叙事…

如何在 UniApp 中实现 iOS 版本更新检测

随着移动应用的不断发展&#xff0c;保持应用程序的更新是必不可少的&#xff0c;这样用户才能获得更好的体验。本文将帮助你在 UniApp 中实现 iOS 版的版本更新检测和提示&#xff0c;适合刚入行的小白。我们将分步骤进行说明&#xff0c;每一步所需的代码及其解释都会一一列出…

解决 npm xxx was blocked, reason: xx bad guy, steal env and delete files

问题复现 今天一位朋友说&#xff0c;vue2的老项目安装不老依赖&#xff0c;报错内容如下&#xff1a; npm install 451 Unavailable For Legal Reasons - GET https://registry.npmmirror.com/vab-count - [UNAVAILABLE_FOR_LEGAL_REASONS] vab-count was blocked, reas…

【AI系统】GPU 架构回顾(从2018年-2024年)

Turing 架构 2018 年 Turing 图灵架构发布&#xff0c;采用 TSMC 12 nm 工艺&#xff0c;总共 18.6 亿个晶体管。在 PC 游戏、专业图形应用程序和深度学习推理方面&#xff0c;效率和性能都取得了重大进步。相比上一代 Volta 架构主要更新了 Tensor Core&#xff08;专门为执行…

每天五分钟机器学习:支持向量机数学基础之超平面分离定理

本文重点 超平面分离定理(Separating Hyperplane Theorem)是数学和机器学习领域中的一个重要概念,特别是在凸集理论和最优化理论中有着广泛的应用。该定理表明,在特定的条件下,两个不相交的凸集总可以用一个超平面进行分离。 定义与表述 超平面分离定理(Separating Hy…

docker镜像源配置、换源、dockerhub国内镜像最新可用加速源(仓库)

一、临时拉取方式 在docker pull后先拼接镜像源域名&#xff0c;后面拼接拉取的镜像名 $ docker pull dockerpull.org/continuumio/miniconda3 二、永久配置方式 vim修改/etc/docker/daemon.json&#xff0c;并重启docker服务。 # 创建目录 sudo mkdir -p /etc/docker# 写…

电脑使用——知乎、钉钉组件访问失败解决

最近发现办公电脑知乎、钉钉内置组件访问不了&#xff0c;但同网络下笔记本可以访问&#xff1b;经过检测排除了目标服务异常、防火墙拦截的原因&#xff1b;最后发现是DNS的原因&#xff0c;调整DNS首先项1.1.1.1为114.114.114.114后解决&#xff0c;现插眼记录 首先排除拦截&…

Consumer Group

不&#xff0c;kafka-consumer-groups.sh 脚本本身并不用于创建 Consumer Group。它主要用于管理和查看 Consumer Group 的状态和详情&#xff0c;比如列出所有的 Consumer Group、查看特定 Consumer Group 的详情、删除 Consumer Group 等。 Consumer Group 是由 Kafka 消费者…

pandas与open读取csv/txt文件速度比较

pandas与open读取csv/txt文件速度比较 由于在工作中经常需要读取txt或csv文件&#xff0c;使用pandas与open均可以读取并操作文件内容&#xff0c;但不知道那个速度更快一些&#xff0c;所以写了一个脚本去比较在文件大小不同的情况下读取数据的速度 测试结果: 大小pandas速度…

观察者模式和订阅模式

观察者模式和订阅模式在概念上是相似的&#xff0c;它们都涉及到一个对象&#xff08;通常称为“主题”或“发布者”&#xff09;和多个依赖对象&#xff08;称为“观察者”或“订阅者”&#xff09;之间的关系。然而&#xff0c;尽管它们有相似之处&#xff0c;但在某些方面也…

自主研发,基于PHP+ vue2+element+ laravel8+ mysql5.7+ vscode开发的不良事件管理系统源码,不良事件管理系统源码

不良事件上报系统源码&#xff0c;不良事件管理系统源码&#xff0c;PHP源码 不良事件上报系统通过 “事前的人员知识培训管理和制度落地促进”、“事中的事件上报和跟进处理”、 以及 “事后的原因分析和工作持续优化”&#xff0c;结合预存上百套已正在使用的模板&#xff0…